diff --git a/src/font/sprite/Face.zig b/src/font/sprite/Face.zig index 1e59adebd..31874e643 100644 --- a/src/font/sprite/Face.zig +++ b/src/font/sprite/Face.zig @@ -19,6 +19,7 @@ const Allocator = std.mem.Allocator; const font = @import("../main.zig"); const Sprite = font.sprite.Sprite; const Box = @import("Box.zig"); +const Powerline = @import("Powerline.zig"); const underline = @import("underline.zig"); const log = std.log.scoped(.font_sprite); @@ -85,6 +86,16 @@ pub fn renderGlyph( self.underline_position, self.thickness, ), + + .powerline => powerline: { + const f: Powerline = .{ + .width = width, + .height = self.height, + .thickness = self.thickness, + }; + + break :powerline try f.renderGlyph(alloc, atlas, cp); + }, }; } @@ -92,6 +103,7 @@ pub fn renderGlyph( const Kind = enum { box, underline, + powerline, pub fn init(cp: u32) ?Kind { return switch (cp) { @@ -113,6 +125,7 @@ const Kind = enum { 0x2500...0x257F, // "Box Drawing" block 0x2580...0x259F, // "Block Elements" block 0x2800...0x28FF, // "Braille" block + 0x1FB00...0x1FB3B, // "Symbols for Legacy Computing" block 0x1FB3C...0x1FB40, 0x1FB47...0x1FB4B, @@ -133,6 +146,15 @@ const Kind = enum { 0x1FB9B, => .box, + // Powerline fonts + 0xE0B0, + 0xE0B2, + 0xE0B8, + 0xE0BA, + 0xE0BC, + 0xE0BE, + => .powerline, + else => null, }; } diff --git a/src/font/sprite/Powerline.zig b/src/font/sprite/Powerline.zig new file mode 100644 index 000000000..dcbc21a96 --- /dev/null +++ b/src/font/sprite/Powerline.zig @@ -0,0 +1,196 @@ +//! This file contains functions for drawing certain characters from Powerline +//! Extra (https://github.com/ryanoasis/powerline-extra-symbols). These +//! characters are similarly to box-drawing characters (see Box.zig), so the +//! logic will be mainly the same, just with a much reduced character set. +//! +//! Note that this is not the complete list of Powerline glyphs that may be +//! needed, so this may grow to add other glyphs from the set. +const Powerline = @This(); + +const std = @import("std"); +const Allocator = std.mem.Allocator; + +const font = @import("../main.zig"); + +const log = std.log.scoped(.powerline_font); + +/// The cell width and height because the boxes are fit perfectly +/// into a cell so that they all properly connect with zero spacing. +width: u32, +height: u32, + +/// Base thickness value for glyphs that are not completely solid (backslashes, +/// thin half-circles, etc). If you want to do any DPI scaling, it is expected +/// to be done earlier. +/// +/// TODO: this and Thickness are currently unused but will be when the +/// aforementioned glyphs are added. +thickness: u32, + +/// The thickness of a line. +const Thickness = enum { + super_light, + light, + heavy, + + /// Calculate the real height of a line based on its thickness + /// and a base thickness value. The base thickness value is expected + /// to be in pixels. + fn height(self: Thickness, base: u32) u32 { + return switch (self) { + .super_light => @max(base / 2, 1), + .light => base, + .heavy => base * 2, + }; + } +}; + +pub fn renderGlyph( + self: Powerline, + alloc: Allocator, + atlas: *font.Atlas, + cp: u32, +) !font.Glyph { + // Create the canvas we'll use to draw + var canvas = try font.sprite.Canvas.init(alloc, self.width, self.height); + defer canvas.deinit(alloc); + + // Perform the actual drawing + try self.draw(&canvas, cp); + + // Write the drawing to the atlas + const region = try canvas.writeAtlas(alloc, atlas); + + // Our coordinates start at the BOTTOM for our renderers so we have to + // specify an offset of the full height because we rendered a full size + // cell. + const offset_y = @as(i32, @intCast(self.height)); + + return font.Glyph{ + .width = self.width, + .height = self.height, + .offset_x = 0, + .offset_y = offset_y, + .atlas_x = region.x, + .atlas_y = region.y, + .advance_x = @floatFromInt(self.width), + }; +} + +fn draw(self: Powerline, canvas: *font.sprite.Canvas, cp: u32) !void { + switch (cp) { + // Hard dividers and triangles + 0xE0B0, + 0xE0B2, + 0xE0B8, + 0xE0BA, + 0xE0BC, + 0xE0BE, + => try self.draw_wedge_triangle(canvas, cp), + + else => return error.InvalidCodepoint, + } +} + +fn draw_wedge_triangle(self: Powerline, canvas: *font.sprite.Canvas, cp: u32) !void { + const width = self.width; + const height = self.height; + + var p1_x: u32 = 0; + var p2_x: u32 = 0; + var p3_x: u32 = 0; + var p1_y: u32 = 0; + var p2_y: u32 = 0; + var p3_y: u32 = 0; + + switch (cp) { + 0xE0B0 => { + p1_x = 0; + p1_y = 0; + p2_x = width; + p2_y = height / 2; + p3_x = 0; + p3_y = height; + }, + + 0xE0B2 => { + p1_x = width; + p1_y = 0; + p2_x = 0; + p2_y = height / 2; + p3_x = width; + p3_y = height; + }, + + 0xE0B8 => { + p1_x = 0; + p1_y = 0; + p2_x = width; + p2_y = height; + p3_x = 0; + p3_y = height; + }, + + 0xE0BA => { + p1_x = width; + p1_y = 0; + p2_x = width; + p2_y = height; + p3_x = 0; + p3_y = height; + }, + + 0xE0BC => { + p1_x = 0; + p1_y = 0; + p2_x = width; + p2_y = 0; + p3_x = 0; + p3_y = height; + }, + + 0xE0BE => { + p1_x = 0; + p1_y = 0; + p2_x = width; + p2_y = 0; + p3_x = width; + p3_y = height; + }, + + else => unreachable, + } + + canvas.triangle(.{ + .p1 = .{ .x = @as(i32, @intCast(p1_x)), .y = @as(i32, @intCast(p1_y)) }, + .p2 = .{ .x = @as(i32, @intCast(p2_x)), .y = @as(i32, @intCast(p2_y)) }, + .p3 = .{ .x = @as(i32, @intCast(p3_x)), .y = @as(i32, @intCast(p3_y)) }, + }, .on); +} + +test "all" { + const testing = std.testing; + const alloc = testing.allocator; + + const cps = [_]u32{ + 0xE0B0, + 0xE0B2, + 0xE0B8, + 0xE0BA, + 0xE0BC, + 0xE0BE, + }; + for (cps) |cp| { + var atlas_greyscale = try font.Atlas.init(alloc, 512, .greyscale); + defer atlas_greyscale.deinit(alloc); + + const face: Powerline = .{ .width = 18, .height = 36, .thickness = 2 }; + const glyph = try face.renderGlyph( + alloc, + &atlas_greyscale, + cp, + ); + try testing.expectEqual(@as(u32, face.width), glyph.width); + try testing.expectEqual(@as(u32, face.height), glyph.height); + } +}