Skip to content

Commit c78ab9c

Browse files
committed
Render glyph repeat count as subscript
1 parent 17feea7 commit c78ab9c

File tree

1 file changed

+80
-39
lines changed

1 file changed

+80
-39
lines changed

src/main.zig

+80-39
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ const TypingDisplay = struct {
254254
return null;
255255
}
256256

257-
pub fn measure(self: *TypingDisplay, font: rl.Font, font_size: f32, max_width: f32) f32 {
257+
pub fn measure(self: *TypingDisplay, fonts: Fonts, max_width: f32) f32 {
258258
var codepoints: [max_string_len]u21 = undefined;
259259
var num_codepoints: usize = 0;
260260

@@ -270,7 +270,7 @@ const TypingDisplay = struct {
270270
const res = std.fmt.bufPrintZ(&buf, "…{}×", .{repeat}) catch max_repeat_indicator;
271271

272272
num_codepoints = get_codepoints(res, &codepoints);
273-
width += measure_codepoints(font, font_size, codepoints[0..num_codepoints]);
273+
width += measure_codepoints(fonts.subscript_font, codepoints[0..num_codepoints]);
274274
if (width > max_width) {
275275
width = max_width;
276276
break;
@@ -280,7 +280,7 @@ const TypingDisplay = struct {
280280
}
281281

282282
num_codepoints = get_codepoints(repeated_string.string, &codepoints);
283-
width += @as(f32, @floatFromInt(repeat)) * measure_codepoints(font, font_size, codepoints[0..num_codepoints]);
283+
width += @as(f32, @floatFromInt(repeat)) * measure_codepoints(fonts.regular_font, codepoints[0..num_codepoints]);
284284
if (width > max_width) {
285285
width = max_width;
286286
break;
@@ -290,9 +290,8 @@ const TypingDisplay = struct {
290290
return width;
291291
}
292292

293-
fn measure_codepoints(font: rl.Font, font_size: f32, codepoints: []u21) f32 {
293+
fn measure_codepoints(font: rl.Font, codepoints: []u21) f32 {
294294
var width: f32 = 0;
295-
const scale_factor: f32 = font_size / @as(f32, @floatFromInt(font.baseSize));
296295

297296
var i: usize = codepoints.len;
298297
while (i > 0) {
@@ -301,16 +300,16 @@ const TypingDisplay = struct {
301300
const glyph_index: usize = @intCast(rl.getGlyphIndex(font, cp));
302301

303302
const advance: f32 = switch (font.glyphs[glyph_index].advanceX) {
304-
0 => font.recs[glyph_index].width * scale_factor,
305-
else => @as(f32, @floatFromInt(font.glyphs[glyph_index].advanceX)) * scale_factor,
303+
0 => font.recs[glyph_index].width,
304+
else => @as(f32, @floatFromInt(font.glyphs[glyph_index].advanceX)),
306305
};
307306

308307
width += advance;
309308
}
310309
return width;
311310
}
312311

313-
pub fn render(self: *TypingDisplay, position: rl.Vector2, font: rl.Font, font_size: f32, tint: rl.Color) void {
312+
pub fn render(self: *TypingDisplay, position: rl.Vector2, fonts: Fonts, tint: rl.Color) void {
314313
var codepoints: [max_string_len]u21 = undefined;
315314
var num_codepoints: usize = 0;
316315
var rendered_codepoints: usize = 0;
@@ -327,15 +326,19 @@ const TypingDisplay = struct {
327326
const res = std.fmt.bufPrintZ(&buf, "…{}×", .{repeat}) catch max_repeat_indicator;
328327

329328
num_codepoints = get_codepoints(res, &codepoints);
330-
rendered_codepoints = render_codepoints(font, position, font_size, tint, codepoints[0..num_codepoints], &offset);
329+
const subscript_position = rl.Vector2.init(
330+
position.x,
331+
position.y + @as(f32, @floatFromInt(fonts.regular_font.baseSize - fonts.subscript_font.baseSize))
332+
);
333+
rendered_codepoints = render_codepoints(fonts.subscript_font, subscript_position, tint, codepoints[0..num_codepoints], &offset);
331334
if (num_codepoints != rendered_codepoints) break :string_render; // no more fit on screen
332335

333336
repeat = repeat_indicator_threshold;
334337
}
335338

336339
num_codepoints = get_codepoints(repeated_string.string, &codepoints);
337340
for (0..repeat) |_| {
338-
rendered_codepoints = render_codepoints(font, position, font_size, tint, codepoints[0..num_codepoints], &offset);
341+
rendered_codepoints = render_codepoints(fonts.regular_font, position, tint, codepoints[0..num_codepoints], &offset);
339342
if (num_codepoints != rendered_codepoints) break :string_render; // no more fit on screen
340343
}
341344
}
@@ -352,9 +355,8 @@ const TypingDisplay = struct {
352355
return i;
353356
}
354357

355-
fn render_codepoints(font: rl.Font, position: rl.Vector2, font_size: f32, tint: rl.Color, codepoints: []u21, offset: *f32) usize {
358+
fn render_codepoints(font: rl.Font, position: rl.Vector2, tint: rl.Color, codepoints: []u21, offset: *f32) usize {
356359
var number_of_rendered: usize = 0;
357-
const scale_factor: f32 = font_size / @as(f32, @floatFromInt(font.baseSize));
358360

359361
var i: usize = codepoints.len;
360362
while (i > 0) {
@@ -364,8 +366,8 @@ const TypingDisplay = struct {
364366
const glyph_index: usize = @intCast(rl.getGlyphIndex(font, cp));
365367

366368
const advance: f32 = switch (font.glyphs[glyph_index].advanceX) {
367-
0 => font.recs[glyph_index].width * scale_factor,
368-
else => @as(f32, @floatFromInt(font.glyphs[glyph_index].advanceX)) * scale_factor,
369+
0 => font.recs[glyph_index].width,
370+
else => @as(f32, @floatFromInt(font.glyphs[glyph_index].advanceX)),
369371
};
370372
offset.* += advance;
371373

@@ -383,10 +385,10 @@ const TypingDisplay = struct {
383385
.height = font.recs[glyph_index].height,
384386
};
385387
const dst_rec: rl.Rectangle = .{
386-
.x = glyph_position.x + offset_x * scale_factor,
387-
.y = glyph_position.y + offset_y * scale_factor,
388-
.width = font.recs[glyph_index].width * scale_factor,
389-
.height = font.recs[glyph_index].height * scale_factor,
388+
.x = glyph_position.x + offset_x,
389+
.y = glyph_position.y + offset_y,
390+
.width = font.recs[glyph_index].width,
391+
.height = font.recs[glyph_index].height,
390392
};
391393

392394
if (dst_rec.x + dst_rec.width < 0) break; // entire glyph out of screen
@@ -446,19 +448,66 @@ fn getDataForFrame(frame: usize) !?KeyData {
446448
var key_data_producer: ?KeyDataProducer = null;
447449
pub var app_state: AppState = undefined;
448450

449-
// https://github.com/bits/UTF-8-Unicode-Test-Documents/blob/master/UTF-8_sequence_unseparated/utf8_sequence_0-0xfff_assigned_printable_unseparated.txt
450-
const text = @embedFile("resources/utf8_sequence_0-0xfff_assigned_printable_unseparated.txt");
451-
452-
const symbols = "↚↹⏎␣⌫↑←→↓ᴷᴾ⏎…×";
453-
const all_text = text ++ symbols;
454-
455451
// draw various lines helping with rendering debugging and position adjustments
456452
const debug_lines = false;
457453

458454
// it contains all current substitutions symbols:
459455
// TODO: font discovery with fallback when glyph not found
460-
// TODO: support for non-monospaced fonts
461-
const font_data = @embedFile("resources/DejaVuSansMono.ttf");
456+
const Fonts = struct {
457+
regular_codepoints: []i32,
458+
regular_font: rl.Font,
459+
subscript_codepoints: []i32,
460+
subscript_font: rl.Font,
461+
462+
const font_data = @embedFile("resources/DejaVuSansMono.ttf");
463+
464+
// https://github.com/bits/UTF-8-Unicode-Test-Documents/blob/master/UTF-8_sequence_unseparated/utf8_sequence_0-0xfff_assigned_printable_unseparated.txt
465+
const text = @embedFile("resources/utf8_sequence_0-0xfff_assigned_printable_unseparated.txt");
466+
const all_symbols = text ++ "↚↹⏎␣⌫↑←→↓ᴷᴾ⏎…×";
467+
468+
// letters are not used but are added to string to artificial increase atlas size,
469+
// packing does not work well for small sizes,
470+
// see: https://github.com/raysan5/raylib/issues/2550
471+
const subscript_symbols: [:0]const u8 = "…0123456789×" ++ "abcdefghijklmnopqrstuvwyxz";
472+
473+
const subscript_scale_factor = 0.58;
474+
475+
pub fn init(font_size: i32) !Fonts {
476+
const regular_codepoints = try rl.loadCodepoints(all_symbols);
477+
const subscript_codepoints = try rl.loadCodepoints(subscript_symbols);
478+
479+
return .{
480+
.regular_codepoints = regular_codepoints,
481+
.regular_font = loadFont(font_size, regular_codepoints),
482+
.subscript_codepoints = subscript_codepoints,
483+
.subscript_font = loadFont(subscriptFontSize(font_size), subscript_codepoints),
484+
};
485+
}
486+
487+
fn loadFont(font_size: i32, font_chars: []i32) rl.Font {
488+
return rl.loadFontFromMemory(".ttf", font_data, font_size, font_chars);
489+
}
490+
491+
fn subscriptFontSize(font_size: i32) i32 {
492+
return @intFromFloat(subscript_scale_factor * @as(f32, @floatFromInt(font_size)));
493+
}
494+
495+
pub fn updateSize(self: *Fonts, font_size: i32) void {
496+
rl.unloadFont(self.regular_font);
497+
rl.unloadFont(self.subscript_font);
498+
self.regular_font = loadFont(font_size, self.regular_codepoints);
499+
self.subscript_font = loadFont(subscriptFontSize(font_size), self.subscript_codepoints);
500+
}
501+
502+
pub fn deinit(self: *Fonts) void {
503+
rl.unloadCodepoints(self.regular_codepoints);
504+
rl.unloadCodepoints(self.subscript_codepoints);
505+
rl.unloadFont(self.regular_font);
506+
rl.unloadFont(self.subscript_font);
507+
self.* = undefined;
508+
}
509+
510+
};
462511

463512
pub const AppState = struct {
464513
key_states: [MAX_KEYS]KeyOnScreen,
@@ -998,16 +1047,9 @@ pub fn main() !void {
9981047
return;
9991048
}
10001049

1001-
// TODO: implement font discovery
1002-
// TODO: if not found fallback to default
1003-
const codepoints = try rl.loadCodepoints(all_text);
1004-
defer rl.unloadCodepoints(codepoints);
1005-
1006-
std.debug.print("Text contains {} codepoints\n", .{codepoints.len});
1007-
10081050
// TODO: font should be configurable
1009-
var font = rl.loadFontFromMemory(".ttf", font_data, app_state.typing_font_size, codepoints);
1010-
defer rl.unloadFont(font);
1051+
var font = try Fonts.init(app_state.typing_font_size);
1052+
defer font.deinit();
10111053

10121054
var show_gui = false;
10131055

@@ -1098,9 +1140,8 @@ pub fn main() !void {
10981140
}
10991141
},
11001142
.typing_font_size => {
1101-
rl.unloadFont(font);
11021143
// TODO: sanitize, sizes <0 and larger than window height probably should be skipped
1103-
font = rl.loadFontFromMemory(".ttf", font_data, app_config.data.typing_font_size, codepoints);
1144+
font.updateSize(app_config.data.typing_font_size);
11041145
app_state.typing_font_size = app_config.data.typing_font_size;
11051146
},
11061147
inline .background_color, .typing_font_color, .typing_background_color, .key_tint_color => |color| {
@@ -1162,7 +1203,7 @@ pub fn main() !void {
11621203
typing_display.update(k, app_state.backspace_mode);
11631204

11641205
const typing_max_width = 0.95 * @as(f32, @floatFromInt(app_state.window_width));
1165-
typing_display_width = typing_display.measure(font, @floatFromInt(app_state.typing_font_size), typing_max_width);
1206+
typing_display_width = typing_display.measure(font, typing_max_width);
11661207
}
11671208
if (app_state.typing_persistance_sec > 0 and !app_state.typingDisplayTimeElapsed()) {
11681209
typing_display.clear();
@@ -1221,7 +1262,7 @@ pub fn main() !void {
12211262
}
12221263

12231264
const position = rl.Vector2.init(text_center_bounds.x + text_center_bounds.width, text_center_bounds.y);
1224-
typing_display.render(position, font, font_size, app_state.typing_font_color);
1265+
typing_display.render(position, font, app_state.typing_font_color);
12251266
}
12261267

12271268
// button for closing application when window decorations disabled,

0 commit comments

Comments
 (0)