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.
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;

View File

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

View File

@ -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

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

View File

@ -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

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