1
0
Fork 0

feat: update to new text rendering system

This commit is contained in:
LordMZTE 2023-12-20 14:59:20 +01:00
parent 3cbcfc0154
commit b8a928f7e9
Signed by: LordMZTE
GPG key ID: B64802DC33A64FF6
5 changed files with 110 additions and 155 deletions

View file

@ -13,8 +13,8 @@
// .hash = "1220685f1c039a53076ef169ef2e2959aeb054c86d4eda5aac736885daa0bf2f6910",
//},
.zenolith = .{
.url = "https://git.mzte.de/zenolith/zenolith/archive/4705b425154b40a1718d2102ab6ad4eecf5135d9.tar.gz",
.hash = "122039dbae23348c8d9320af685c4d43420b8f362d53898a1d57f7ec0ced04ab8711",
.url = "https://git.mzte.de/zenolith/zenolith/archive/8908dbcc0d0f54bd942a2fd1cf973b5ef2ef9ffc.tar.gz",
.hash = "122099a1bd57dee5e0a2b454ab435755b9b206fe0de4f7949edc2ae972af2663a1f4",
},
},
}

View file

@ -15,7 +15,7 @@ pub fn main() !void {
});
defer platform.deinit();
var font = zenolith.font.Font.create(try platform.createFont(.{
var font = zenolith.text.Font.create(try platform.createFont(.{
.source = .{ .path = "/usr/share/fonts/noto/NotoSans-Regular.ttf" },
}), {});
defer font.deinit();
@ -45,30 +45,68 @@ pub fn main() !void {
},
},
.padding = 10,
.font_size = 32,
.text_color = zenolith.Color.fromInt(0xcdd6f4ff),
.font_style = .{},
};
root.data.attreebutes = attrs;
}
root.downcast(zenolith.widget.Box).?.orth_expand = true;
try root.downcast(zenolith.widget.Box).?.addChildPositioned(root, null, try zenolith.widget.Label.init(.{
.alloc = alloc,
.font = &font,
.text = "Hello, Zenolith!",
.size = 64,
}), .center);
try root.downcast(zenolith.widget.Box).?.addChildPositioned(
root,
null,
try zenolith.widget.Label.init(alloc, .{
.font = &font,
.text = "Hello, Zenolith!",
.style = .{ .size = 64 },
}),
.center,
);
try root.addChild(null, try zenolith.widget.Label.init(.{
.alloc = alloc,
try root.addChild(null, try zenolith.widget.Label.init(alloc, .{
.font = &font,
.text = "Labels!",
.size = 32,
.style = .{ .size = 32 },
}));
try root.addChild(null, try zenolith.widget.Button.init(alloc, "Click Me!"));
{
var chunk = zenolith.text.Chunk.init(alloc);
errdefer chunk.deinit();
try chunk.spans.append(.{ .span = try zenolith.text.Span.init(alloc, .{
.font = &font,
.text = "Span 1",
.style = .{ .size = 32 },
}) });
try chunk.spans.append(.{ .span = try zenolith.text.Span.init(alloc, .{
.font = &font,
.text = "Span 2",
.style = .{ .color = zenolith.Color.fromInt(0xff0000ff) },
}) });
try chunk.spans.append(.{ .span = try zenolith.text.Span.init(alloc, .{
.font = &font,
.text = "Span 3",
}), .wrap_mode = .always });
try chunk.spans.append(.{ .span = try zenolith.text.Span.init(alloc, .{
.font = &font,
.text = "abcdefghijklmnopqrstuvwxyz",
.style = .{ .size = 48 },
}), .wrap_mode = .always });
for ("abcdefghijklmnopqrstuvwxyz") |ch| {
try chunk.spans.append(.{ .span = try zenolith.text.Span.init(alloc, .{
.font = &font,
.text = &.{ch},
.style = .{ .size = 48 },
}) });
}
try root.addChild(null, try zenolith.widget.ChunkView.init(alloc, chunk));
}
try root.treevent(zenolith.treevent.Link{
.parent = null,
.platform = &zplatform,

View file

@ -12,61 +12,36 @@ atlas: *c.SDL_Texture,
renderer: *c.SDL_Renderer,
/// This is an ArrayHashMap to speed up iteration which is requried for collision checking.
glyphs: std.AutoArrayHashMap(GlyphProperties, Glyph),
glyphs: std.AutoArrayHashMap(GlyphProperties, AtlasGlyph),
/// A buffer for creating glyph pixel data in for SDL2 textures.
pixel_buf: std.ArrayList(u8),
const Sdl2Font = @This();
pub const Chunk = struct {
font: *Sdl2Font,
glyphs: std.ArrayList(PositionedGlyph),
// Saving the size here to improve performance by not recalculating this.
// This is an immutable data structure.
size: zenolith.layout.Size,
pub fn deinit(self: Chunk) void {
self.glyphs.deinit();
}
pub fn getSize(self: Chunk) zenolith.layout.Size {
return self.size;
}
};
const PositionedGlyph = struct {
glyph: Glyph,
pos: zenolith.layout.Position,
};
pub const Glyph = struct {
/// Rectangle in atlas-local coordinates of this glyph.
pub const AtlasGlyph = struct {
glyph: zenolith.text.Glyph,
sprite: zenolith.layout.Rectangle,
/// Offset the glyph is rendered at.
bearing: [2]i32,
advance: usize,
};
pub const GlyphProperties = struct {
codepoint: u21,
size: u32,
size: usize,
};
pub fn deinit(self_: Sdl2Font) void {
var self = self_;
pub fn deinit(self: *Sdl2Font) void {
c.SDL_DestroyTexture(self.atlas);
_ = c.FT_Done_Face(self.face);
self.glyphs.deinit();
self.pixel_buf.deinit();
}
pub fn getGlyph(self: *Sdl2Font, props: GlyphProperties) !Glyph {
if (self.glyphs.get(props)) |g| return g;
pub fn getGlyph(self: *Sdl2Font, codepoint: u21, style: zenolith.text.Style) !zenolith.text.Glyph {
const props = GlyphProperties{ .codepoint = codepoint, .size = style.size };
if (self.glyphs.get(props)) |g| return g.glyph;
try ffi.handleFTError(c.FT_Set_Pixel_Sizes(self.face, 0, props.size));
try ffi.handleFTError(c.FT_Load_Char(self.face, props.codepoint, c.FT_LOAD_RENDER));
try ffi.handleFTError(c.FT_Set_Pixel_Sizes(self.face, 0, @intCast(style.size)));
try ffi.handleFTError(c.FT_Load_Char(self.face, codepoint, c.FT_LOAD_RENDER));
const bmp = self.face.*.glyph.*.bitmap;
@ -75,20 +50,29 @@ pub fn getGlyph(self: *Sdl2Font, props: GlyphProperties) !Glyph {
.size = .{ .width = 0, .height = 0 },
} else try self.addAtlastSprite(bmp.buffer[0 .. bmp.rows * bmp.width], bmp.width);
const glyph = Glyph{
.sprite = rect,
const glyph = zenolith.text.Glyph{
.codepoint = codepoint,
.size = rect.size,
.bearing = .{
self.face.*.glyph.*.bitmap_left,
self.face.*.glyph.*.bitmap_top,
.x = self.face.*.glyph.*.bitmap_left,
.y = -self.face.*.glyph.*.bitmap_top,
},
// I see no point in supporting negative glyph advance.
.advance = @intCast(@max(0, self.face.*.glyph.*.advance.x) >> 6),
};
try self.glyphs.put(props, glyph);
try self.glyphs.put(props, .{ .glyph = glyph, .sprite = rect });
return glyph;
}
pub fn yOffset(self: *Sdl2Font, size: usize) usize {
if (c.FT_Set_Pixel_Sizes(self.face, 0, @intCast(size)) != 0)
// TODO: wonk
@panic("Unable to FT_Set_Pixel_Sizes for determining y offset");
return @intCast(self.face.*.size.*.metrics.height >> 6);
}
fn getSize(self: *Sdl2Font) zenolith.layout.Size {
var w: c_int = 0;
var h: c_int = 0;
@ -110,8 +94,8 @@ pub fn addAtlastSprite(self: *Sdl2Font, data: []const u8, width: usize) !zenolit
var collision = zenolith.layout.Rectangle{
// start in bottom right
.pos = .{
.x = size.width - width,
.y = size.height,
.x = @intCast(size.width - width),
.y = @intCast(size.height),
},
// size of glyph + padding
@ -146,7 +130,7 @@ pub fn addAtlastSprite(self: *Sdl2Font, data: []const u8, width: usize) !zenolit
},
};
if (collision.pos.y + collision.size.height - padding > size.height)
if (@as(usize, @intCast(collision.pos.y)) + collision.size.height - padding > size.height)
return error.AtlastTooSmall;
try self.pixel_buf.resize(data.len * 4);
@ -174,93 +158,16 @@ pub fn addAtlastSprite(self: *Sdl2Font, data: []const u8, width: usize) !zenolit
return rect;
}
pub fn layout(
self: *Sdl2Font,
text: []const u8,
size: usize,
wrap: zenolith.font.TextWrap,
) !zenolith.font.Chunk {
_ = wrap;
var glyphs = std.ArrayList(PositionedGlyph).init(self.pixel_buf.allocator);
errdefer glyphs.deinit();
var cur_pos = zenolith.layout.Position{
// We start in the middle of the possible range to leave as much space in all directions
// as possible. This is all aligned to 0/0 in the last layout step.
.x = std.math.maxInt(usize) / 2,
.y = std.math.maxInt(usize) / 2,
};
// This is the position of the top-left-most glyph. This is later subtracted from all positions.
var min_pos = zenolith.layout.Position{
.x = std.math.maxInt(usize),
.y = std.math.maxInt(usize),
};
// The height of the current line of text.
var cur_line_height: usize = 0;
var iter = std.unicode.Utf8Iterator{ .i = 0, .bytes = text };
while (iter.nextCodepoint()) |codepoint| {
// TODO: correctly calculate size of next line first
if (codepoint == '\n') {
cur_pos.x = std.math.maxInt(usize) / 2;
cur_pos.y += @intCast(self.face.*.size.*.metrics.height >> 6);
cur_line_height = 0;
continue;
}
const glyph = try self.getGlyph(.{
.size = @intCast(size),
.codepoint = codepoint,
});
const gpos = zenolith.layout.Position{
// hacky addition with negative numbers
.x = cur_pos.x +% @as(usize, @bitCast(@as(isize, glyph.bearing[0]))),
.y = cur_pos.y -% @as(usize, @bitCast(@as(isize, glyph.bearing[1]))),
};
try glyphs.append(.{
.glyph = glyph,
.pos = gpos,
});
cur_pos.x += glyph.advance;
if (glyph.sprite.size.height > cur_line_height) cur_line_height = glyph.sprite.size.height;
if (gpos.x < min_pos.x) min_pos.x = gpos.x;
if (gpos.y < min_pos.y) min_pos.y = gpos.y;
}
var csize = zenolith.layout.Size.zero;
// Position glyphs at top left and calculate chunk size.
for (glyphs.items) |*g| {
const new_pos = g.pos.sub(min_pos);
g.pos = new_pos;
const xmax = g.pos.x + g.glyph.sprite.size.width;
const ymax = g.pos.y + g.glyph.sprite.size.height;
if (xmax > csize.width) csize.width = xmax;
if (ymax > csize.height) csize.height = ymax;
}
return zenolith.font.Chunk.create(Chunk{
.font = self,
.glyphs = glyphs,
.size = csize,
}, {});
pub fn getSprite(self: *Sdl2Font, codepoint: u21, size: usize) ?zenolith.layout.Rectangle {
return (self.glyphs.get(.{ .codepoint = codepoint, .size = size }) orelse return null).sprite;
}
fn isTouchingAnyOnTop(self: *Sdl2Font, rect: zenolith.layout.Rectangle) bool {
if (rect.pos.y == 0) return true;
for (self.glyphs.unmanaged.entries.items(.value)) |other| {
if (other.sprite.pos.y + other.sprite.size.height == rect.pos.y and
other.sprite.pos.x < rect.pos.x + rect.size.width and
other.sprite.pos.x + other.sprite.size.width > rect.pos.x) return true;
if (other.sprite.pos.y + @as(isize, @intCast(other.sprite.size.height)) == rect.pos.y and
other.sprite.pos.x < rect.pos.x + @as(isize, @intCast(rect.size.width)) and
other.sprite.pos.x + @as(isize, @intCast(other.sprite.size.width)) > rect.pos.x) return true;
}
return false;
}
@ -268,9 +175,9 @@ fn isTouchingAnyOnTop(self: *Sdl2Font, rect: zenolith.layout.Rectangle) bool {
fn isTouchingAnyOnLeft(self: *Sdl2Font, rect: zenolith.layout.Rectangle) bool {
if (rect.pos.x == 0) return true;
for (self.glyphs.unmanaged.entries.items(.value)) |other| {
if (other.sprite.pos.x + other.sprite.size.width == rect.pos.x and
other.sprite.pos.y < rect.pos.y + rect.size.height and
other.sprite.pos.y + other.sprite.size.height > rect.pos.y) return true;
if (other.sprite.pos.x + @as(isize, @intCast(other.sprite.size.width)) == rect.pos.x and
other.sprite.pos.y < rect.pos.y + @as(isize, @intCast(rect.size.height)) and
other.sprite.pos.y + @as(isize, @intCast(other.sprite.size.height)) > rect.pos.y) return true;
}
return false;
}

View file

@ -41,23 +41,33 @@ pub fn texturedRect(
) != 0) return error.Render;
}
pub fn text(
pub fn span(
self: *Sdl2Painter,
pos: zenolith.layout.Position,
zchunk: zenolith.font.Chunk,
color: zenolith.Color,
zspan: zenolith.text.Span,
) !void {
const chunk = zchunk.downcast(Sdl2Font.Chunk) orelse unreachable;
if (c.SDL_SetTextureColorMod(chunk.font.atlas, color.r, color.g, color.b) != 0) return error.Render;
if (c.SDL_SetTextureAlphaMod(chunk.font.atlas, color.a) != 0) return error.Render;
for (chunk.glyphs.items) |g| {
const font = zspan.font.downcast(Sdl2Font) orelse unreachable;
if (c.SDL_SetTextureColorMod(
font.atlas,
zspan.style.color.r,
zspan.style.color.g,
zspan.style.color.b,
) != 0) return error.Render;
if (c.SDL_SetTextureAlphaMod(font.atlas, zspan.style.color.a) != 0) return error.Render;
for (zspan.glyphs.items) |g| {
if (c.SDL_RenderCopy(
self.renderer,
chunk.font.atlas,
&util.toSdlRect(g.glyph.sprite),
font.atlas,
&util.toSdlRect(
// This is sound as the span will have already gotten the glyph from the font,
// causing it to add it to the map.
font.getSprite(g.glyph.codepoint, zspan.style.size) orelse unreachable,
),
&util.toSdlRect(.{
.pos = pos.add(g.pos),
.size = g.glyph.sprite.size,
.pos = pos.add(g.position),
.size = g.glyph.size,
}),
) != 0) return error.Render;
}

View file

@ -62,7 +62,7 @@ pub fn init(options: InitOptions) InitializeError!Sdl2Platform {
window_pos[1],
options.window_size[0],
options.window_size[1],
c.SDL_WINDOW_SHOWN, // TODO: add API to change this
c.SDL_WINDOW_SHOWN | c.SDL_WINDOW_RESIZABLE, // TODO: add API to change this
) orelse return error.CreateWindow;
errdefer c.SDL_DestroyWindow(window);
@ -294,7 +294,7 @@ pub fn createFont(self: Sdl2Platform, opts: CreateFontOptions) CreateFontError!S
.face = face,
.atlas = atlas,
.renderer = self.renderer,
.glyphs = std.AutoArrayHashMap(Sdl2Font.GlyphProperties, Sdl2Font.Glyph).init(self.alloc),
.glyphs = std.AutoArrayHashMap(Sdl2Font.GlyphProperties, Sdl2Font.AtlasGlyph).init(self.alloc),
.pixel_buf = std.ArrayList(u8).init(self.alloc),
};
}