Skip to content
This repository was archived by the owner on May 29, 2024. It is now read-only.

Commit 6f92bfc

Browse files
committed
Copy bitmap font renderer from mclone
- Takes a `.fnt` files and it's associated `.png` files - Handles aligning and drawing text
1 parent 3397698 commit 6f92bfc

File tree

4 files changed

+440
-1
lines changed

4 files changed

+440
-1
lines changed

src/font_render.zig

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
const std = @import("std");
2+
const platform = @import("platform");
3+
const gl = platform.gl;
4+
const math = @import("math");
5+
const Texture = @import("./texture.zig").Texture;
6+
const FlatRenderer = @import("./flat_render.zig").FlatRenderer;
7+
8+
const ArrayList = std.ArrayList;
9+
10+
const util = @import("util");
11+
const Vec2f = math.Vec(2, f32);
12+
const vec2f = Vec2f.init;
13+
14+
pub const BitmapFontRenderer = struct {
15+
pages: []Texture,
16+
glyphs: std.AutoHashMap(u32, Glyph),
17+
lineHeight: f32,
18+
base: f32,
19+
scale: Vec2f,
20+
21+
const Glyph = struct {
22+
page: u32,
23+
pos: Vec2f,
24+
size: Vec2f,
25+
offset: Vec2f,
26+
xadvance: f32,
27+
};
28+
29+
pub fn initFromFile(allocator: *std.mem.Allocator, filename: []const u8) !@This() {
30+
const contents = try platform.fetch(allocator, filename);
31+
defer allocator.free(contents);
32+
33+
const base_path = std.fs.path.dirname(filename) orelse "./";
34+
35+
var pages = ArrayList(Texture).init(allocator);
36+
var glyphs = std.AutoHashMap(u32, Glyph).init(allocator);
37+
var lineHeight: f32 = undefined;
38+
var base: f32 = undefined;
39+
var scaleW: f32 = 0;
40+
var scaleH: f32 = 0;
41+
var expected_num_pages: usize = 0;
42+
43+
var line_iter = std.mem.tokenize(contents, "\n\r");
44+
while (line_iter.next()) |line| {
45+
var pair_iter = std.mem.tokenize(line, " \t");
46+
47+
const kind = pair_iter.next() orelse continue;
48+
49+
if (std.mem.eql(u8, "char", kind)) {
50+
var id: ?u32 = null;
51+
var x: f32 = undefined;
52+
var y: f32 = undefined;
53+
var width: f32 = undefined;
54+
var height: f32 = undefined;
55+
var xoffset: f32 = undefined;
56+
var yoffset: f32 = undefined;
57+
var xadvance: f32 = undefined;
58+
var page: u32 = undefined;
59+
60+
while (pair_iter.next()) |pair| {
61+
var kv_iter = std.mem.split(pair, "=");
62+
const key = kv_iter.next().?;
63+
const value = kv_iter.rest();
64+
65+
if (std.mem.eql(u8, "id", key)) {
66+
id = try std.fmt.parseInt(u32, value, 10);
67+
} else if (std.mem.eql(u8, "x", key)) {
68+
x = try std.fmt.parseFloat(f32, value);
69+
} else if (std.mem.eql(u8, "y", key)) {
70+
y = try std.fmt.parseFloat(f32, value);
71+
} else if (std.mem.eql(u8, "width", key)) {
72+
width = try std.fmt.parseFloat(f32, value);
73+
} else if (std.mem.eql(u8, "height", key)) {
74+
height = try std.fmt.parseFloat(f32, value);
75+
} else if (std.mem.eql(u8, "xoffset", key)) {
76+
xoffset = try std.fmt.parseFloat(f32, value);
77+
} else if (std.mem.eql(u8, "yoffset", key)) {
78+
yoffset = try std.fmt.parseFloat(f32, value);
79+
} else if (std.mem.eql(u8, "xadvance", key)) {
80+
xadvance = try std.fmt.parseFloat(f32, value);
81+
} else if (std.mem.eql(u8, "page", key)) {
82+
page = try std.fmt.parseInt(u32, value, 10);
83+
} else if (std.mem.eql(u8, "chnl", key)) {
84+
// TODO
85+
} else {
86+
std.log.warn("unknown pair for {} kind: {}", .{ kind, pair });
87+
}
88+
}
89+
90+
if (id == null) {
91+
return error.InvalidFormat;
92+
}
93+
94+
try glyphs.put(id.?, .{
95+
.page = page,
96+
.pos = vec2f(x, y),
97+
.size = vec2f(width, height),
98+
.offset = vec2f(xoffset, yoffset),
99+
.xadvance = xadvance,
100+
});
101+
} else if (std.mem.eql(u8, "common", kind)) {
102+
while (pair_iter.next()) |pair| {
103+
var kv_iter = std.mem.split(pair, "=");
104+
const key = kv_iter.next().?;
105+
const value = kv_iter.rest();
106+
107+
if (std.mem.eql(u8, "lineHeight", key)) {
108+
lineHeight = try std.fmt.parseFloat(f32, value);
109+
} else if (std.mem.eql(u8, "base", key)) {
110+
base = try std.fmt.parseFloat(f32, value);
111+
} else if (std.mem.eql(u8, "scaleW", key)) {
112+
scaleW = try std.fmt.parseFloat(f32, value);
113+
} else if (std.mem.eql(u8, "scaleH", key)) {
114+
scaleH = try std.fmt.parseFloat(f32, value);
115+
} else if (std.mem.eql(u8, "packed", key)) {
116+
// TODO
117+
} else if (std.mem.eql(u8, "pages", key)) {
118+
expected_num_pages = try std.fmt.parseInt(usize, value, 10);
119+
} else {
120+
std.log.warn("unknown pair for {} kind: {}", .{ kind, pair });
121+
}
122+
}
123+
} else if (std.mem.eql(u8, "page", kind)) {
124+
var id: u32 = @intCast(u32, pages.items.len);
125+
var page_filename = try allocator.alloc(u8, 0);
126+
defer allocator.free(page_filename);
127+
128+
while (pair_iter.next()) |pair| {
129+
var kv_iter = std.mem.split(pair, "=");
130+
const key = kv_iter.next().?;
131+
const value = kv_iter.rest();
132+
133+
if (std.mem.eql(u8, "id", key)) {
134+
id = try std.fmt.parseInt(u32, value, 10);
135+
} else if (std.mem.eql(u8, "file", key)) {
136+
const trimmed = std.mem.trim(u8, value, "\"");
137+
page_filename = try std.fs.path.join(allocator, &[_][]const u8{ base_path, trimmed });
138+
} else {
139+
std.log.warn("unknown pair for {} kind: {}", .{ kind, pair });
140+
}
141+
}
142+
143+
try pages.resize(id + 1);
144+
pages.items[id] = try Texture.initFromFile(allocator, page_filename);
145+
}
146+
}
147+
148+
if (pages.items.len != expected_num_pages) {
149+
std.log.warn("Font pages expected {} != font pages found {}", .{ expected_num_pages, pages.items.len });
150+
}
151+
152+
return @This(){
153+
.pages = pages.toOwnedSlice(),
154+
.glyphs = glyphs,
155+
.lineHeight = lineHeight,
156+
.base = base,
157+
.scale = vec2f(scaleW, scaleH),
158+
};
159+
}
160+
161+
pub fn deinit(this: *@This()) void {
162+
this.glyphs.allocator.free(this.pages);
163+
this.glyphs.deinit();
164+
}
165+
166+
const TextAlign = enum { Left, Center, Right };
167+
const TextBaseline = enum { Bottom, Middle, Top };
168+
169+
const DrawOptions = struct {
170+
textAlign: TextAlign = .Left,
171+
textBaseline: TextBaseline = .Bottom,
172+
// color: math.Color = math.Color.white,
173+
scale: f32 = 1,
174+
};
175+
176+
pub fn drawText(this: @This(), drawbatcher: *FlatRenderer, text: []const u8, pos: Vec2f, options: DrawOptions) !void {
177+
var x = switch (options.textAlign) {
178+
.Left, .Right => pos.x,
179+
.Center => calc_text_width: {
180+
var total_width: f32 = 0;
181+
for (text) |char| {
182+
if (this.glyphs.get(char)) |glyph| {
183+
const xadvance = (glyph.xadvance * options.scale);
184+
total_width += xadvance;
185+
}
186+
}
187+
break :calc_text_width pos.x - (total_width / 2);
188+
},
189+
};
190+
var y = switch (options.textBaseline) {
191+
.Bottom => pos.y - std.math.floor(this.lineHeight * options.scale),
192+
.Middle => pos.y - std.math.floor(this.lineHeight * options.scale / 2),
193+
.Top => pos.y,
194+
};
195+
const direction: f32 = switch (options.textAlign) {
196+
.Left, .Center => 1,
197+
.Right => -1,
198+
};
199+
200+
var i: usize = 0;
201+
while (i < text.len) : (i += 1) {
202+
const char = switch (options.textAlign) {
203+
.Left, .Center => text[i],
204+
.Right => text[text.len - 1 - i],
205+
};
206+
if (this.glyphs.get(char)) |glyph| {
207+
const xadvance = (glyph.xadvance * options.scale);
208+
const offset = glyph.offset.scale(options.scale);
209+
const texture = this.pages[glyph.page];
210+
// const quad = math.Quad.init(glyph.pos.x, glyph.pos.y, glyph.size.x, glyph.size.y, this.scale.x, this.scale.y);
211+
const textureSize = texture.size.intToFloat(f32);
212+
213+
const textAlignOffset = switch (options.textAlign) {
214+
.Left, .Center => 0,
215+
.Right => -xadvance,
216+
};
217+
218+
const renderPos = vec2f(
219+
x + offset.x + textAlignOffset,
220+
y + offset.y,
221+
);
222+
223+
const glyphPos = glyph.pos.divv(textureSize);
224+
225+
try drawbatcher.drawTextureRect(texture, glyphPos, glyph.pos.addv(glyph.size).divv(textureSize), renderPos, glyph.size.scale(options.scale));
226+
227+
x += direction * xadvance;
228+
}
229+
}
230+
}
231+
};

