font: draw single and double underlines as sprites

This commit is contained in:
Mitchell Hashimoto
2022-11-27 15:14:49 -08:00
parent 584149121d
commit 278668c953
6 changed files with 155 additions and 16 deletions

View File

@ -289,13 +289,6 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window {
// Pre-calculate our initial cell size ourselves. // Pre-calculate our initial cell size ourselves.
const cell_size = try renderer.CellSize.init(alloc, font_group); 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 // Convert our padding from points to pixels
const padding_x = (@intToFloat(f32, config.@"window-padding-x") * x_dpi) / 72; const padding_x = (@intToFloat(f32, config.@"window-padding-x") * x_dpi) / 72;
const padding_y = (@intToFloat(f32, config.@"window-padding-y") * y_dpi) / 72; const padding_y = (@intToFloat(f32, config.@"window-padding-y") * y_dpi) / 72;

View File

@ -11,6 +11,7 @@ pub const Glyph = @import("Glyph.zig");
pub const Library = @import("Library.zig"); pub const Library = @import("Library.zig");
pub const Shaper = @import("Shaper.zig"); pub const Shaper = @import("Shaper.zig");
pub const sprite = @import("sprite.zig"); pub const sprite = @import("sprite.zig");
pub const Sprite = sprite.Sprite;
pub const Descriptor = discovery.Descriptor; pub const Descriptor = discovery.Descriptor;
pub const Discover = discovery.Discover; pub const Discover = discovery.Discover;
@ -60,6 +61,9 @@ pub const Presentation = enum(u1) {
emoji = 1, // U+FEOF 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 { test {
@import("std").testing.refAllDecls(@This()); @import("std").testing.refAllDecls(@This());
} }

View File

@ -11,10 +11,11 @@ pub const Face = @import("sprite/Face.zig");
/// Area of Unicode. /// Area of Unicode.
pub const Sprite = enum(u32) { pub const Sprite = enum(u32) {
// Start 1 above the maximum Unicode codepoint. // Start 1 above the maximum Unicode codepoint.
const start: u32 = std.math.maxInt(u21) + 1; pub const start: u32 = std.math.maxInt(u21) + 1;
const end: u32 = std.math.maxInt(u32); pub const end: u32 = std.math.maxInt(u32);
underline = start, underline = start,
underline_double = start + 1,
// Note: we don't currently put the box drawing glyphs in here because // 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 // there are a LOT and I'm lazy. What I want to do is spend more time

View File

@ -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 /// Draw and fill a rectangle. This is the main primitive for drawing
/// lines as well (which are just generally skinny rectangles...) /// 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{ const boxes = &[_]pixman.Box32{
.{ .{
.x1 = @intCast(i32, v.x), .x1 = @intCast(i32, v.x),
.y1 = @intCast(i32, v.y), .y1 = @intCast(i32, v.y),
.x2 = @intCast(i32, v.width), .x2 = @intCast(i32, v.x + v.width),
.y2 = @intCast(i32, v.height), .y2 = @intCast(i32, v.y + v.height),
}, },
}; };

View File

@ -17,8 +17,10 @@ const builtin = @import("builtin");
const assert = std.debug.assert; const assert = std.debug.assert;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const font = @import("../main.zig"); const font = @import("../main.zig");
const Sprite = font.sprite.Sprite;
const Atlas = @import("../../Atlas.zig"); const Atlas = @import("../../Atlas.zig");
const Box = @import("Box.zig"); const Box = @import("Box.zig");
const underline = @import("underline.zig");
/// The cell width and height. /// The cell width and height.
width: u32, width: u32,
@ -28,6 +30,9 @@ height: u32,
/// want to do any DPI scaling, it is expected to be done earlier. /// want to do any DPI scaling, it is expected to be done earlier.
thickness: u32, thickness: u32,
/// The position fo the underline.
underline_position: u32 = 0,
/// Returns true if the codepoint exists in our sprite font. /// Returns true if the codepoint exists in our sprite font.
pub fn hasCodepoint(self: Face, cp: u32, p: ?font.Presentation) bool { pub fn hasCodepoint(self: Face, cp: u32, p: ?font.Presentation) bool {
// We ignore presentation. No matter what presentation is requested // 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)); if (std.debug.runtime_safety) assert(self.hasCodepoint(cp, null));
// Safe to ".?" because of the above assertion. // Safe to ".?" because of the above assertion.
switch (Kind.init(cp).?) { return switch (Kind.init(cp).?) {
.box => { .box => box: {
const f: Box = .{ const f: Box = .{
.width = self.width, .width = self.width,
.height = self.height, .height = self.height,
.thickness = self.thickness, .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. /// Kind of sprites we have. Drawing is implemented separately for each kind.
const Kind = enum { const Kind = enum {
box, box,
underline,
pub fn init(cp: u32) ?Kind { pub fn init(cp: u32) ?Kind {
return switch (cp) { return switch (cp) {
Sprite.start...Sprite.end => switch (@intToEnum(Sprite, cp)) {
.underline,
.underline_double,
=> .underline,
},
// Box fonts // Box fonts
0x2500...0x257F, // "Box Drawing" block 0x2500...0x257F, // "Box Drawing" block
0x2580...0x259F, // "Block Elements" block 0x2580...0x259F, // "Block Elements" block

View File

@ -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,
);
}