diff --git a/src/Window.zig b/src/Window.zig index 82bd511ef..ebe3b67c7 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -289,13 +289,6 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window { // Pre-calculate our initial cell size ourselves. const cell_size = try renderer.CellSize.init(alloc, font_group); - // Setup our box font - font_group.group.sprite = font.sprite.Face{ - .width = @floatToInt(u32, cell_size.width), - .height = @floatToInt(u32, cell_size.height), - .thickness = 2, - }; - // Convert our padding from points to pixels const padding_x = (@intToFloat(f32, config.@"window-padding-x") * x_dpi) / 72; const padding_y = (@intToFloat(f32, config.@"window-padding-y") * y_dpi) / 72; diff --git a/src/font/main.zig b/src/font/main.zig index 4bcc74ff4..ddcfe551f 100644 --- a/src/font/main.zig +++ b/src/font/main.zig @@ -11,6 +11,7 @@ pub const Glyph = @import("Glyph.zig"); pub const Library = @import("Library.zig"); pub const Shaper = @import("Shaper.zig"); pub const sprite = @import("sprite.zig"); +pub const Sprite = sprite.Sprite; pub const Descriptor = discovery.Descriptor; pub const Discover = discovery.Discover; @@ -60,6 +61,9 @@ pub const Presentation = enum(u1) { emoji = 1, // U+FEOF }; +/// A FontIndex that can be used to use the sprite font directly. +pub const sprite_index = Group.FontIndex.initSpecial(.sprite); + test { @import("std").testing.refAllDecls(@This()); } diff --git a/src/font/sprite.zig b/src/font/sprite.zig index e548cd4e9..dc3843de6 100644 --- a/src/font/sprite.zig +++ b/src/font/sprite.zig @@ -11,10 +11,11 @@ pub const Face = @import("sprite/Face.zig"); /// Area of Unicode. pub const Sprite = enum(u32) { // Start 1 above the maximum Unicode codepoint. - const start: u32 = std.math.maxInt(u21) + 1; - const end: u32 = std.math.maxInt(u32); + pub const start: u32 = std.math.maxInt(u21) + 1; + pub const end: u32 = std.math.maxInt(u32); underline = start, + underline_double = start + 1, // Note: we don't currently put the box drawing glyphs in here because // there are a LOT and I'm lazy. What I want to do is spend more time diff --git a/src/font/sprite/Canvas.zig b/src/font/sprite/Canvas.zig index 05ef1e5a1..208ad3593 100644 --- a/src/font/sprite/Canvas.zig +++ b/src/font/sprite/Canvas.zig @@ -117,13 +117,13 @@ pub fn writeAtlas(self: *Canvas, alloc: Allocator, atlas: *Atlas) !Atlas.Region /// Draw and fill a rectangle. This is the main primitive for drawing /// lines as well (which are just generally skinny rectangles...) -pub fn rect(self: *Canvas, v: Rect, color: pixman.Color) void { +pub fn rect(self: *Canvas, v: Rect, color: Color) void { const boxes = &[_]pixman.Box32{ .{ .x1 = @intCast(i32, v.x), .y1 = @intCast(i32, v.y), - .x2 = @intCast(i32, v.width), - .y2 = @intCast(i32, v.height), + .x2 = @intCast(i32, v.x + v.width), + .y2 = @intCast(i32, v.y + v.height), }, }; diff --git a/src/font/sprite/Face.zig b/src/font/sprite/Face.zig index cb4643833..d42b3ef0b 100644 --- a/src/font/sprite/Face.zig +++ b/src/font/sprite/Face.zig @@ -17,8 +17,10 @@ const builtin = @import("builtin"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; const font = @import("../main.zig"); +const Sprite = font.sprite.Sprite; const Atlas = @import("../../Atlas.zig"); const Box = @import("Box.zig"); +const underline = @import("underline.zig"); /// The cell width and height. width: u32, @@ -28,6 +30,9 @@ height: u32, /// want to do any DPI scaling, it is expected to be done earlier. thickness: u32, +/// The position fo the underline. +underline_position: u32 = 0, + /// Returns true if the codepoint exists in our sprite font. pub fn hasCodepoint(self: Face, cp: u32, p: ?font.Presentation) bool { // We ignore presentation. No matter what presentation is requested @@ -47,25 +52,42 @@ pub fn renderGlyph( if (std.debug.runtime_safety) assert(self.hasCodepoint(cp, null)); // Safe to ".?" because of the above assertion. - switch (Kind.init(cp).?) { - .box => { + return switch (Kind.init(cp).?) { + .box => box: { const f: Box = .{ .width = self.width, .height = self.height, .thickness = self.thickness, }; - return try f.renderGlyph(alloc, atlas, cp); + break :box try f.renderGlyph(alloc, atlas, cp); }, - } + + .underline => try underline.renderGlyph( + alloc, + atlas, + @intToEnum(Sprite, cp), + self.width, + self.height, + self.underline_position, + self.thickness, + ), + }; } /// Kind of sprites we have. Drawing is implemented separately for each kind. const Kind = enum { box, + underline, pub fn init(cp: u32) ?Kind { return switch (cp) { + Sprite.start...Sprite.end => switch (@intToEnum(Sprite, cp)) { + .underline, + .underline_double, + => .underline, + }, + // Box fonts 0x2500...0x257F, // "Box Drawing" block 0x2580...0x259F, // "Block Elements" block diff --git a/src/font/sprite/underline.zig b/src/font/sprite/underline.zig new file mode 100644 index 000000000..5f6d2f9af --- /dev/null +++ b/src/font/sprite/underline.zig @@ -0,0 +1,119 @@ +//! This file renders underline sprites. To draw underlines, we render the +//! full cell-width as a sprite and then draw it as a separate pass to the +//! text. +//! +//! We used to render the underlines directly in the GPU shaders but its +//! annoying to support multiple types of underlines and its also annoying +//! to maintain and debug another set of shaders for each renderer instead of +//! just relying on the glyph system we already need to support for text +//! anyways. +const std = @import("std"); +const builtin = @import("builtin"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; +const font = @import("../main.zig"); +const Sprite = font.sprite.Sprite; +const Atlas = @import("../../Atlas.zig"); + +/// Draw an underline. +pub fn renderGlyph( + alloc: Allocator, + atlas: *Atlas, + sprite: Sprite, + width: u32, + height: u32, + line_pos: u32, + line_thickness: u32, +) !font.Glyph { + // Create the canvas we'll use to draw. We draw the underline in + // a full cell size and position it according to "pos". + var canvas = try font.sprite.Canvas.init(alloc, width, height); + defer canvas.deinit(alloc); + + // Perform the actual drawing + (Draw{ + .width = width, + .height = height, + .pos = line_pos, + .thickness = line_thickness, + }).draw(&canvas, sprite); + + // 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 = @intCast(i32, height); + + return font.Glyph{ + .width = width, + .height = height, + .offset_x = 0, + .offset_y = offset_y, + .atlas_x = region.x, + .atlas_y = region.y, + .advance_x = @intToFloat(f32, width), + }; +} + +/// Stores drawing state. +const Draw = struct { + width: u32, + height: u32, + pos: u32, + thickness: u32, + + /// Draw a specific underline sprite to the canvas. + fn draw(self: Draw, canvas: *font.sprite.Canvas, sprite: Sprite) void { + switch (sprite) { + .underline => self.drawSingle(canvas), + .underline_double => self.drawDouble(canvas), + } + } + + /// Draw a single underline. + fn drawSingle(self: Draw, canvas: *font.sprite.Canvas) void { + canvas.rect(.{ + .x = 0, + .y = self.pos, + .width = self.width, + .height = self.thickness, + }, .on); + } + + /// Draw a double underline. + fn drawDouble(self: Draw, canvas: *font.sprite.Canvas) void { + canvas.rect(.{ + .x = 0, + .y = self.pos, + .width = self.width, + .height = self.thickness, + }, .on); + + canvas.rect(.{ + .x = 0, + .y = self.pos + (self.thickness * 2), + .width = self.width, + .height = self.thickness, + }, .on); + } +}; + +test "single" { + const testing = std.testing; + const alloc = testing.allocator; + + var atlas_greyscale = try Atlas.init(alloc, 512, .greyscale); + defer atlas_greyscale.deinit(alloc); + + _ = try renderGlyph( + alloc, + &atlas_greyscale, + .underline, + 36, + 18, + 9, + 2, + ); +}