src/main.zig

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const Map = @import("./map.zig").Map;
1313
const tile = @import("./tile.zig");
1414
const generate = @import("./generate.zig");
1515
const Mat4f = math.Mat4(f32);
16+
const Font = @import("./font_render.zig").BitmapFontRenderer;
1617

1718
// Setup environment
1819
pub const panic = platform.panic;
@@ -50,6 +51,7 @@ const allocator = &gpa.allocator;
5051

5152
var tilesetTex: Texture = undefined;
5253
var flatRenderer: FlatRenderer = undefined;
54+
var font: Font = undefined;
5355
var map: Map = undefined;
5456
var playerPos = vec2i(10, 10);
5557
var playerMove = vec2i(0, 0);
@@ -58,13 +60,16 @@ pub fn onInit() !void {
5860
std.log.info("app init", .{});
5961

6062
var load_tileset = async Texture.initFromFile(allocator, "colored_packed.png");
63+
var load_font = async Font.initFromFile(allocator, "PressStart2P_8.fnt");
6164

6265
// == Load tileset
6366
tilesetTex = try await load_tileset;
6467

6568
// == Initialize renderer
6669
flatRenderer = try FlatRenderer.init(allocator, platform.getScreenSize().intToFloat(f32));
6770

71+
font = try await load_font;
72+
6873
// Create map
6974
map = try generate.generateMap(allocator, .{
7075
.size = vec2i(50, 50),
@@ -81,6 +86,7 @@ pub fn onInit() !void {
8186
fn onDeinit() void {
8287
std.log.info("app deinit", .{});
8388
map.deinit();
89+
font.deinit();
8490
flatRenderer.deinit();
8591
_ = gpa.deinit();
8692
}
@@ -136,10 +142,13 @@ pub fn render(alpha: f64) !void {
136142
const cam_pos = playerPos.scale(16).intToFloat(f32).subv(cam_size.scaleDiv(2));
137143

138144
flatRenderer.perspective = Mat4f.orthographic(cam_pos.x, cam_pos.x + cam_size.x, cam_pos.y + cam_size.y, cam_pos.y, -1, 1);
139-
140145
map.render(&flatRenderer);
141146
render_tile(&flatRenderer, .{ .pos = 25 }, playerPos);
142147
flatRenderer.flush();
148+
149+
flatRenderer.perspective = Mat4f.orthographic(0, cam_size.x, cam_size.y, 0, -1, 1);
150+
try font.drawText(&flatRenderer, "Hello!", vec2f(10, 10), .{});
151+
flatRenderer.flush();
143152
}
144153

145154
pub fn render_tile(fr: *FlatRenderer, tid: tile.TID, pos: Vec2i) void {

0 commit comments

Comments
 (0)