chrono_batch_render/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
208 / 294
70.7%
Branches:
124 / 235
52.8%
Functions:
17 / 19
89.5%

overlay/font.cpp
Line Branch Exec Source
1 // SPDX-License-Identifier: BSD-3-Clause
2 #include <stb_truetype.h>
3
4 #include <algorithm>
5 #include <cmath>
6 #include <cstddef>
7 #include <optional>
8 #include <vector>
9
10 #include <dlpack/dlpack.h>
11
12 #include "chrono_batch_render/device/device.h"
13 #include "chrono_batch_render/device/stream.h"
14 #include "chrono_batch_render/overlay/overlay_impl.h"
15 #include "chrono_batch_render/util/error.h"
16
17 extern "C" {
18 extern const unsigned char kChronoBatchRenderFont_inter[];
19 extern const std::size_t kChronoBatchRenderFont_inter_size;
20 }
21
22 namespace chrono::batch_render {
23 namespace {
24
25 constexpr uint32_t kGposKern = 0x6b65726e;
26
27 265 bool Printable(uint32_t code) {
28 265 return (code >= 32 && code < 127) || (code >= 160 && code < 256);
29 }
30
31 296961 int BitCount(uint32_t bits) {
32 296961 int count = 0;
33
2/2
✓ Branch 0 taken 203634 times.
✓ Branch 1 taken 296961 times.
500595 for (; bits != 0; bits &= bits - 1) {
34 203634 ++count;
35 }
36 296961 return count;
37 }
38
39 class Table {
40 public:
41 336354 Table(const unsigned char* data, size_t size, size_t at)
42 336354 : m_data(data), m_size(size), m_at(at) {}
43
44 1208499 uint16_t U16(size_t offset) const {
45 1208499 const size_t at = m_at + offset;
46
1/2
✓ Branch 0 taken 1208499 times.
✗ Branch 1 not taken.
1208499 return at + 2 <= m_size ? uint16_t((m_data[at] << 8) | m_data[at + 1]) : 0;
47 }
48
49 61967 int16_t S16(size_t offset) const {
50 61967 return int16_t(U16(offset));
51 }
52
53 8 uint32_t U32(size_t offset) const {
54 8 return (uint32_t{U16(offset)} << 16) | U16(offset + 2);
55 }
56
57 336354 Table Subtable(size_t offset) const {
58 336354 return {m_data, m_size, m_at + offset};
59 }
60
61 private:
62 const unsigned char* m_data;
63 size_t m_size;
64 size_t m_at;
65 };
66
67 1568 std::optional<uint16_t> CoverageIndex(const Table& coverage, uint16_t glyph) {
68 1568 const uint16_t count = coverage.U16(2);
69
2/2
✓ Branch 0 taken 672 times.
✓ Branch 1 taken 896 times.
1568 if (coverage.U16(0) == 1) {
70
2/2
✓ Branch 0 taken 30532 times.
✓ Branch 1 taken 631 times.
31163 for (uint16_t i = 0; i < count; ++i) {
71
2/2
✓ Branch 0 taken 41 times.
✓ Branch 1 taken 30491 times.
30532 if (coverage.U16(4 + 2u * i) == glyph) {
72 41 return i;
73 }
74 }
75
1/2
✓ Branch 0 taken 896 times.
✗ Branch 1 not taken.
896 } else if (coverage.U16(0) == 2) {
76
2/2
✓ Branch 0 taken 74264 times.
✓ Branch 1 taken 411 times.
74675 for (uint16_t i = 0; i < count; ++i) {
77 74264 const Table range = coverage.Subtable(4 + 6u * i);
78
4/4
✓ Branch 0 taken 38144 times.
✓ Branch 1 taken 36120 times.
✓ Branch 2 taken 485 times.
✓ Branch 3 taken 37659 times.
74264 if (glyph >= range.U16(0) && glyph <= range.U16(2)) {
79 485 return uint16_t(range.U16(4) + glyph - range.U16(0));
80 }
81 }
82 }
83 1042 return std::nullopt;
84 }
85
86 896 uint16_t GlyphClass(const Table& classes, uint16_t glyph) {
87
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 896 times.
896 if (classes.U16(0) == 1) {
88 ✗ const uint16_t first = classes.U16(2);
89
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ if (glyph >= first && glyph - first < classes.U16(4)) {
90 ✗ return classes.U16(6 + 2u * (glyph - first));
91 }
92
1/2
✓ Branch 0 taken 896 times.
✗ Branch 1 not taken.
896 } else if (classes.U16(0) == 2) {
93 896 const uint16_t count = classes.U16(2);
94
2/2
✓ Branch 0 taken 143334 times.
✓ Branch 1 taken 359 times.
143693 for (uint16_t i = 0; i < count; ++i) {
95 143334 const Table range = classes.Subtable(4 + 6u * i);
96
4/4
✓ Branch 0 taken 86132 times.
✓ Branch 1 taken 57202 times.
✓ Branch 2 taken 85595 times.
✓ Branch 3 taken 537 times.
143334 if (glyph >= range.U16(0) && glyph <= range.U16(2)) {
97 537 return range.U16(4);
98 }
99 }
100 }
101 return 0;
102 }
103
104 61967 int16_t XAdvance(const Table& record, uint16_t format) {
105
1/2
✓ Branch 0 taken 61967 times.
✗ Branch 1 not taken.
61967 return (format & 4) != 0 ? record.S16(2 * size_t(BitCount(format & 3u))) : 0;
106 }
107
108 234994 size_t RecordSize(uint16_t format) {
109 234994 return 2 * size_t(BitCount(format & 0xFFu));
110 }
111
112 struct PairTable {
113 Table sub;
114 std::vector<int32_t> index;
115 std::vector<uint16_t> firstClasses;
116 std::vector<uint16_t> secondClasses;
117 };
118
119 7 PairTable BuildPairTable(const Table& sub, const std::vector<uint16_t>& glyphs) {
120 7 PairTable table{sub, {}, {}, {}};
121 7 const Table coverage = sub.Subtable(sub.U16(2));
122
2/2
✓ Branch 0 taken 1568 times.
✓ Branch 1 taken 7 times.
1575 for (const uint16_t glyph : glyphs) {
123 1568 const std::optional<uint16_t> index = CoverageIndex(coverage, glyph);
124
3/3
✓ Branch 0 taken 526 times.
✓ Branch 1 taken 1042 times.
✓ Branch 3 taken 1568 times.
1568 table.index.push_back(index ? *index : -1);
125
2/2
✓ Branch 0 taken 448 times.
✓ Branch 1 taken 1120 times.
1568 if (sub.U16(0) == 2) {
126
1/1
✓ Branch 1 taken 448 times.
448 table.firstClasses.push_back(GlyphClass(sub.Subtable(sub.U16(8)), glyph));
127
1/1
✓ Branch 1 taken 448 times.
448 table.secondClasses.push_back(GlyphClass(sub.Subtable(sub.U16(10)), glyph));
128 }
129 }
130 7 return table;
131 ✗ }
132
133 55865 std::optional<int16_t> PairSetAdjustment(const Table& set, uint16_t second, uint16_t format,
134 size_t stride) {
135 55865 size_t low = 0;
136 55865 size_t high = set.U16(0);
137
2/2
✓ Branch 0 taken 122276 times.
✓ Branch 1 taken 55530 times.
233671 while (low < high) {
138 122276 const size_t middle = (low + high) / 2;
139 122276 const uint16_t glyph = set.U16(2 + middle * stride);
140
2/2
✓ Branch 0 taken 335 times.
✓ Branch 1 taken 121941 times.
122276 if (glyph == second) {
141 335 return XAdvance(set.Subtable(4 + middle * stride), format);
142 }
143
2/2
✓ Branch 0 taken 46732 times.
✓ Branch 1 taken 75209 times.
121941 if (glyph < second) {
144 46732 low = middle + 1;
145 } else {
146 high = middle;
147 }
148 }
149 55530 return std::nullopt;
150 }
151
152 350676 std::optional<int16_t> PairAdjustment(const PairTable& pairs, size_t a, size_t b, uint16_t second) {
153
2/2
✓ Branch 0 taken 233179 times.
✓ Branch 1 taken 117497 times.
350676 if (pairs.index[a] < 0) {
154 233179 return std::nullopt;
155 }
156 117497 const Table& sub = pairs.sub;
157 117497 const auto index = uint16_t(pairs.index[a]);
158 117497 const uint16_t format = sub.U16(4);
159 117497 const size_t firstSize = RecordSize(format);
160 117497 const size_t secondSize = RecordSize(sub.U16(6));
161
2/2
✓ Branch 0 taken 55865 times.
✓ Branch 1 taken 61632 times.
117497 if (sub.U16(0) == 1) {
162
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 55865 times.
55865 if (index >= sub.U16(8)) {
163 ✗ return std::nullopt;
164 }
165 55865 return PairSetAdjustment(sub.Subtable(sub.U16(10 + 2u * index)), second, format,
166 55865 2 + firstSize + secondSize);
167 }
168
1/2
✓ Branch 0 taken 61632 times.
✗ Branch 1 not taken.
61632 if (sub.U16(0) == 2) {
169
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 61632 times.
61632 const uint16_t first = pairs.firstClasses[a];
170 61632 const uint16_t next = pairs.secondClasses[b];
171 61632 const uint16_t firstCount = sub.U16(12);
172 61632 const uint16_t secondCount = sub.U16(14);
173
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 61632 times.
61632 if (first >= firstCount || next >= secondCount) {
174 ✗ return std::nullopt;
175 }
176 61632 return XAdvance(
177 61632 sub.Subtable(16 + (size_t{first} * secondCount + next) * (firstSize + secondSize)),
178 61632 format);
179 }
180 ✗ return std::nullopt;
181 }
182
183 1 std::vector<std::vector<Table>> FindKernLookups(const unsigned char* data, size_t size, int gpos) {
184 1 std::vector<std::vector<Table>> lookups;
185
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 if (gpos <= 0) {
186 return lookups;
187 }
188 1 const Table table(data, size, size_t(gpos));
189 1 const Table features = table.Subtable(table.U16(6));
190 1 const Table list = table.Subtable(table.U16(8));
191 1 std::vector<uint16_t> indices;
192
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 time.
6 for (uint16_t f = 0; f < features.U16(0); ++f) {
193 5 const Table record = features.Subtable(2 + 6u * f);
194
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
5 if (record.U32(0) != kGposKern) {
195 3 continue;
196 }
197 2 const Table feature = features.Subtable(record.U16(4));
198
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 2 times.
11 for (uint16_t i = 0; i < feature.U16(2); ++i) {
199
1/1
✓ Branch 1 taken 9 times.
9 indices.push_back(feature.U16(4 + 2u * i));
200 }
201 }
202 1 std::ranges::sort(indices);
203 1 indices.erase(std::unique(indices.begin(), indices.end()), indices.end());
204
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
4 for (const uint16_t index : indices) {
205
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (index >= list.U16(0)) {
206 ✗ continue;
207 }
208 3 const Table lookup = list.Subtable(list.U16(2 + 2u * index));
209 3 std::vector<Table> subtables;
210
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 3 times.
10 for (uint16_t s = 0; s < lookup.U16(4); ++s) {
211 7 Table sub = lookup.Subtable(lookup.U16(6 + 2u * s));
212 7 uint16_t type = lookup.U16(0);
213
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 4 times.
7 if (type == 9) {
214 3 type = sub.U16(2);
215 3 sub = sub.Subtable(sub.U32(4));
216 }
217
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 if (type == 2) {
218
1/1
✓ Branch 1 taken 7 times.
7 subtables.push_back(sub);
219 }
220 }
221
1/1
✓ Branch 1 taken 3 times.
3 lookups.push_back(std::move(subtables));
222 3 }
223 1 return lookups;
224 1 }
225
226 190 void BakeGlyph(const stbtt_fontinfo& info, int glyph, float units, float cap, OverlayGlyph& out,
227 std::vector<uint8_t>& field) {
228 190 int advance = 0;
229 190 int bearing = 0;
230 190 stbtt_GetGlyphHMetrics(&info, glyph, &advance, &bearing);
231 190 out.advance = float(advance) * units;
232 190 int width = 0;
233 190 int height = 0;
234 190 int left = 0;
235 190 int top = 0;
236 190 unsigned char* baked =
237 190 stbtt_GetGlyphSDF(&info, kOverlayFieldTexelsPerUnit * units, glyph,
238 int(kOverlayFieldPadding), static_cast<unsigned char>(kOverlayFieldEdge),
239 kOverlayFieldLevels, &width, &height, &left, &top);
240
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 188 times.
190 if (baked == nullptr) {
241 2 return;
242 }
243 188 out.texel = uint32_t(field.size());
244 188 out.width = uint32_t(width);
245 188 out.height = uint32_t(height);
246 188 out.x0 = float(left) / kOverlayFieldTexelsPerUnit;
247 188 out.y0 = float(top) / kOverlayFieldTexelsPerUnit + cap;
248 188 out.x1 = float(left + width) / kOverlayFieldTexelsPerUnit;
249 188 out.y1 = float(top + height) / kOverlayFieldTexelsPerUnit + cap;
250 188 field.insert(field.end(), baked, baked + size_t(width) * size_t(height));
251 188 stbtt_FreeSDF(baked, nullptr);
252 }
253
254 1 std::vector<int8_t> BakeKerning(const stbtt_fontinfo& info, const std::array<int, 256>& glyphs,
255 float units) {
256 1 std::vector<uint16_t> kerned;
257
2/2
✓ Branch 0 taken 224 times.
✓ Branch 1 taken 1 time.
225 for (uint32_t code = 0; code < kOverlayKernCodeCount; ++code) {
258
1/1
✓ Branch 1 taken 224 times.
224 kerned.push_back(uint16_t(glyphs[code + kOverlayKernFirstCode]));
259 }
260 1 std::vector<std::vector<PairTable>> lookups;
261 4 for (const std::vector<Table>& subtables : FindKernLookups(
262
3/3
✓ Branch 1 taken 1 time.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 1 time.
4 kChronoBatchRenderFont_inter, kChronoBatchRenderFont_inter_size, info.gpos)) {
263
1/1
✓ Branch 1 taken 3 times.
3 lookups.emplace_back();
264
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 3 times.
10 for (const Table& sub : subtables) {
265
1/1
✓ Branch 1 taken 7 times.
14 lookups.back().push_back(BuildPairTable(sub, kerned));
266 }
267 1 }
268
1/1
✓ Branch 1 taken 1 time.
1 std::vector<int8_t> kerning(size_t{kOverlayKernCodeCount} * kOverlayKernCodeCount, 0);
269
2/2
✓ Branch 0 taken 224 times.
✓ Branch 1 taken 1 time.
225 for (uint32_t a = 0; a < kOverlayKernCodeCount; ++a) {
270
2/2
✓ Branch 0 taken 50176 times.
✓ Branch 1 taken 224 times.
50400 for (uint32_t b = 0; b < kOverlayKernCodeCount; ++b) {
271 50176 int adjust = 0;
272
2/2
✓ Branch 0 taken 150528 times.
✓ Branch 1 taken 50176 times.
200704 for (const std::vector<PairTable>& subtables : lookups) {
273
2/2
✓ Branch 0 taken 350676 times.
✓ Branch 1 taken 88561 times.
439237 for (const PairTable& pairs : subtables) {
274
2/2
✓ Branch 0 taken 61967 times.
✓ Branch 1 taken 288709 times.
350676 if (const std::optional<int16_t> value =
275
2/2
✓ Branch 1 taken 61967 times.
✓ Branch 2 taken 288709 times.
350676 PairAdjustment(pairs, a, b, kerned[b])) {
276 61967 adjust += *value;
277 61967 break;
278 }
279 }
280 }
281 100352 kerning[size_t{a} * kOverlayKernCodeCount + b] =
282
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 50176 times.
100352 int8_t(std::clamp(std::lround(float(adjust) * units * 16.0f), -127L, 127L));
283 }
284 }
285 1 return kerning;
286 1 }
287
288 1 OverlayFont BakeFont() {
289 1 stbtt_fontinfo info;
290
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
1 if (stbtt_InitFont(&info, kChronoBatchRenderFont_inter, 0) == 0) {
291
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ throw Error("Overlay text's font failed to load");
292 }
293 1 int ascent = 0;
294 1 int descent = 0;
295 1 int gap = 0;
296 1 stbtt_GetFontVMetrics(&info, &ascent, &descent, &gap);
297 1 const float units = kOverlayLineUnits / float(ascent - descent);
298 1 int x0 = 0;
299 1 int y0 = 0;
300 1 int x1 = 0;
301 1 int capHeight = 0;
302 1 stbtt_GetCodepointBox(&info, 'H', &x0, &y0, &x1, &capHeight);
303 1 const float cap = float(capHeight) * units;
304 1 OverlayFont font;
305 1 font.textHeight = cap - float(descent) * units;
306 1 std::array<int, 256> glyphs{};
307
2/2
✓ Branch 0 taken 256 times.
✓ Branch 1 taken 1 time.
257 for (uint32_t code = 0; code < 256; ++code) {
308
3/3
✓ Branch 0 taken 191 times.
✓ Branch 1 taken 65 times.
✓ Branch 3 taken 191 times.
447 const int glyph = Printable(code) ? stbtt_FindGlyphIndex(&info, int(code)) : 0;
309
2/2
✓ Branch 0 taken 190 times.
✓ Branch 1 taken 1 time.
191 if (glyph != 0) {
310
1/1
✓ Branch 1 taken 190 times.
190 glyphs[code] = glyph;
311
1/1
✓ Branch 1 taken 190 times.
190 BakeGlyph(info, glyph, units, cap, font.glyphs[code], font.field);
312 }
313 }
314
2/2
✓ Branch 0 taken 256 times.
✓ Branch 1 taken 1 time.
257 for (uint32_t code = 0; code < 256; ++code) {
315
2/2
✓ Branch 0 taken 66 times.
✓ Branch 1 taken 190 times.
256 if (glyphs[code] == 0) {
316 66 font.glyphs[code] = font.glyphs['?'];
317 66 glyphs[code] = glyphs['?'];
318 }
319 }
320
1/1
✓ Branch 1 taken 1 time.
1 font.kerning = BakeKerning(info, glyphs, units);
321 1 return font;
322 ✗ }
323
324 }
325
326 9 const OverlayFont& OverlayTextFont() {
327
4/5
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 8 times.
✓ Branch 3 taken 1 time.
✗ Branch 4 not taken.
✓ Branch 6 taken 1 time.
9 static const OverlayFont font = BakeFont();
328 9 return font;
329 }
330
331 2 std::vector<uint8_t> OverlayCodes(std::string_view text) {
332 2 std::vector<uint8_t> codes;
333
1/1
✓ Branch 1 taken 2 times.
2 codes.reserve(text.size());
334
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 2 times.
11 for (size_t at = 0; at < text.size();) {
335
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 const auto lead = static_cast<unsigned char>(text[at]);
336
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 const size_t length = lead < 0x80 ? 1
337
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ : (lead >> 5) == 6 ? 2
338
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ : (lead >> 4) == 14 ? 3
339
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ : (lead >> 3) == 30 ? 4
340 : 0;
341 9 uint32_t code = '?';
342
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 if (length == 0 || at + length > text.size()) {
343 ✗ ++at;
344 } else {
345
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 code = length == 1 ? lead : lead & (0x7Fu >> length);
346 bool valid = true;
347
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 for (size_t k = 1; k < length; ++k) {
348
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ const auto next = static_cast<unsigned char>(text[at + k]);
349
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ valid = valid && (next >> 6) == 2;
350 ✗ code = (code << 6) | (next & 0x3Fu);
351 }
352
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 at += valid ? length : 1;
353
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 if (!valid) {
354 code = '?';
355 }
356 }
357
3/5
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 9 times.
9 codes.push_back(code == kOverlayNewline || Printable(code) ? uint8_t(code) : uint8_t('?'));
358 }
359 2 return codes;
360 ✗ }
361
362 ✗ device::Tensor RenderOverlayText(device::Device& target, std::string_view text, float lineHeight,
363 Float4 color) {
364 ✗ const OverlayFont& font = OverlayTextFont();
365 ✗ std::vector<uint8_t> codes = OverlayCodes(text);
366 ✗ std::erase(codes, uint8_t(kOverlayNewline));
367
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ const float scale = std::max(lineHeight, 1.0f) / kOverlayLineUnits;
368 ✗ std::vector<float> pens;
369 ✗ float pen = 0;
370
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ for (size_t i = 0; i < codes.size(); ++i) {
371
0/1
✗ Branch 1 not taken.
✗ pens.push_back(pen);
372
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ pen += font.glyphs[codes[i]].advance;
373
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ const uint32_t next = i + 1 < codes.size() ? codes[i + 1] : 0;
374
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ if (codes[i] >= kOverlayKernFirstCode && next >= kOverlayKernFirstCode) {
375 ✗ pen +=
376 ✗ float(
377 ✗ font.kerning[size_t(codes[i] - kOverlayKernFirstCode) * kOverlayKernCodeCount +
378 ✗ (next - kOverlayKernFirstCode)]) /
379 16;
380 }
381 }
382
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ const auto width = int64_t(std::max(std::ceil(pen * scale), 1.0f));
383
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ const auto height = int64_t(std::max(std::ceil(font.textHeight * scale), 1.0f));
384
0/1
✗ Branch 1 not taken.
✗ std::vector<uint8_t> pixels(size_t(width * height * 4), 0);
385 ✗ const float texelsPerPixel = kOverlayFieldTexelsPerUnit / scale;
386
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ for (size_t i = 0; i < codes.size(); ++i) {
387
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ const OverlayGlyph& glyph = font.glyphs[codes[i]];
388
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ if (glyph.width == 0 || glyph.height == 0) {
389 ✗ continue;
390 }
391
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ const float left = (pens[i] + glyph.x0) * scale;
392 ✗ const float top = glyph.y0 * scale;
393 ✗ const float right = (pens[i] + glyph.x1) * scale;
394 ✗ const float bottom = glyph.y1 * scale;
395 ✗ const auto sample = [&](float u, float v) {
396
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ u = std::clamp(u, 0.0f, float(glyph.width - 1));
397
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ v = std::clamp(v, 0.0f, float(glyph.height - 1));
398 ✗ const auto u0 = uint32_t(u);
399 ✗ const auto v0 = uint32_t(v);
400
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ const uint32_t u1 = std::min(u0 + 1, glyph.width - 1);
401
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ const uint32_t v1 = std::min(v0 + 1, glyph.height - 1);
402 ✗ const auto at = [&](uint32_t x, uint32_t y) {
403 ✗ return float(font.field[glyph.texel + size_t(y) * glyph.width + x]);
404 ✗ };
405 ✗ const float fu = u - float(u0);
406 ✗ const float fv = v - float(v0);
407 ✗ return (at(u0, v0) * (1 - fu) + at(u1, v0) * fu) * (1 - fv) +
408 ✗ (at(u0, v1) * (1 - fu) + at(u1, v1) * fu) * fv;
409 ✗ };
410
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ for (auto y = int64_t(std::max(std::floor(top), 0.0f));
411
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ y < std::min(int64_t(std::ceil(bottom)), height); ++y) {
412
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ for (auto x = int64_t(std::max(std::floor(left), 0.0f));
413
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ x < std::min(int64_t(std::ceil(right)), width); ++x) {
414 ✗ const float u = (float(x) + 0.5f - left) / (right - left) * float(glyph.width);
415 ✗ const float v = (float(y) + 0.5f - top) / (bottom - top) * float(glyph.height);
416 ✗ const float distance =
417 ✗ (sample(u - 0.5f, v - 0.5f) - kOverlayFieldEdge) / kOverlayFieldLevels;
418
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ const float coverage = std::clamp(0.5f + distance / texelsPerPixel, 0.0f, 1.0f);
419
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ uint8_t* out = &pixels[size_t((y * width + x) * 4)];
420
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ const auto alpha = uint8_t(std::lround(coverage * color.w * 255));
421
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (alpha > out[3]) {
422
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ out[0] = uint8_t(std::lround(std::clamp(color.x, 0.0f, 1.0f) * 255));
423
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ out[1] = uint8_t(std::lround(std::clamp(color.y, 0.0f, 1.0f) * 255));
424
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ out[2] = uint8_t(std::lround(std::clamp(color.z, 0.0f, 1.0f) * 255));
425 ✗ out[3] = alpha;
426 }
427 }
428 }
429 }
430 ✗ int64_t shape[3] = {height, width, 4};
431 ✗ DLTensor host{};
432
0/1
✗ Branch 1 not taken.
✗ host.data = pixels.data();
433 ✗ host.device = {kDLCPU, 0};
434 ✗ host.ndim = 3;
435 ✗ host.dtype = {kDLUInt, 8, 1};
436 ✗ host.shape = shape;
437
0/2
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✗ return target.Import(host, Stream::None(), "overlay text");
438 ✗ }
439
440 }
441