move font handling to separate file
This commit is contained in:
parent
4df4f42022
commit
9f3c41f991
@ -1,8 +1,28 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Font = @import("./engine/font.zig");
|
||||||
|
const Gfx = @import("./engine/graphics.zig");
|
||||||
|
|
||||||
const Assets = @This();
|
const Assets = @This();
|
||||||
|
|
||||||
|
pub const FontVariant = enum {
|
||||||
|
regular,
|
||||||
|
italic,
|
||||||
|
bold,
|
||||||
|
|
||||||
|
const Map = std.EnumArray(FontVariant, Font.Id);
|
||||||
|
};
|
||||||
|
|
||||||
|
font_map: FontVariant.Map = .initFill(.invalid),
|
||||||
|
|
||||||
pub fn init() !Assets {
|
pub fn init() !Assets {
|
||||||
|
const font_map: FontVariant.Map = .init(.{
|
||||||
|
.regular = try Gfx.fonts.add("regular", @embedFile("./assets/roboto-font/Roboto-Regular.ttf")),
|
||||||
|
.italic = try Gfx.fonts.add("italic", @embedFile("./assets/roboto-font/Roboto-Italic.ttf")),
|
||||||
|
.bold = try Gfx.fonts.add("bold", @embedFile("./assets/roboto-font/Roboto-Bold.ttf")),
|
||||||
|
});
|
||||||
|
|
||||||
return Assets{
|
return Assets{
|
||||||
|
.font_map = font_map
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
614
src/engine/font.zig
Normal file
614
src/engine/font.zig
Normal file
@ -0,0 +1,614 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const sokol = @import("sokol");
|
||||||
|
const sapp = sokol.app;
|
||||||
|
|
||||||
|
const Math = @import("./math.zig");
|
||||||
|
const Vec2 = Math.Vec2;
|
||||||
|
const Rect = Math.Rect;
|
||||||
|
const Vec4 = Math.Vec4;
|
||||||
|
|
||||||
|
const Gfx = @import("./graphics.zig");
|
||||||
|
|
||||||
|
const c = @cImport({
|
||||||
|
@cInclude("sokol/sokol_gfx.h");
|
||||||
|
@cInclude("fontstash.h");
|
||||||
|
@cInclude("sokol_fontstash.h");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Struct definition copies from fonstash.h
|
||||||
|
// TODO: Create a getter, to avoid copying this struct by hand
|
||||||
|
const FONSstate = extern struct {
|
||||||
|
font: c_int,
|
||||||
|
@"align": c_int,
|
||||||
|
size: f32,
|
||||||
|
color: c_uint,
|
||||||
|
blur: f32,
|
||||||
|
spacing: f32
|
||||||
|
};
|
||||||
|
|
||||||
|
const FONSfont = opaque{};
|
||||||
|
const FONSglyph = opaque{};
|
||||||
|
|
||||||
|
extern fn zig_isTopLeft(stash: ?*c.FONScontext) bool;
|
||||||
|
extern fn zig_getGlyphIndex(glyph: ?*FONSglyph) c_int;
|
||||||
|
extern fn zig_fons__getState(stash: ?*c.struct_FONScontext) ?*FONSstate;
|
||||||
|
extern fn zig_getFont(stash: ?*c.FONScontext, index: c_int) ?*FONSfont;
|
||||||
|
extern fn zig_fons__tt_getPixelHeightScale(font: ?*FONSfont, size: f32) f32;
|
||||||
|
extern fn zig_fons__getVertAlign(stash: ?*c.FONScontext, font: ?*FONSfont, @"align": c_int, isize: i16) f32;
|
||||||
|
extern fn zig_fons__getGlyph(
|
||||||
|
stash: ?*c.FONScontext,
|
||||||
|
font: ?*FONSfont,
|
||||||
|
codepoint: c_uint,
|
||||||
|
@"isize": i16,
|
||||||
|
iblur: i16
|
||||||
|
) ?*FONSglyph;
|
||||||
|
|
||||||
|
extern fn zig_fons__getQuad(
|
||||||
|
stash: ?*c.FONScontext,
|
||||||
|
font: ?*FONSfont,
|
||||||
|
prevGlyphIndex: c_int, glyph: ?*FONSglyph,
|
||||||
|
scale: f32,
|
||||||
|
spacing: f32,
|
||||||
|
x: ?*f32, y: ?*f32,
|
||||||
|
q: ?*c.FONSquad
|
||||||
|
) void;
|
||||||
|
|
||||||
|
const Font = @This();
|
||||||
|
|
||||||
|
pub const Id = enum(c_int) {
|
||||||
|
_,
|
||||||
|
|
||||||
|
pub const invalid: Id = @enumFromInt(c.FONS_INVALID);
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const AlignX = enum {
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
center,
|
||||||
|
|
||||||
|
fn toFONSAlignment(self: AlignX) c_int {
|
||||||
|
return switch (self) {
|
||||||
|
.left => c.FONS_ALIGN_LEFT,
|
||||||
|
.center => c.FONS_ALIGN_CENTER,
|
||||||
|
.right => c.FONS_ALIGN_RIGHT,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const AlignY = enum {
|
||||||
|
top,
|
||||||
|
middle,
|
||||||
|
bottom,
|
||||||
|
baseline,
|
||||||
|
|
||||||
|
fn toFONSAlignment(self: AlignY) c_int {
|
||||||
|
return switch (self) {
|
||||||
|
.top => c.FONS_ALIGN_TOP,
|
||||||
|
.middle => c.FONS_ALIGN_MIDDLE,
|
||||||
|
.bottom => c.FONS_ALIGN_BOTTOM,
|
||||||
|
.baseline => c.FONS_ALIGN_BASELINE,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const QuadIterator = struct {
|
||||||
|
ctx: Context,
|
||||||
|
|
||||||
|
x: f32,
|
||||||
|
y: f32,
|
||||||
|
next_x: f32,
|
||||||
|
next_y: f32,
|
||||||
|
spacing: f32,
|
||||||
|
scale: f32,
|
||||||
|
isize: c_short,
|
||||||
|
iblur: c_short,
|
||||||
|
prev_glyph_index: c_int,
|
||||||
|
font: *FONSfont,
|
||||||
|
|
||||||
|
pub fn init(ctx: Context, font: Font) QuadIterator {
|
||||||
|
const stash = ctx.fons_context;
|
||||||
|
ctx.setAsCurrent(font);
|
||||||
|
|
||||||
|
const x: f32 = 0;
|
||||||
|
var y: f32 = 0;
|
||||||
|
|
||||||
|
const state = zig_fons__getState(stash).?;
|
||||||
|
const fons_font = zig_getFont(stash, state.font) orelse @panic("Invalid font");
|
||||||
|
|
||||||
|
const font_isize: c_short = @intFromFloat(@trunc(state.size * 10.0));
|
||||||
|
const font_iblur: c_short = @intFromFloat(@trunc(state.blur));
|
||||||
|
const scale = zig_fons__tt_getPixelHeightScale(fons_font, @as(f32, @floatFromInt(font_isize)) / 10.0);
|
||||||
|
|
||||||
|
assert(state.@"align" & c.FONS_ALIGN_LEFT != 0);
|
||||||
|
assert(state.@"align" & c.FONS_ALIGN_RIGHT == 0);
|
||||||
|
assert(state.@"align" & c.FONS_ALIGN_CENTER == 0);
|
||||||
|
|
||||||
|
// Align vertically.
|
||||||
|
y += zig_fons__getVertAlign(stash, fons_font, @truncate(state.@"align"), font_isize);
|
||||||
|
|
||||||
|
return QuadIterator{
|
||||||
|
.ctx = ctx,
|
||||||
|
.x = x,
|
||||||
|
.y = y,
|
||||||
|
.next_x = x,
|
||||||
|
.next_y = y,
|
||||||
|
.isize = font_isize,
|
||||||
|
.iblur = font_iblur,
|
||||||
|
.scale = scale,
|
||||||
|
.spacing = state.spacing,
|
||||||
|
.font = fons_font,
|
||||||
|
.prev_glyph_index = -1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(self: *QuadIterator, codepoint: u21) ?Rect {
|
||||||
|
const stash = self.ctx.fons_context;
|
||||||
|
|
||||||
|
self.x = self.next_x;
|
||||||
|
self.y = self.next_y;
|
||||||
|
|
||||||
|
const glyph = zig_fons__getGlyph(stash, self.font, codepoint, self.isize, self.iblur);
|
||||||
|
if (glyph != null) {
|
||||||
|
var q: c.FONSquad = .{};
|
||||||
|
zig_fons__getQuad(stash, self.font, self.prev_glyph_index, glyph, self.scale, self.spacing, &self.x, &self.y, &q);
|
||||||
|
self.prev_glyph_index = zig_getGlyphIndex(glyph);
|
||||||
|
return Rect.init(q.x0, q.y0, q.x1 - q.x0, q.y1 - q.y0);
|
||||||
|
} else {
|
||||||
|
self.prev_glyph_index = -1;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Ascii = struct {
|
||||||
|
iter: QuadIterator,
|
||||||
|
text: []const u8,
|
||||||
|
index: usize,
|
||||||
|
|
||||||
|
pub fn init(ctx: Context, font: Font, text: []const u8) Ascii {
|
||||||
|
return Ascii{
|
||||||
|
.iter = .init(ctx, font),
|
||||||
|
.text = text,
|
||||||
|
.index = 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(self: *Ascii) ?Rect {
|
||||||
|
while (self.index < self.text.len) {
|
||||||
|
const codepoint_len = std.unicode.utf8ByteSequenceLength(self.text[self.index]) catch {
|
||||||
|
self.index += 1;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (self.index + codepoint_len > self.text.len) {
|
||||||
|
self.index = self.text.len;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const codepoint = std.unicode.utf8Decode(self.text[self.index..][0..codepoint_len]) catch {
|
||||||
|
self.index += 1;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
self.index += codepoint_len;
|
||||||
|
|
||||||
|
if (self.iter.next(codepoint)) |rect| {
|
||||||
|
return rect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn last(self: *Ascii) ?Rect {
|
||||||
|
var last_rect: ?Rect = null;
|
||||||
|
while (self.next()) |rect| {
|
||||||
|
last_rect = rect;
|
||||||
|
}
|
||||||
|
return last_rect;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Unicode = struct {
|
||||||
|
iter: QuadIterator,
|
||||||
|
text: []const u21,
|
||||||
|
index: usize,
|
||||||
|
|
||||||
|
pub fn init(ctx: Context, font: Font, text: []const u21) Unicode {
|
||||||
|
return Unicode{
|
||||||
|
.iter = .init(ctx, font),
|
||||||
|
.text = text,
|
||||||
|
.index = 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(self: *Unicode) ?Rect {
|
||||||
|
while (self.index < self.text.len) {
|
||||||
|
const next_rect = self.iter.next(self.text[self.index]);
|
||||||
|
self.index += 1;
|
||||||
|
|
||||||
|
if (next_rect) |rect| {
|
||||||
|
return rect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn last(self: *Unicode) ?Rect {
|
||||||
|
var last_rect: ?Rect = null;
|
||||||
|
while (self.next()) |rect| {
|
||||||
|
last_rect = rect;
|
||||||
|
}
|
||||||
|
return last_rect;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Context = struct {
|
||||||
|
fons_context: *c.struct_FONScontext,
|
||||||
|
|
||||||
|
pub fn init(atlas_size: f32) !Context {
|
||||||
|
const dpi_scale = sapp.dpiScale();
|
||||||
|
|
||||||
|
const atlas_dim = std.math.ceilPowerOfTwoAssert(u32, @intFromFloat(atlas_size * dpi_scale));
|
||||||
|
const fons_context = c.sfons_create(&c.sfons_desc_t{
|
||||||
|
.width = @intCast(atlas_dim),
|
||||||
|
.height = @intCast(atlas_dim),
|
||||||
|
});
|
||||||
|
if (fons_context == null) {
|
||||||
|
return error.sfons_create;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Context{
|
||||||
|
.fons_context = fons_context.?
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: Context) void {
|
||||||
|
c.sfons_destroy(self.fons_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(self: Context, name: [*c]const u8, data: []const u8) !Id {
|
||||||
|
const font_id = c.fonsAddFontMem(
|
||||||
|
self.fons_context,
|
||||||
|
name,
|
||||||
|
@constCast(data.ptr),
|
||||||
|
@intCast(data.len),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
if (font_id == c.FONS_INVALID) {
|
||||||
|
return error.fonsAddFontMem;
|
||||||
|
}
|
||||||
|
return @enumFromInt(font_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clearState(self: Context) void {
|
||||||
|
c.fonsClearState(self.fons_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn flush(self: Context) void {
|
||||||
|
c.sfons_flush(self.fons_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setAsCurrent(self: Context, font: Font) void {
|
||||||
|
const fs = self.fons_context;
|
||||||
|
const dpi_scale = sapp.dpiScale();
|
||||||
|
|
||||||
|
c.fonsSetFont(fs, @intFromEnum(font.id));
|
||||||
|
c.fonsSetSize(fs, font.size * dpi_scale);
|
||||||
|
c.fonsSetAlign(fs, Font.AlignX.left.toFONSAlignment() | Font.AlignY.top.toFONSAlignment());
|
||||||
|
c.fonsSetSpacing(fs, font.spacing);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn drawText(self: Context, font: Font, pos: Vec2, color: Vec4, text: []const u8) void {
|
||||||
|
const fs = self.fons_context;
|
||||||
|
|
||||||
|
// TODO: Test if calling `.setAsCurrent()` isn't expensive
|
||||||
|
self.setAsCurrent(font);
|
||||||
|
|
||||||
|
const fons_color = c.sfons_rgba(
|
||||||
|
@intFromFloat(color.x * 255),
|
||||||
|
@intFromFloat(color.y * 255),
|
||||||
|
@intFromFloat(color.z * 255),
|
||||||
|
@intFromFloat(color.w * 255)
|
||||||
|
);
|
||||||
|
c.fonsSetColor(fs, fons_color);
|
||||||
|
|
||||||
|
Gfx.pushTransform(.init(0, 0), 1.0/64.0);
|
||||||
|
defer Gfx.popTransform();
|
||||||
|
|
||||||
|
_ = c.fonsDrawText(
|
||||||
|
fs,
|
||||||
|
pos.x*64,
|
||||||
|
pos.y*64,
|
||||||
|
text.ptr,
|
||||||
|
text.ptr + text.len
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getBounds(self: Context, font: Font, text: []const u8) Rect {
|
||||||
|
const fs = self.fons_context;
|
||||||
|
self.setAsCurrent(font);
|
||||||
|
|
||||||
|
var line_bounds: [4]f32 = undefined;
|
||||||
|
_ = c.fonsTextBounds(fs, 0, 0, text.ptr, text.ptr + text.len, &line_bounds);
|
||||||
|
|
||||||
|
const min_x = line_bounds[0];
|
||||||
|
const min_y = line_bounds[1];
|
||||||
|
const max_x = line_bounds[2];
|
||||||
|
const max_y = line_bounds[3];
|
||||||
|
|
||||||
|
const width = max_x - min_x;
|
||||||
|
const height = max_y - min_y;
|
||||||
|
|
||||||
|
return Rect.init(min_x, min_y, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getBoundsUtf8(self: Context, font: Font, text: []const u21) Rect {
|
||||||
|
const fs = self.fons_context;
|
||||||
|
self.setAsCurrent(font);
|
||||||
|
|
||||||
|
var line_bounds: [4]f32 = undefined;
|
||||||
|
_ = fonsTextBoundsUtf8(fs, 0, 0, text, &line_bounds);
|
||||||
|
|
||||||
|
const min_x = line_bounds[0];
|
||||||
|
const min_y = line_bounds[1];
|
||||||
|
const max_x = line_bounds[2];
|
||||||
|
const max_y = line_bounds[3];
|
||||||
|
|
||||||
|
const width = max_x - min_x;
|
||||||
|
const height = max_y - min_y;
|
||||||
|
|
||||||
|
return Rect.init(min_x, min_y, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn measure(self: Context, font: Font, text: []const u8) Vec2 {
|
||||||
|
const bounds = self.getBounds(font, text);
|
||||||
|
return bounds.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn quadIter(self: Context, font: Font, text: []const u8) QuadIterator.Ascii {
|
||||||
|
return QuadIterator.Ascii.init(self, font, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn quadUnicodeIter(self: Context, font: Font, text: []const u21) QuadIterator.Unicode {
|
||||||
|
return QuadIterator.Unicode.init(self, font, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A reimplementation of `fonsTextBounds` which uses an array of already decoded codepoints
|
||||||
|
fn fonsTextBoundsUtf8(stash: ?*c.struct_FONScontext, initial_x: f32, initial_y: f32, str: []const u21, bounds: [*c]f32) f32 {
|
||||||
|
if (stash == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var x = initial_x;
|
||||||
|
var y = initial_y;
|
||||||
|
|
||||||
|
const state = zig_fons__getState(stash).?;
|
||||||
|
const font = zig_getFont(stash, state.font) orelse return 0;
|
||||||
|
|
||||||
|
const font_isize: i16 = @intFromFloat(@trunc(state.size * 10.0));
|
||||||
|
const font_iblur: i16 = @intFromFloat(@trunc(state.blur));
|
||||||
|
|
||||||
|
const scale = zig_fons__tt_getPixelHeightScale(font, @as(f32, @floatFromInt(font_isize)) / 10.0);
|
||||||
|
|
||||||
|
// Align vertically.
|
||||||
|
y += zig_fons__getVertAlign(stash, font, @truncate(state.@"align"), font_isize);
|
||||||
|
|
||||||
|
var minx = x;
|
||||||
|
var maxx = x;
|
||||||
|
var miny = y;
|
||||||
|
var maxy = y;
|
||||||
|
const startx = x;
|
||||||
|
|
||||||
|
var prevGlyphIndex: c_int = -1;
|
||||||
|
for (str) |codepoint| {
|
||||||
|
const glyph = zig_fons__getGlyph(stash, font, codepoint, font_isize, font_iblur);
|
||||||
|
if (glyph != null) {
|
||||||
|
var q: c.FONSquad = .{};
|
||||||
|
zig_fons__getQuad(stash, font, prevGlyphIndex, glyph, scale, state.spacing, &x, &y, &q);
|
||||||
|
if (q.x0 < minx) minx = q.x0;
|
||||||
|
if (q.x1 > maxx) maxx = q.x1;
|
||||||
|
if (zig_isTopLeft(stash)) {
|
||||||
|
if (q.y0 < miny) miny = q.y0;
|
||||||
|
if (q.y1 > maxy) maxy = q.y1;
|
||||||
|
} else {
|
||||||
|
if (q.y1 < miny) miny = q.y1;
|
||||||
|
if (q.y0 > maxy) maxy = q.y0;
|
||||||
|
}
|
||||||
|
|
||||||
|
prevGlyphIndex = zig_getGlyphIndex(glyph);
|
||||||
|
} else {
|
||||||
|
prevGlyphIndex = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const advance = x - startx;
|
||||||
|
|
||||||
|
// Align horizontally
|
||||||
|
if ((state.@"align" & c.FONS_ALIGN_LEFT) != 0) {
|
||||||
|
// empty
|
||||||
|
} else if ((state.@"align" & c.FONS_ALIGN_RIGHT) != 0) {
|
||||||
|
minx -= advance;
|
||||||
|
maxx -= advance;
|
||||||
|
} else if ((state.@"align" & c.FONS_ALIGN_CENTER) != 0) {
|
||||||
|
minx -= advance * 0.5;
|
||||||
|
maxx -= advance * 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bounds != null) {
|
||||||
|
bounds[0] = minx;
|
||||||
|
bounds[1] = miny;
|
||||||
|
bounds[2] = maxx;
|
||||||
|
bounds[3] = maxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
return advance;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const DrawTextContext = struct {
|
||||||
|
pub const Line = struct {
|
||||||
|
// Assumes that the text won't be invalidted between `queue*` and `draw` functions
|
||||||
|
text: []const u8,
|
||||||
|
pos: Vec2,
|
||||||
|
bounds: Rect,
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Add support for multiple font in a single context.
|
||||||
|
// I.e. each command should be able to have a separate font
|
||||||
|
ctx: Context,
|
||||||
|
font: Font,
|
||||||
|
|
||||||
|
line_height: f32 = 0,
|
||||||
|
pos: Vec2 = Vec2.zero,
|
||||||
|
lines: std.ArrayListUnmanaged(Line) = .empty,
|
||||||
|
bounds: Rect = Rect.zero,
|
||||||
|
|
||||||
|
pub fn init(ctx: Context, font: Font) DrawTextContext {
|
||||||
|
var self = DrawTextContext{
|
||||||
|
.ctx = ctx,
|
||||||
|
.font = font
|
||||||
|
};
|
||||||
|
|
||||||
|
const fs = ctx.fons_context;
|
||||||
|
self.ctx.setAsCurrent(font);
|
||||||
|
c.fonsVertMetrics(fs, null, null, &self.line_height);
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *DrawTextContext, allocator: Allocator) void {
|
||||||
|
self.lines.deinit(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn queueText(self: *DrawTextContext, allocator: Allocator, text: []const u8) !void {
|
||||||
|
var line_iter = std.mem.splitScalar(u8, text, '\n');
|
||||||
|
while (line_iter.next()) |line| {
|
||||||
|
try self.queueLine(allocator, line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn measureText(self: *const DrawTextContext, text: []const u8) Rect {
|
||||||
|
const fs = self.ctx.fons_context;
|
||||||
|
self.ctx.setAsCurrent(self.font);
|
||||||
|
|
||||||
|
var min_x: f32 = 0;
|
||||||
|
var max_x: f32 = 0;
|
||||||
|
var min_y: f32 = 0;
|
||||||
|
var max_y: f32 = 0;
|
||||||
|
|
||||||
|
var pos = Vec2.zero;
|
||||||
|
var line_iter = std.mem.splitScalar(u8, text, '\n');
|
||||||
|
while (line_iter.next()) |line| {
|
||||||
|
var line_bounds: [4]f32 = undefined;
|
||||||
|
_ = c.fonsTextBounds(fs, pos.x, pos.y, line.ptr, line.ptr + line.len, &line_bounds);
|
||||||
|
min_x = @min(min_x, line_bounds[0]);
|
||||||
|
min_y = @min(min_y, line_bounds[1]);
|
||||||
|
max_x = @max(max_x, line_bounds[2]);
|
||||||
|
max_y = @max(max_y, line_bounds[3]);
|
||||||
|
|
||||||
|
pos.y += self.line_height * self.font.line_spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Rect.init(min_x, min_y, max_x - min_x, max_y - min_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn queueLine(self: *DrawTextContext, allocator: Allocator, line: []const u8) !void {
|
||||||
|
const fs = self.ctx.fons_context;
|
||||||
|
self.ctx.setAsCurrent(self.font);
|
||||||
|
|
||||||
|
var line_bounds: [4]f32 = undefined;
|
||||||
|
_ = c.fonsTextBounds(fs, self.pos.x, self.pos.y, line.ptr, line.ptr + line.len, &line_bounds);
|
||||||
|
|
||||||
|
try self.lines.append(allocator, .{
|
||||||
|
.bounds = Rect.init(
|
||||||
|
line_bounds[0],
|
||||||
|
line_bounds[1],
|
||||||
|
line_bounds[2] - line_bounds[0],
|
||||||
|
line_bounds[3] - line_bounds[1],
|
||||||
|
),
|
||||||
|
.pos = self.pos,
|
||||||
|
.text = line
|
||||||
|
});
|
||||||
|
self.pos.y += self.line_height * self.font.line_spacing;
|
||||||
|
|
||||||
|
var min_x = self.bounds.pos.x;
|
||||||
|
var max_x = self.bounds.pos.x + self.bounds.size.x;
|
||||||
|
var min_y = self.bounds.pos.y;
|
||||||
|
var max_y = self.bounds.pos.y + self.bounds.size.y;
|
||||||
|
|
||||||
|
min_x = @min(min_x, line_bounds[0]);
|
||||||
|
min_y = @min(min_y, line_bounds[1]);
|
||||||
|
max_x = @max(max_x, line_bounds[2]);
|
||||||
|
max_y = @max(max_y, line_bounds[3]);
|
||||||
|
|
||||||
|
self.bounds = Rect.init(min_x, min_y, max_x - min_x, max_y - min_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(self: *DrawTextContext, pos: Vec2, color: Vec4) void {
|
||||||
|
for (self.lines.items) |cmd| {
|
||||||
|
self.ctx.drawText(self.font, pos.add(cmd.pos), color, cmd.text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isEmpty(self: *DrawTextContext) bool {
|
||||||
|
return self.lines.items.len == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub fn drawLine(self: *DrawTextContext, line: []const u8) void {
|
||||||
|
// const fs = fons_context;
|
||||||
|
//
|
||||||
|
// _ = c.fonsDrawText(fs, self.pos.x, self.pos.y, line.ptr, line.ptr + line.len);
|
||||||
|
// self.pos.y += self.line_height * self.font.line_spacing;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// pub fn drawLines(self: *DrawTextContext, lines: []const []const u8) void {
|
||||||
|
// for (lines) |line| {
|
||||||
|
// self.drawLine(line);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// pub fn drawText(self: *DrawTextContext, text: []const u8) void {
|
||||||
|
// var line_iter = std.mem.splitScalar(u8, text, '\n');
|
||||||
|
// while (line_iter.next()) |line| {
|
||||||
|
// self.drawLine(line);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// pub fn measureLine(self: *DrawTextContext, line: []const u8) void {
|
||||||
|
// const fs = fons_context;
|
||||||
|
//
|
||||||
|
// var min_x: f32 = 0;
|
||||||
|
// var min_y: f32 = 0;
|
||||||
|
// var max_x: f32 = 0;
|
||||||
|
// var max_y: f32 = 0;
|
||||||
|
//
|
||||||
|
// var line_iter = std.mem.splitScalar(u8, text, '\n');
|
||||||
|
// var y: f32 = 0;
|
||||||
|
// while (line_iter.next()) |line| {
|
||||||
|
// var line_bounds: [4]f32 = undefined;
|
||||||
|
//
|
||||||
|
// _ = c.fonsTextBounds(fs, 0, y, line.ptr, line.ptr + line.len, &line_bounds);
|
||||||
|
// y += line_height * font.line_spacing;
|
||||||
|
//
|
||||||
|
// min_x = @min(min_x, line_bounds[0]);
|
||||||
|
// min_y = @min(min_y, line_bounds[1]);
|
||||||
|
// max_x = @max(max_x, line_bounds[2]);
|
||||||
|
// max_y = @max(max_y, line_bounds[3]);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return Rect.init(min_x, min_y, max_x - min_x, max_y - min_y);
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub fn end(self: *DrawTextContext) void {
|
||||||
|
_ = self;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
id: Id,
|
||||||
|
size: f32,
|
||||||
|
spacing: f32 = 0,
|
||||||
|
line_spacing: f32 = 1,
|
||||||
@ -38,47 +38,11 @@ const hex = Math.rgb_hex;
|
|||||||
const rgba = Math.rgba;
|
const rgba = Math.rgba;
|
||||||
const log = std.log.scoped(.graphics);
|
const log = std.log.scoped(.graphics);
|
||||||
|
|
||||||
|
pub const Font = @import("./font.zig");
|
||||||
|
|
||||||
pub const white = rgb(255, 255, 255);
|
pub const white = rgb(255, 255, 255);
|
||||||
pub const black = rgb(0, 0, 0);
|
pub const black = rgb(0, 0, 0);
|
||||||
|
|
||||||
// Struct definition copies from fonstash.h
|
|
||||||
// TODO: Create a getter, to avoid copying this struct by hand
|
|
||||||
const FONSstate = extern struct {
|
|
||||||
font: c_int,
|
|
||||||
@"align": c_int,
|
|
||||||
size: f32,
|
|
||||||
color: c_uint,
|
|
||||||
blur: f32,
|
|
||||||
spacing: f32
|
|
||||||
};
|
|
||||||
|
|
||||||
const FONSfont = opaque{};
|
|
||||||
const FONSglyph = opaque{};
|
|
||||||
|
|
||||||
extern fn zig_isTopLeft(stash: ?*c.FONScontext) bool;
|
|
||||||
extern fn zig_getGlyphIndex(glyph: ?*FONSglyph) c_int;
|
|
||||||
extern fn zig_fons__getState(stash: ?*c.struct_FONScontext) ?*FONSstate;
|
|
||||||
extern fn zig_getFont(stash: ?*c.FONScontext, index: c_int) ?*FONSfont;
|
|
||||||
extern fn zig_fons__tt_getPixelHeightScale(font: ?*FONSfont, size: f32) f32;
|
|
||||||
extern fn zig_fons__getVertAlign(stash: ?*c.FONScontext, font: ?*FONSfont, @"align": c_int, isize: i16) f32;
|
|
||||||
extern fn zig_fons__getGlyph(
|
|
||||||
stash: ?*c.FONScontext,
|
|
||||||
font: ?*FONSfont,
|
|
||||||
codepoint: c_uint,
|
|
||||||
@"isize": i16,
|
|
||||||
iblur: i16
|
|
||||||
) ?*FONSglyph;
|
|
||||||
|
|
||||||
extern fn zig_fons__getQuad(
|
|
||||||
stash: ?*c.FONScontext,
|
|
||||||
font: ?*FONSfont,
|
|
||||||
prevGlyphIndex: c_int, glyph: ?*FONSglyph,
|
|
||||||
scale: f32,
|
|
||||||
spacing: f32,
|
|
||||||
x: ?*f32, y: ?*f32,
|
|
||||||
q: ?*c.FONSquad
|
|
||||||
) void;
|
|
||||||
|
|
||||||
const Vertex = extern struct {
|
const Vertex = extern struct {
|
||||||
position: Vec2,
|
position: Vec2,
|
||||||
color: Vec4,
|
color: Vec4,
|
||||||
@ -100,364 +64,6 @@ const DrawFrame = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const FontVariant = enum {
|
|
||||||
regular,
|
|
||||||
italic,
|
|
||||||
bold
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Font = struct {
|
|
||||||
pub const AlignX = enum {
|
|
||||||
left,
|
|
||||||
right,
|
|
||||||
center,
|
|
||||||
|
|
||||||
fn toFONSAlignment(self: AlignX) c_int {
|
|
||||||
return switch (self) {
|
|
||||||
.left => c.FONS_ALIGN_LEFT,
|
|
||||||
.center => c.FONS_ALIGN_CENTER,
|
|
||||||
.right => c.FONS_ALIGN_RIGHT,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const AlignY = enum {
|
|
||||||
top,
|
|
||||||
middle,
|
|
||||||
bottom,
|
|
||||||
baseline,
|
|
||||||
|
|
||||||
fn toFONSAlignment(self: AlignY) c_int {
|
|
||||||
return switch (self) {
|
|
||||||
.top => c.FONS_ALIGN_TOP,
|
|
||||||
.middle => c.FONS_ALIGN_MIDDLE,
|
|
||||||
.bottom => c.FONS_ALIGN_BOTTOM,
|
|
||||||
.baseline => c.FONS_ALIGN_BASELINE,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const QuadIterator = struct {
|
|
||||||
x: f32,
|
|
||||||
y: f32,
|
|
||||||
next_x: f32,
|
|
||||||
next_y: f32,
|
|
||||||
spacing: f32,
|
|
||||||
scale: f32,
|
|
||||||
isize: c_short,
|
|
||||||
iblur: c_short,
|
|
||||||
prev_glyph_index: c_int,
|
|
||||||
font: *FONSfont,
|
|
||||||
|
|
||||||
pub fn init(font: Font) QuadIterator {
|
|
||||||
const stash = g_fons_context;
|
|
||||||
font.setAsCurrent();
|
|
||||||
|
|
||||||
const x: f32 = 0;
|
|
||||||
var y: f32 = 0;
|
|
||||||
|
|
||||||
const state = zig_fons__getState(stash).?;
|
|
||||||
const fons_font = zig_getFont(stash, state.font) orelse @panic("Invalid font");
|
|
||||||
|
|
||||||
const font_isize: c_short = @intFromFloat(@trunc(state.size * 10.0));
|
|
||||||
const font_iblur: c_short = @intFromFloat(@trunc(state.blur));
|
|
||||||
const scale = zig_fons__tt_getPixelHeightScale(fons_font, @as(f32, @floatFromInt(font_isize)) / 10.0);
|
|
||||||
|
|
||||||
assert(state.@"align" & c.FONS_ALIGN_LEFT != 0);
|
|
||||||
assert(state.@"align" & c.FONS_ALIGN_RIGHT == 0);
|
|
||||||
assert(state.@"align" & c.FONS_ALIGN_CENTER == 0);
|
|
||||||
|
|
||||||
// Align vertically.
|
|
||||||
y += zig_fons__getVertAlign(stash, fons_font, @truncate(state.@"align"), font_isize);
|
|
||||||
|
|
||||||
return QuadIterator{
|
|
||||||
.x = x,
|
|
||||||
.y = y,
|
|
||||||
.next_x = x,
|
|
||||||
.next_y = y,
|
|
||||||
.isize = font_isize,
|
|
||||||
.iblur = font_iblur,
|
|
||||||
.scale = scale,
|
|
||||||
.spacing = state.spacing,
|
|
||||||
.font = fons_font,
|
|
||||||
.prev_glyph_index = -1
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn next(self: *QuadIterator, codepoint: u21) ?Rect {
|
|
||||||
const stash = g_fons_context;
|
|
||||||
|
|
||||||
self.x = self.next_x;
|
|
||||||
self.y = self.next_y;
|
|
||||||
|
|
||||||
const glyph = zig_fons__getGlyph(stash, self.font, codepoint, self.isize, self.iblur);
|
|
||||||
if (glyph != null) {
|
|
||||||
var q: c.FONSquad = .{};
|
|
||||||
zig_fons__getQuad(stash, self.font, self.prev_glyph_index, glyph, self.scale, self.spacing, &self.x, &self.y, &q);
|
|
||||||
self.prev_glyph_index = zig_getGlyphIndex(glyph);
|
|
||||||
return Rect.init(q.x0, q.y0, q.x1 - q.x0, q.y1 - q.y0);
|
|
||||||
} else {
|
|
||||||
self.prev_glyph_index = -1;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const Ascii = struct {
|
|
||||||
iter: QuadIterator,
|
|
||||||
text: []const u8,
|
|
||||||
index: usize,
|
|
||||||
|
|
||||||
pub fn init(font: Font, text: []const u8) Ascii {
|
|
||||||
return Ascii{
|
|
||||||
.iter = .init(font),
|
|
||||||
.text = text,
|
|
||||||
.index = 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn next(self: *Ascii) ?Rect {
|
|
||||||
while (self.index < self.text.len) {
|
|
||||||
const codepoint_len = std.unicode.utf8ByteSequenceLength(self.text[self.index]) catch {
|
|
||||||
self.index += 1;
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (self.index + codepoint_len > self.text.len) {
|
|
||||||
self.index = self.text.len;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const codepoint = std.unicode.utf8Decode(self.text[self.index..][0..codepoint_len]) catch {
|
|
||||||
self.index += 1;
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
self.index += codepoint_len;
|
|
||||||
|
|
||||||
if (self.iter.next(codepoint)) |rect| {
|
|
||||||
return rect;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn last(self: *Ascii) ?Rect {
|
|
||||||
var last_rect: ?Rect = null;
|
|
||||||
while (self.next()) |rect| {
|
|
||||||
last_rect = rect;
|
|
||||||
}
|
|
||||||
return last_rect;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Unicode = struct {
|
|
||||||
iter: QuadIterator,
|
|
||||||
text: []const u21,
|
|
||||||
index: usize,
|
|
||||||
|
|
||||||
pub fn init(font: Font, text: []const u21) Unicode {
|
|
||||||
return Unicode{
|
|
||||||
.iter = .init(font),
|
|
||||||
.text = text,
|
|
||||||
.index = 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn next(self: *Unicode) ?Rect {
|
|
||||||
while (self.index < self.text.len) {
|
|
||||||
const next_rect = self.iter.next(self.text[self.index]);
|
|
||||||
self.index += 1;
|
|
||||||
|
|
||||||
if (next_rect) |rect| {
|
|
||||||
return rect;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn last(self: *Unicode) ?Rect {
|
|
||||||
var last_rect: ?Rect = null;
|
|
||||||
while (self.next()) |rect| {
|
|
||||||
last_rect = rect;
|
|
||||||
}
|
|
||||||
return last_rect;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: Maybe this should also have a `color: Vec4` field?
|
|
||||||
variant: FontVariant,
|
|
||||||
size: f32,
|
|
||||||
spacing: f32 = 0,
|
|
||||||
line_spacing: f32 = 1,
|
|
||||||
|
|
||||||
pub const default = Font{
|
|
||||||
.variant = .regular,
|
|
||||||
.size = 64,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const bold = Font{
|
|
||||||
.variant = .bold,
|
|
||||||
.size = 64,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn setAsCurrent(self: Font) void {
|
|
||||||
const fs = g_fons_context;
|
|
||||||
|
|
||||||
c.fonsSetFont(fs, font_id_map.get(self.variant));
|
|
||||||
c.fonsSetSize(fs, self.size * dpi_scale);
|
|
||||||
c.fonsSetAlign(fs, Font.AlignX.left.toFONSAlignment() | Font.AlignY.top.toFONSAlignment());
|
|
||||||
c.fonsSetSpacing(fs, self.spacing);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn drawText(self: Font, pos: Vec2, color: Vec4, text: []const u8) void {
|
|
||||||
const fs = g_fons_context;
|
|
||||||
|
|
||||||
// TODO: Test if calling `.setAsCurrent()` isn't expensive
|
|
||||||
self.setAsCurrent();
|
|
||||||
|
|
||||||
const fons_color = c.sfons_rgba(
|
|
||||||
@intFromFloat(color.x * 255),
|
|
||||||
@intFromFloat(color.y * 255),
|
|
||||||
@intFromFloat(color.z * 255),
|
|
||||||
@intFromFloat(color.w * 255)
|
|
||||||
);
|
|
||||||
c.fonsSetColor(fs, fons_color);
|
|
||||||
|
|
||||||
pushTransform(.init(0, 0), 1.0/64.0);
|
|
||||||
defer popTransform();
|
|
||||||
|
|
||||||
_ = c.fonsDrawText(
|
|
||||||
fs,
|
|
||||||
pos.x*64,
|
|
||||||
pos.y*64,
|
|
||||||
text.ptr,
|
|
||||||
text.ptr + text.len
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getBounds(self: Font, text: []const u8) Rect {
|
|
||||||
const fs = g_fons_context;
|
|
||||||
self.setAsCurrent();
|
|
||||||
|
|
||||||
var line_bounds: [4]f32 = undefined;
|
|
||||||
_ = c.fonsTextBounds(fs, 0, 0, text.ptr, text.ptr + text.len, &line_bounds);
|
|
||||||
|
|
||||||
const min_x = line_bounds[0];
|
|
||||||
const min_y = line_bounds[1];
|
|
||||||
const max_x = line_bounds[2];
|
|
||||||
const max_y = line_bounds[3];
|
|
||||||
|
|
||||||
const width = max_x - min_x;
|
|
||||||
const height = max_y - min_y;
|
|
||||||
|
|
||||||
return Rect.init(min_x, min_y, width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
// A reimplementation of `fonsTextBounds` which uses an array of already decoded codepoints
|
|
||||||
fn fonsTextBoundsUtf8(stash: ?*c.struct_FONScontext, initial_x: f32, initial_y: f32, str: []const u21, bounds: [*c]f32) f32 {
|
|
||||||
if (stash == null) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
var x = initial_x;
|
|
||||||
var y = initial_y;
|
|
||||||
|
|
||||||
const state = zig_fons__getState(stash).?;
|
|
||||||
const font = zig_getFont(stash, state.font) orelse return 0;
|
|
||||||
|
|
||||||
const font_isize: i16 = @intFromFloat(@trunc(state.size * 10.0));
|
|
||||||
const font_iblur: i16 = @intFromFloat(@trunc(state.blur));
|
|
||||||
|
|
||||||
const scale = zig_fons__tt_getPixelHeightScale(font, @as(f32, @floatFromInt(font_isize)) / 10.0);
|
|
||||||
|
|
||||||
// Align vertically.
|
|
||||||
y += zig_fons__getVertAlign(stash, font, @truncate(state.@"align"), font_isize);
|
|
||||||
|
|
||||||
var minx = x;
|
|
||||||
var maxx = x;
|
|
||||||
var miny = y;
|
|
||||||
var maxy = y;
|
|
||||||
const startx = x;
|
|
||||||
|
|
||||||
var prevGlyphIndex: c_int = -1;
|
|
||||||
for (str) |codepoint| {
|
|
||||||
const glyph = zig_fons__getGlyph(stash, font, codepoint, font_isize, font_iblur);
|
|
||||||
if (glyph != null) {
|
|
||||||
var q: c.FONSquad = .{};
|
|
||||||
zig_fons__getQuad(stash, font, prevGlyphIndex, glyph, scale, state.spacing, &x, &y, &q);
|
|
||||||
if (q.x0 < minx) minx = q.x0;
|
|
||||||
if (q.x1 > maxx) maxx = q.x1;
|
|
||||||
if (zig_isTopLeft(stash)) {
|
|
||||||
if (q.y0 < miny) miny = q.y0;
|
|
||||||
if (q.y1 > maxy) maxy = q.y1;
|
|
||||||
} else {
|
|
||||||
if (q.y1 < miny) miny = q.y1;
|
|
||||||
if (q.y0 > maxy) maxy = q.y0;
|
|
||||||
}
|
|
||||||
|
|
||||||
prevGlyphIndex = zig_getGlyphIndex(glyph);
|
|
||||||
} else {
|
|
||||||
prevGlyphIndex = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const advance = x - startx;
|
|
||||||
|
|
||||||
// Align horizontally
|
|
||||||
if ((state.@"align" & c.FONS_ALIGN_LEFT) != 0) {
|
|
||||||
// empty
|
|
||||||
} else if ((state.@"align" & c.FONS_ALIGN_RIGHT) != 0) {
|
|
||||||
minx -= advance;
|
|
||||||
maxx -= advance;
|
|
||||||
} else if ((state.@"align" & c.FONS_ALIGN_CENTER) != 0) {
|
|
||||||
minx -= advance * 0.5;
|
|
||||||
maxx -= advance * 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bounds != null) {
|
|
||||||
bounds[0] = minx;
|
|
||||||
bounds[1] = miny;
|
|
||||||
bounds[2] = maxx;
|
|
||||||
bounds[3] = maxy;
|
|
||||||
}
|
|
||||||
|
|
||||||
return advance;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getBoundsUtf8(self: Font, text: []const u21) Rect {
|
|
||||||
const fs = g_fons_context;
|
|
||||||
self.setAsCurrent();
|
|
||||||
|
|
||||||
var line_bounds: [4]f32 = undefined;
|
|
||||||
_ = fonsTextBoundsUtf8(fs, 0, 0, text, &line_bounds);
|
|
||||||
|
|
||||||
const min_x = line_bounds[0];
|
|
||||||
const min_y = line_bounds[1];
|
|
||||||
const max_x = line_bounds[2];
|
|
||||||
const max_y = line_bounds[3];
|
|
||||||
|
|
||||||
const width = max_x - min_x;
|
|
||||||
const height = max_y - min_y;
|
|
||||||
|
|
||||||
return Rect.init(min_x, min_y, width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn measure(self: Font, text: []const u8) Vec2 {
|
|
||||||
const bounds = self.getBounds(text);
|
|
||||||
return bounds.size;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn quadIter(self: Font, text: []const u8) QuadIterator.Ascii {
|
|
||||||
return QuadIterator.Ascii.init(self, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn quadUnicodeIter(self: Font, text: []const u21) QuadIterator.Unicode {
|
|
||||||
return QuadIterator.Unicode.init(self, text);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const ImageId = enum {
|
pub const ImageId = enum {
|
||||||
tilemap
|
tilemap
|
||||||
};
|
};
|
||||||
@ -502,174 +108,12 @@ pub const Corners = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const DrawTextContext = struct {
|
|
||||||
pub const Line = struct {
|
|
||||||
// Assumes that the text won't be invalidted between `queue*` and `draw` functions
|
|
||||||
text: []const u8,
|
|
||||||
pos: Vec2,
|
|
||||||
bounds: Rect,
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: Add support for multiple font in a single context.
|
|
||||||
// I.e. each command should be able to have a separate font
|
|
||||||
font: Font,
|
|
||||||
|
|
||||||
line_height: f32 = 0,
|
|
||||||
pos: Vec2 = Vec2.zero,
|
|
||||||
lines: std.ArrayListUnmanaged(Line) = .empty,
|
|
||||||
bounds: Rect = Rect.zero,
|
|
||||||
|
|
||||||
pub fn init(font: Font) DrawTextContext {
|
|
||||||
var self = DrawTextContext{
|
|
||||||
.font = font
|
|
||||||
};
|
|
||||||
|
|
||||||
const fs = g_fons_context;
|
|
||||||
self.font.setAsCurrent();
|
|
||||||
c.fonsVertMetrics(fs, null, null, &self.line_height);
|
|
||||||
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *DrawTextContext, allocator: Allocator) void {
|
|
||||||
self.lines.deinit(allocator);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn queueText(self: *DrawTextContext, allocator: Allocator, text: []const u8) !void {
|
|
||||||
var line_iter = std.mem.splitScalar(u8, text, '\n');
|
|
||||||
while (line_iter.next()) |line| {
|
|
||||||
try self.queueLine(allocator, line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn measureText(self: *const DrawTextContext, text: []const u8) Rect {
|
|
||||||
const fs = g_fons_context;
|
|
||||||
self.font.setAsCurrent();
|
|
||||||
|
|
||||||
var min_x: f32 = 0;
|
|
||||||
var max_x: f32 = 0;
|
|
||||||
var min_y: f32 = 0;
|
|
||||||
var max_y: f32 = 0;
|
|
||||||
|
|
||||||
var pos = Vec2.zero;
|
|
||||||
var line_iter = std.mem.splitScalar(u8, text, '\n');
|
|
||||||
while (line_iter.next()) |line| {
|
|
||||||
var line_bounds: [4]f32 = undefined;
|
|
||||||
_ = c.fonsTextBounds(fs, pos.x, pos.y, line.ptr, line.ptr + line.len, &line_bounds);
|
|
||||||
min_x = @min(min_x, line_bounds[0]);
|
|
||||||
min_y = @min(min_y, line_bounds[1]);
|
|
||||||
max_x = @max(max_x, line_bounds[2]);
|
|
||||||
max_y = @max(max_y, line_bounds[3]);
|
|
||||||
|
|
||||||
pos.y += self.line_height * self.font.line_spacing;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Rect.init(min_x, min_y, max_x - min_x, max_y - min_y);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn queueLine(self: *DrawTextContext, allocator: Allocator, line: []const u8) !void {
|
|
||||||
const fs = g_fons_context;
|
|
||||||
|
|
||||||
self.font.setAsCurrent();
|
|
||||||
|
|
||||||
var line_bounds: [4]f32 = undefined;
|
|
||||||
_ = c.fonsTextBounds(fs, self.pos.x, self.pos.y, line.ptr, line.ptr + line.len, &line_bounds);
|
|
||||||
|
|
||||||
try self.lines.append(allocator, .{
|
|
||||||
.bounds = Rect.init(
|
|
||||||
line_bounds[0],
|
|
||||||
line_bounds[1],
|
|
||||||
line_bounds[2] - line_bounds[0],
|
|
||||||
line_bounds[3] - line_bounds[1],
|
|
||||||
),
|
|
||||||
.pos = self.pos,
|
|
||||||
.text = line
|
|
||||||
});
|
|
||||||
self.pos.y += self.line_height * self.font.line_spacing;
|
|
||||||
|
|
||||||
var min_x = self.bounds.pos.x;
|
|
||||||
var max_x = self.bounds.pos.x + self.bounds.size.x;
|
|
||||||
var min_y = self.bounds.pos.y;
|
|
||||||
var max_y = self.bounds.pos.y + self.bounds.size.y;
|
|
||||||
|
|
||||||
min_x = @min(min_x, line_bounds[0]);
|
|
||||||
min_y = @min(min_y, line_bounds[1]);
|
|
||||||
max_x = @max(max_x, line_bounds[2]);
|
|
||||||
max_y = @max(max_y, line_bounds[3]);
|
|
||||||
|
|
||||||
self.bounds = Rect.init(min_x, min_y, max_x - min_x, max_y - min_y);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw(self: *DrawTextContext, pos: Vec2, color: Vec4) void {
|
|
||||||
for (self.lines.items) |cmd| {
|
|
||||||
self.font.drawText(pos.add(cmd.pos), color, cmd.text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn isEmpty(self: *DrawTextContext) bool {
|
|
||||||
return self.lines.items.len == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// pub fn drawLine(self: *DrawTextContext, line: []const u8) void {
|
|
||||||
// const fs = fons_context;
|
|
||||||
//
|
|
||||||
// _ = c.fonsDrawText(fs, self.pos.x, self.pos.y, line.ptr, line.ptr + line.len);
|
|
||||||
// self.pos.y += self.line_height * self.font.line_spacing;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// pub fn drawLines(self: *DrawTextContext, lines: []const []const u8) void {
|
|
||||||
// for (lines) |line| {
|
|
||||||
// self.drawLine(line);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// pub fn drawText(self: *DrawTextContext, text: []const u8) void {
|
|
||||||
// var line_iter = std.mem.splitScalar(u8, text, '\n');
|
|
||||||
// while (line_iter.next()) |line| {
|
|
||||||
// self.drawLine(line);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// pub fn measureLine(self: *DrawTextContext, line: []const u8) void {
|
|
||||||
// const fs = fons_context;
|
|
||||||
//
|
|
||||||
// var min_x: f32 = 0;
|
|
||||||
// var min_y: f32 = 0;
|
|
||||||
// var max_x: f32 = 0;
|
|
||||||
// var max_y: f32 = 0;
|
|
||||||
//
|
|
||||||
// var line_iter = std.mem.splitScalar(u8, text, '\n');
|
|
||||||
// var y: f32 = 0;
|
|
||||||
// while (line_iter.next()) |line| {
|
|
||||||
// var line_bounds: [4]f32 = undefined;
|
|
||||||
//
|
|
||||||
// _ = c.fonsTextBounds(fs, 0, y, line.ptr, line.ptr + line.len, &line_bounds);
|
|
||||||
// y += line_height * font.line_spacing;
|
|
||||||
//
|
|
||||||
// min_x = @min(min_x, line_bounds[0]);
|
|
||||||
// min_y = @min(min_y, line_bounds[1]);
|
|
||||||
// max_x = @max(max_x, line_bounds[2]);
|
|
||||||
// max_y = @max(max_y, line_bounds[3]);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return Rect.init(min_x, min_y, max_x - min_x, max_y - min_y);
|
|
||||||
// }
|
|
||||||
|
|
||||||
pub fn end(self: *DrawTextContext) void {
|
|
||||||
_ = self;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// The lower the better. Best quality with 1
|
// The lower the better. Best quality with 1
|
||||||
pub var circle_quality: f32 = 6;
|
pub var circle_quality: f32 = 6;
|
||||||
|
|
||||||
pub var draw_frame: DrawFrame = undefined;
|
pub var draw_frame: DrawFrame = undefined;
|
||||||
var dpi_scale: f32 = 1;
|
|
||||||
var g_fons_context: ?*c.struct_FONScontext = null;
|
|
||||||
var main_pipeline: sgl.Pipeline = .{};
|
var main_pipeline: sgl.Pipeline = .{};
|
||||||
|
|
||||||
var font_id_map: std.EnumArray(FontVariant, c_int) = .initFill(c.FONS_INVALID);
|
|
||||||
|
|
||||||
var image_map: std.EnumArray(ImageId, sg.Image) = .initFill(.{});
|
var image_map: std.EnumArray(ImageId, sg.Image) = .initFill(.{});
|
||||||
var image_view_map: std.EnumArray(ImageId, sg.View) = .initFill(.{});
|
var image_view_map: std.EnumArray(ImageId, sg.View) = .initFill(.{});
|
||||||
|
|
||||||
@ -680,6 +124,8 @@ var tile_coords: std.EnumArray(TileId, Vec2) = .initUndefined();
|
|||||||
const tile_size: Vec2 = .init(8, 8);
|
const tile_size: Vec2 = .init(8, 8);
|
||||||
var tilemap_size: Vec2 = .init(0, 0);
|
var tilemap_size: Vec2 = .init(0, 0);
|
||||||
|
|
||||||
|
pub var fonts: Font.Context = undefined;
|
||||||
|
|
||||||
const Options = struct {
|
const Options = struct {
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
logger: sg.Logger = .{},
|
logger: sg.Logger = .{},
|
||||||
@ -689,15 +135,6 @@ inline fn structCast(T: type, value: anytype) T {
|
|||||||
return @as(*T, @ptrFromInt(@intFromPtr(&value))).*;
|
return @as(*T, @ptrFromInt(@intFromPtr(&value))).*;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn loadEmbededFont(fs: ?*c.FONScontext, name: [*c]const u8, comptime path: []const u8) c_int {
|
|
||||||
const data = @embedFile(path);
|
|
||||||
|
|
||||||
const font_id = c.fonsAddFontMem(fs, name, @constCast(data.ptr), data.len, 0);
|
|
||||||
assert(font_id != c.FONS_INVALID);
|
|
||||||
|
|
||||||
return font_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ImageData = struct {
|
const ImageData = struct {
|
||||||
rgba8_pixels: [*c]u8,
|
rgba8_pixels: [*c]u8,
|
||||||
width: u32,
|
width: u32,
|
||||||
@ -785,8 +222,6 @@ fn makeImageFromMemory(image_datas: []const []const u8) !sg.Image {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(options: Options) !void {
|
pub fn init(options: Options) !void {
|
||||||
dpi_scale = sapp.dpiScale();
|
|
||||||
|
|
||||||
draw_frame.init();
|
draw_frame.init();
|
||||||
|
|
||||||
sg.setup(.{
|
sg.setup(.{
|
||||||
@ -827,19 +262,7 @@ pub fn init(options: Options) !void {
|
|||||||
|
|
||||||
imgui.addFont(@embedFile("../assets/roboto-font/Roboto-Regular.ttf"), 16);
|
imgui.addFont(@embedFile("../assets/roboto-font/Roboto-Regular.ttf"), 16);
|
||||||
|
|
||||||
// make sure the fontstash atlas width/height is pow-2
|
fonts = try Font.Context.init(512);
|
||||||
const atlas_dim = std.math.ceilPowerOfTwoAssert(u32, @intFromFloat(512 * dpi_scale));
|
|
||||||
g_fons_context = c.sfons_create(&c.sfons_desc_t{
|
|
||||||
.width = @intCast(atlas_dim),
|
|
||||||
.height = @intCast(atlas_dim),
|
|
||||||
});
|
|
||||||
assert(g_fons_context != null);
|
|
||||||
|
|
||||||
font_id_map = .init(.{
|
|
||||||
.regular = loadEmbededFont(g_fons_context, "regular", "../assets/roboto-font/Roboto-Regular.ttf"),
|
|
||||||
.italic = loadEmbededFont(g_fons_context, "italic", "../assets/roboto-font/Roboto-Italic.ttf"),
|
|
||||||
.bold = loadEmbededFont(g_fons_context, "bold", "../assets/roboto-font/Roboto-Bold.ttf"),
|
|
||||||
});
|
|
||||||
|
|
||||||
const tilemap = try ImageData.load(@embedFile("../assets/kenney-micro-roguelike/colored_tilemap_packed.png"));
|
const tilemap = try ImageData.load(@embedFile("../assets/kenney-micro-roguelike/colored_tilemap_packed.png"));
|
||||||
defer tilemap.deinit();
|
defer tilemap.deinit();
|
||||||
@ -885,7 +308,7 @@ pub fn init(options: Options) !void {
|
|||||||
|
|
||||||
pub fn deinit() void {
|
pub fn deinit() void {
|
||||||
imgui.shutdown();
|
imgui.shutdown();
|
||||||
c.sfons_destroy(g_fons_context);
|
fonts.deinit();
|
||||||
sgl.shutdown();
|
sgl.shutdown();
|
||||||
sg.shutdown();
|
sg.shutdown();
|
||||||
}
|
}
|
||||||
@ -1022,7 +445,7 @@ pub fn drawLine(from: Vec2, to: Vec2, color: Vec4, width: f32) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn drawText(allocator: Allocator, pos: Vec2, font: Font, color: Vec4, text: []const u8) !void {
|
pub fn drawText(allocator: Allocator, pos: Vec2, font: Font, color: Vec4, text: []const u8) !void {
|
||||||
var ctx = DrawTextContext.init(font);
|
var ctx = Font.DrawTextContext.init(fonts, font);
|
||||||
defer ctx.deinit(allocator);
|
defer ctx.deinit(allocator);
|
||||||
|
|
||||||
try ctx.queueText(allocator, text);
|
try ctx.queueText(allocator, text);
|
||||||
@ -1030,7 +453,7 @@ pub fn drawText(allocator: Allocator, pos: Vec2, font: Font, color: Vec4, text:
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn measureText(font: Font, text: []const u8) Rect {
|
pub fn measureText(font: Font, text: []const u8) Rect {
|
||||||
var ctx = DrawTextContext.init(font);
|
var ctx = Font.DrawTextContext.init(fonts, font);
|
||||||
return ctx.measureText(text);
|
return ctx.measureText(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1089,7 +512,7 @@ pub fn beginFrame() void {
|
|||||||
.dpi_scale = sapp.dpiScale()
|
.dpi_scale = sapp.dpiScale()
|
||||||
});
|
});
|
||||||
|
|
||||||
c.fonsClearState(g_fons_context);
|
fonts.clearState();
|
||||||
sgl.defaults();
|
sgl.defaults();
|
||||||
sgl.matrixModeProjection();
|
sgl.matrixModeProjection();
|
||||||
sgl.ortho(0, draw_frame.screen_size.x, draw_frame.screen_size.y, 0, -1, 1);
|
sgl.ortho(0, draw_frame.screen_size.x, draw_frame.screen_size.y, 0, -1, 1);
|
||||||
@ -1114,7 +537,7 @@ pub fn endFrame() void {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
c.sfons_flush(g_fons_context);
|
fonts.flush();
|
||||||
|
|
||||||
{
|
{
|
||||||
sg.beginPass(.{
|
sg.beginPass(.{
|
||||||
|
|||||||
23
src/game.zig
23
src/game.zig
@ -63,6 +63,7 @@ gpa: Allocator,
|
|||||||
|
|
||||||
canvas_size: Vec2,
|
canvas_size: Vec2,
|
||||||
level: Level,
|
level: Level,
|
||||||
|
assets: *Assets,
|
||||||
|
|
||||||
current_level: u32,
|
current_level: u32,
|
||||||
levels: std.ArrayList(Level),
|
levels: std.ArrayList(Level),
|
||||||
@ -83,13 +84,13 @@ finale_timer: ?Timer.Id = null,
|
|||||||
finale_counter: u32 = 0,
|
finale_counter: u32 = 0,
|
||||||
|
|
||||||
pub fn init(gpa: Allocator, assets: *Assets) !Game {
|
pub fn init(gpa: Allocator, assets: *Assets) !Game {
|
||||||
_ = assets; // autofix
|
|
||||||
var self = Game{
|
var self = Game{
|
||||||
.gpa = gpa,
|
.gpa = gpa,
|
||||||
.canvas_size = (Vec2.init(20, 15)),
|
.canvas_size = (Vec2.init(20, 15)),
|
||||||
.level = .empty,
|
.level = .empty,
|
||||||
.levels = .empty,
|
.levels = .empty,
|
||||||
.current_level = 0,
|
.current_level = 0,
|
||||||
|
.assets = assets
|
||||||
};
|
};
|
||||||
errdefer self.deinit();
|
errdefer self.deinit();
|
||||||
|
|
||||||
@ -484,30 +485,40 @@ pub fn tickFinale(self: *Game) !void {
|
|||||||
text: []const u8,
|
text: []const u8,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const regular_font = Gfx.Font{
|
||||||
|
.id = self.assets.font_map.get(.regular),
|
||||||
|
.size = 64
|
||||||
|
};
|
||||||
|
|
||||||
|
const bold_font = Gfx.Font{
|
||||||
|
.id = self.assets.font_map.get(.bold),
|
||||||
|
.size = 64
|
||||||
|
};
|
||||||
|
|
||||||
const lines = [5]Line{
|
const lines = [5]Line{
|
||||||
.{
|
.{
|
||||||
.pos = Vec2.init(1, 1),
|
.pos = Vec2.init(1, 1),
|
||||||
.font = Gfx.Font.default,
|
.font = regular_font,
|
||||||
.text = "Congratulations scientist"
|
.text = "Congratulations scientist"
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.pos = Vec2.init(1, 2),
|
.pos = Vec2.init(1, 2),
|
||||||
.font = Gfx.Font.default,
|
.font = regular_font,
|
||||||
.text = "You have passed the entrance exam"
|
.text = "You have passed the entrance exam"
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.pos = Vec2.init(1, 3),
|
.pos = Vec2.init(1, 3),
|
||||||
.font = Gfx.Font.default,
|
.font = regular_font,
|
||||||
.text = "Here is your entry code"
|
.text = "Here is your entry code"
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.pos = Vec2.init(1, 5),
|
.pos = Vec2.init(1, 5),
|
||||||
.font = Gfx.Font.bold,
|
.font = bold_font,
|
||||||
.text = key
|
.text = key
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.pos = Vec2.init(1, 7),
|
.pos = Vec2.init(1, 7),
|
||||||
.font = Gfx.Font.default,
|
.font = regular_font,
|
||||||
.text = "I'll meet you at the lab"
|
.text = "I'll meet you at the lab"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user