From 7f5de6c15243bd8f3f9952ed091e4dd15e809020 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yaroslav=20de=20la=20Pe=C3=B1a=20Smirnov?= Date: Thu, 12 Sep 2024 22:51:07 +0300 Subject: pango and cairo with shm Use pango and cairo to render text to the shared mem buffer. --- src/main.zig | 51 ++++++++++++++++++- src/pangocairo.zig | 142 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 src/pangocairo.zig (limited to 'src') diff --git a/src/main.zig b/src/main.zig index 4902176..436e14d 100644 --- a/src/main.zig +++ b/src/main.zig @@ -6,6 +6,10 @@ const wayland = @import("wayland"); const wl = wayland.client.wl; const xdg = wayland.client.xdg; +const pangocairo = @import("pangocairo.zig"); +const cairo = pangocairo.cairo; +const pango = pangocairo.pango; + /// Global Wayland registry context; contains pointers to the globals we expect /// from the compositor. const RegistryContext = struct { @@ -22,6 +26,8 @@ const WindowBuffer = struct { wl_buffer: *wl.Buffer, /// Backing data slice from memory mapping for this buffer map: []u8, + /// Cairo surface for painting with cairo + c_surface: cairo.Surface, /// Are we being read by the compositor right now? busy: bool = false, }; @@ -41,6 +47,8 @@ pub const Window = struct { buffers: [2]WindowBuffer = undefined, /// Memory mapped for both buffers buffers_map: []align(mem.page_size) u8 = undefined, + /// Pango font description for rendering our text + font: pango.FontDescription, /// This window's title title: [*:0]const u8, /// Width in pixels @@ -51,6 +59,8 @@ pub const Window = struct { offset: f32 = 0.0, /// Last frame's timestamp in ms last_frame: u32 = 0, + /// Frames per second statistic + current_fps: f32 = 0.0, /// Should we keep this window open? open: bool = true, @@ -64,10 +74,16 @@ pub const Window = struct { .allocator = allocator, .wl_surface = try compositor.createSurface(), .title = title, + // .font = try pango.FontDescription.fromString("monospace 24"), + .font = try pango.FontDescription.create(), }; win.xdg_surface = try wm_base.getXdgSurface(win.wl_surface); win.xdg_toplevel = try win.xdg_surface.getToplevel(); + win.font.setFamily("monospace"); + win.font.setWeight(600); + win.font.setSizePt(18); + try win.allocBuffers(); win.xdg_surface.setListener(*Window, xdgSurfaceListener, win); @@ -86,6 +102,7 @@ pub const Window = struct { /// Destroy this window and all its associated resources pub fn destroy(win: *Window) void { win.destroyBuffers(); + win.font.destroy(); win.xdg_toplevel.destroy(); win.xdg_surface.destroy(); win.wl_surface.destroy(); @@ -94,7 +111,8 @@ pub const Window = struct { /// Allocate new buffers for this window fn allocBuffers(win: *Window) !void { - const stride = win.width * 4; + const c_format = cairo.Format.argb32; + const stride = cairo.Surface.formatStrideForWidth(c_format, win.width); const buffer_size = stride * win.height; const total_size = buffer_size * 2; // we need two buffers @@ -124,12 +142,21 @@ pub const Window = struct { const bsize: usize = @intCast(buffer_size); buffer.busy = false; buffer.map = win.buffers_map[bsize *% i .. bsize *% (i + 1)]; + buffer.c_surface = try cairo.Surface.createImageForData( + buffer.map, + c_format, + win.width, + win.height, + stride, + ); buffer.wl_buffer.setListener(*WindowBuffer, bufferListener, buffer); } } /// Destroy this window's buffers fn destroyBuffers(win: *Window) void { + win.buffers[0].c_surface.destroy(); + win.buffers[1].c_surface.destroy(); win.buffers[0].wl_buffer.destroy(); win.buffers[1].wl_buffer.destroy(); posix.munmap(win.buffers_map); @@ -163,6 +190,25 @@ pub const Window = struct { } } + const layout = try pango.Layout.createForCairo(&buffer.c_surface); + defer layout.unref(); + buffer.c_surface.setSourceRgba(1, 1, 1, 1); + + layout.setFont(win.font); + layout.setText("Hello, wayland!", -1); + buffer.c_surface.moveTo(5, 5); + layout.show(&buffer.c_surface); + + var fps_buf: [32]u8 = undefined; + const fps_output = std.fmt.bufPrintZ( + &fps_buf, + "{d} FPS", + .{win.current_fps}, + ) catch unreachable; + layout.setText(fps_output, -1); + buffer.c_surface.moveTo(5, 40); + layout.show(&buffer.c_surface); + buffer.busy = true; return buffer.wl_buffer; } @@ -230,8 +276,9 @@ pub const Window = struct { if (win.last_frame != 0) { const elapsed: f32 = @floatFromInt(done.callback_data -% win.last_frame); win.offset -= elapsed / 1000.0 * 32.0; + win.current_fps = 1000.0 / elapsed; } - defer win.last_frame = done.callback_data; + win.last_frame = done.callback_data; const wl_buffer = win.drawFrame() catch |err| { std.debug.print("error drawing frame {}\n", .{err}); diff --git a/src/pangocairo.zig b/src/pangocairo.zig new file mode 100644 index 0000000..3995ba7 --- /dev/null +++ b/src/pangocairo.zig @@ -0,0 +1,142 @@ +const c = @cImport({ + @cInclude("pango/pangocairo.h"); +}); + +pub const pango = struct { + pub const Layout = struct { + layout: *c.PangoLayout, + + pub fn createForCairo(s: *cairo.Surface) !Layout { + const layout = c.pango_cairo_create_layout( + s.cairo, + ) orelse return error.PangoCairoError; + return .{ .layout = layout }; + } + + pub fn setFont(l: Layout, f: FontDescription) void { + c.pango_layout_set_font_description(l.layout, f.font); + } + + pub fn setFontFromString(l: Layout, font: [*:0]const u8) !void { + const pfont = c.pango_font_description_from_string( + font, + ) orelse return error.BadFont; + defer c.pango_font_description_free(pfont); + c.pango_layout_set_font_description(l.layout, pfont); + } + + pub fn setText(l: Layout, text: [*:0]const u8, len: i32) void { + c.pango_layout_set_text(l.layout, text, len); + } + + pub fn show(l: Layout, s: *cairo.Surface) void { + c.pango_cairo_show_layout(s.cairo, l.layout); + } + + pub fn unref(l: Layout) void { + c.g_object_unref(l.layout); + } + }; + + pub const FontDescription = struct { + font: *c.PangoFontDescription, + + pub fn create() !FontDescription { + const font = c.pango_font_description_new() orelse return error.PangoError; + return .{ .font = font }; + } + + pub fn fromString(str: [*:0]const u8) !FontDescription { + const font = c.pango_font_description_from_string(str) orelse return error.PangoError; + return .{ .font= font }; + } + + pub fn setFamily(f: FontDescription, name: [*:0]const u8) void { + c.pango_font_description_set_family(f.font, name); + } + + pub fn setWeight(f: FontDescription, weight: u32) void { + c.pango_font_description_set_weight(f.font, weight); + } + + pub fn setSize(f: FontDescription, size: i32) void { + c.pango_font_description_set_size(f.font, size); + } + + pub fn setSizePt(f: FontDescription, pt: i32) void { + c.pango_font_description_set_size(f.font, pt * c.PANGO_SCALE); + } + + pub fn destroy(f: FontDescription) void { + c.pango_font_description_free(f.font); + } + }; +}; + +pub const cairo = struct { + pub const Format = enum(c_int) { + invalid = -1, + argb32, + rgb24, + }; + + pub const Surface = struct { + cairo: *c.cairo_t, + surface: *c.cairo_surface_t, + + pub fn formatStrideForWidth(format: Format, width: i32) i32 { + return c.cairo_format_stride_for_width(@intFromEnum(format), width); + } + + pub fn createImage(format: Format, width: i32, height: i32) !Surface { + const surface = c.cairo_image_surface_create( + @intFromEnum(format), + width, + height, + ) orelse return error.CairoError; + const cr = c.cairo_create(surface) orelse return error.CairoError; + return .{ .cairo = cr, .surface = surface }; + } + + pub fn createImageForData( + data: []u8, + format: Format, + width: i32, + height: i32, + stride: i32, + ) !Surface { + const surface = c.cairo_image_surface_create_for_data( + data.ptr, + @intFromEnum(format), + width, + height, + stride, + ) orelse return error.CairoError; + const cr = c.cairo_create(surface) orelse return error.CairoError; + return .{ .cairo = cr, .surface = surface }; + } + + pub fn moveTo(s: Surface, x: f64, y: f64) void { + c.cairo_move_to(s.cairo, x, y); + } + + pub fn setSourceRgba( + s: Surface, + red: f64, + green: f64, + blue: f64, + alpha: f64, + ) void { + c.cairo_set_source_rgba(s.cairo, red, green, blue, alpha); + } + + pub fn paint(s: Surface) void { + c.cairo_paint(s.cairo); + } + + pub fn destroy(s: Surface) void { + c.cairo_destroy(s.cairo); + c.cairo_surface_destroy(s.surface); + } + }; +}; -- cgit v1.2.3