rearrange box rendering to prepare for more sprite drawing

This commit is contained in:
Mitchell Hashimoto
2022-11-27 10:17:12 -08:00
parent 3fbeca914b
commit b34e336c5c
9 changed files with 342 additions and 257 deletions

View File

@ -290,7 +290,7 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window {
const cell_size = try renderer.CellSize.init(alloc, font_group); const cell_size = try renderer.CellSize.init(alloc, font_group);
// Setup our box font // Setup our box font
font_group.group.box_font = font.BoxFont{ font_group.group.sprite = font.sprite.Face{
.width = @floatToInt(u32, cell_size.width), .width = @floatToInt(u32, cell_size.width),
.height = @floatToInt(u32, cell_size.height), .height = @floatToInt(u32, cell_size.height),
.thickness = 2, .thickness = 2,

View File

@ -50,10 +50,11 @@ faces: StyleArray,
/// the codepoint. This can be set after initialization. /// the codepoint. This can be set after initialization.
discover: ?font.Discover = null, discover: ?font.Discover = null,
/// Set this to a non-null value to enable box font glyph drawing. If this /// Set this to a non-null value to enable sprite glyph drawing. If this
/// isn't enabled we'll just fall through to trying to use regular fonts /// isn't enabled we'll just fall through to trying to use regular fonts
/// to render box glyphs. /// to render sprite glyphs. But more than likely, if this isn't set then
box_font: ?font.BoxFont = null, /// terminal rendering will look wrong.
sprite: ?font.sprite.Face = null,
pub fn init( pub fn init(
alloc: Allocator, alloc: Allocator,
@ -129,8 +130,8 @@ pub const FontIndex = packed struct {
// We start all special fonts at this index so they can be detected. // We start all special fonts at this index so they can be detected.
pub const start = std.math.maxInt(IndexInt); pub const start = std.math.maxInt(IndexInt);
/// Box drawing, this is rendered JIT using 2D graphics APIs. /// Sprite drawing, this is rendered JIT using 2D graphics APIs.
box = start, sprite = start,
}; };
style: Style = .regular, style: Style = .regular,
@ -178,50 +179,11 @@ pub fn indexForCodepoint(
style: Style, style: Style,
p: ?Presentation, p: ?Presentation,
) ?FontIndex { ) ?FontIndex {
// If this is a box drawing glyph, we use the special font index. This // If we have sprite drawing enabled, check if our sprite face can
// will force special logic where we'll render this ourselves. If we don't // handle this.
// have a box font set, then we just try to use regular fonts. if (self.sprite) |sprite| {
if (self.box_font != null) { if (sprite.hasCodepoint(cp, p)) {
if (switch (cp) { return FontIndex.initSpecial(.sprite);
// "Box Drawing" block
0x2500...0x257F => true,
// "Block Elements" block
0x2580...0x259f => true,
// "Braille" block
0x2800...0x28FF => true,
// "Symbols for Legacy Computing" block
0x1FB00...0x1FB3B => true,
0x1FB3C...0x1FB40,
0x1FB47...0x1FB4B,
0x1FB57...0x1FB5B,
0x1FB62...0x1FB66,
0x1FB6C...0x1FB6F,
=> true,
0x1FB41...0x1FB45,
0x1FB4C...0x1FB50,
0x1FB52...0x1FB56,
0x1FB5D...0x1FB61,
0x1FB68...0x1FB6B,
=> true,
0x1FB46,
0x1FB51,
0x1FB5C,
0x1FB67,
0x1FB9A,
0x1FB9B,
=> true,
0x1FB70...0x1FB8B => true,
else => false,
}) {
return FontIndex.initSpecial(.box);
} }
} }
@ -275,7 +237,7 @@ fn indexForCodepointExact(self: Group, cp: u32, style: Style, p: ?Presentation)
/// determining what atlas is needed. /// determining what atlas is needed.
pub fn presentationFromIndex(self: Group, index: FontIndex) !font.Presentation { pub fn presentationFromIndex(self: Group, index: FontIndex) !font.Presentation {
if (index.special()) |sp| switch (sp) { if (index.special()) |sp| switch (sp) {
.box => return .text, .sprite => return .text,
}; };
const face = try self.faceFromIndex(index); const face = try self.faceFromIndex(index);
@ -312,7 +274,7 @@ pub fn renderGlyph(
) !Glyph { ) !Glyph {
// Special-case fonts are rendered directly. // Special-case fonts are rendered directly.
if (index.special()) |sp| switch (sp) { if (index.special()) |sp| switch (sp) {
.box => return try self.box_font.?.renderGlyph( .sprite => return try self.sprite.?.renderGlyph(
alloc, alloc,
atlas, atlas,
glyph_index, glyph_index,
@ -402,12 +364,12 @@ test "box glyph" {
defer group.deinit(); defer group.deinit();
// Set box font // Set box font
group.box_font = font.BoxFont{ .width = 18, .height = 36, .thickness = 2 }; group.sprite = font.sprite.Face{ .width = 18, .height = 36, .thickness = 2 };
// Should find a box glyph // Should find a box glyph
const idx = group.indexForCodepoint(0x2500, .regular, null).?; const idx = group.indexForCodepoint(0x2500, .regular, null).?;
try testing.expectEqual(Style.regular, idx.style); try testing.expectEqual(Style.regular, idx.style);
try testing.expectEqual(@enumToInt(FontIndex.Special.box), idx.idx); try testing.expectEqual(@enumToInt(FontIndex.Special.sprite), idx.idx);
// Should render it // Should render it
const glyph = try group.renderGlyph( const glyph = try group.renderGlyph(

View File

@ -582,7 +582,7 @@ test "shape box glyphs" {
defer testdata.deinit(); defer testdata.deinit();
// Setup the box font // Setup the box font
testdata.cache.group.box_font = font.BoxFont{ testdata.cache.group.sprite = font.sprite.Face{
.width = 18, .width = 18,
.height = 36, .height = 36,
.thickness = 2, .thickness = 2,

View File

@ -1,7 +1,6 @@
const std = @import("std"); const std = @import("std");
const build_options = @import("build_options"); const build_options = @import("build_options");
pub const BoxFont = @import("BoxFont.zig");
pub const discovery = @import("discovery.zig"); pub const discovery = @import("discovery.zig");
pub const face = @import("face.zig"); pub const face = @import("face.zig");
pub const DeferredFace = @import("DeferredFace.zig"); pub const DeferredFace = @import("DeferredFace.zig");
@ -11,6 +10,7 @@ pub const GroupCache = @import("GroupCache.zig");
pub const Glyph = @import("Glyph.zig"); 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 Descriptor = discovery.Descriptor; pub const Descriptor = discovery.Descriptor;
pub const Discover = discovery.Discover; pub const Discover = discovery.Discover;

27
src/font/sprite.zig Normal file
View File

@ -0,0 +1,27 @@
const std = @import("std");
pub const Face = @import("sprite/Face.zig");
/// Sprites are represented as special codepoints outside of the Unicode
/// codepoint range. Unicode maxes out at U+10FFFF (21 bits), and we use the
/// high 11 bits to hide our special characters.
///
/// These characters are ONLY used for rendering and NEVER used written to
/// text files or any other exported format, so we don't use the Private Use
/// 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);
underline = start,
// 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
// studying the patterns to see if we can programmatically build our
// enum perhaps and comptime generate the drawing code at the same time.
// I'm not sure if that's advisable yet though.
};
test {
@import("std").testing.refAllDecls(@This());
}

File diff suppressed because it is too large Load Diff

96
src/font/sprite/Face.zig Normal file
View File

@ -0,0 +1,96 @@
//! This implements the built-in "sprite face". This font renders
//! the built-in glyphs for the terminal, such as box drawing fonts, as well
//! as specific sprites that are part of our rendering model such as
//! text decorations (underlines).
//!
//! This isn't really a "font face" so much as it is quacks like a font
//! face with regards to how it works with font.Group. We don't use any
//! dynamic dispatch so it isn't truly an interface but the functions
//! and behaviors are close enough to a system face that it makes it easy
//! to integrate with font.Group. This is desirable so that higher level
//! processes such as GroupCache, Shaper, etc. don't need to be aware of
//! special sprite handling and just treat it like a normal font face.
const Face = @This();
const std = @import("std");
const builtin = @import("builtin");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const font = @import("../main.zig");
const Atlas = @import("../../Atlas.zig");
const Box = @import("Box.zig");
/// The cell width and height.
width: u32,
height: u32,
/// Base thickness value for lines of sprites. This is in pixels. If you
/// want to do any DPI scaling, it is expected to be done earlier.
thickness: u32,
/// 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
// we always provide glyphs for our codepoints.
_ = p;
_ = self;
return Kind.init(cp) != null;
}
/// Render the glyph.
pub fn renderGlyph(
self: Face,
alloc: Allocator,
atlas: *Atlas,
cp: u32,
) !font.Glyph {
if (std.debug.runtime_safety) assert(self.hasCodepoint(cp, null));
// Safe to ".?" because of the above assertion.
switch (Kind.init(cp).?) {
.box => {
const f: Box = .{
.width = self.width,
.height = self.height,
.thickness = self.thickness,
};
return try f.renderGlyph(alloc, atlas, cp);
},
}
}
/// Kind of sprites we have. Drawing is implemented separately for each kind.
const Kind = enum {
box,
pub fn init(cp: u32) ?Kind {
return switch (cp) {
// Box fonts
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,
0x1FB57...0x1FB5B,
0x1FB62...0x1FB66,
0x1FB6C...0x1FB6F,
0x1FB41...0x1FB45,
0x1FB4C...0x1FB50,
0x1FB52...0x1FB56,
0x1FB5D...0x1FB61,
0x1FB68...0x1FB6B,
0x1FB70...0x1FB8B,
0x1FB46,
0x1FB51,
0x1FB5C,
0x1FB67,
0x1FB9A,
0x1FB9B,
=> .box,
else => null,
};
}
};

View File

@ -409,9 +409,9 @@ pub fn setFontSize(self: *Metal, size: font.face.DesiredSize) !void {
self.cell_size = new_cell_size; self.cell_size = new_cell_size;
// Set the cell size of the box font // Set the cell size of the box font
if (self.font_group.group.box_font) |*box| { if (self.font_group.group.sprite) |*sprite| {
box.width = @floatToInt(u32, self.cell_size.width); sprite.width = @floatToInt(u32, self.cell_size.width);
box.height = @floatToInt(u32, self.cell_size.height); sprite.height = @floatToInt(u32, self.cell_size.height);
} }
// Notify the window that the cell size changed. // Notify the window that the cell size changed.

View File

@ -502,9 +502,9 @@ pub fn setFontSize(self: *OpenGL, size: font.face.DesiredSize) !void {
self.cell_size = new_cell_size; self.cell_size = new_cell_size;
// Set the cell size of the box font // Set the cell size of the box font
if (self.font_group.group.box_font) |*box| { if (self.font_group.group.sprite) |*sprite| {
box.width = @floatToInt(u32, self.cell_size.width); sprite.width = @floatToInt(u32, self.cell_size.width);
box.height = @floatToInt(u32, self.cell_size.height); sprite.height = @floatToInt(u32, self.cell_size.height);
} }
// Notify the window that the cell size changed. // Notify the window that the cell size changed.