hachem.wtf
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile1
-rw-r--r--boot/boot.zig38
-rw-r--r--boot/fonts.zig11
-rw-r--r--boot/heap.zig46
-rw-r--r--boot/limine.conf1
-rw-r--r--bridge/bridge.zig2
-rw-r--r--bridge/hosts.zig32
-rw-r--r--build.zig14
-rw-r--r--fonts/acorn8.psfbin0 -> 2052 bytes
-rw-r--r--fonts/dejavu.ttfbin0 -> 8488 bytes
-rw-r--r--fonts/pearl8.psfbin0 -> 2052 bytes
-rw-r--r--fonts/sun16.psfbin0 -> 4100 bytes
-rw-r--r--fonts/terminess.ttfbin0 -> 9496 bytes
-rw-r--r--fonts/vga16.psfbin0 -> 4100 bytes
-rw-r--r--fonts/vga8.psfbin0 -> 2052 bytes
-rw-r--r--gils/ed.rv2
-rw-r--r--gils/gils.rv4
-rw-r--r--kernel/console.rv79
-rw-r--r--kernel/fs.rv16
-rw-r--r--kernel/input.rv23
-rw-r--r--kernel/main.rv7
-rw-r--r--kernel/seed.rv8
-rw-r--r--kernel/shell.rv156
-rw-r--r--kernel/ttf.rv286
-rw-r--r--kernel/tty.rv151
-rw-r--r--tools/genfonts.py41
-rwxr-xr-xtools/subset-ttf.sh13
27 files changed, 883 insertions, 48 deletions
diff --git a/Makefile b/Makefile
index 5b44c0b..6560bd9 100644
--- a/Makefile
+++ b/Makefile
@@ -26,7 +26,6 @@ kernel:
limine/limine:
$(MAKE) -C limine
-
iso: kernel limine/limine
rm -rf iso_root
mkdir -p iso_root/boot/limine iso_root/EFI/BOOT
diff --git a/boot/boot.zig b/boot/boot.zig
index 4918881..267fc3f 100644
--- a/boot/boot.zig
+++ b/boot/boot.zig
@@ -9,6 +9,7 @@ const heap = @import("heap.zig");
const vmm = @import("vmm.zig");
const user = @import("user.zig");
const elf = @import("elf.zig");
+const fontdata = @import("fonts.zig");
const keyboard = @import("keyboard.zig");
const timer = @import("timer.zig");
const sched = @import("sched.zig");
@@ -160,6 +161,38 @@ fn kTaskMax() u64 {
return sched.taskMax();
}
+// i dont actually like implementing fonts in any project
+// it be revel or any opengl or vulkan or whatever the fuck
+// fonts are always the ugh parts of a projects
+// just so much boilerplate
+const FontBlob = struct { name: []const u8, phys: u64, size: u64 };
+var g_fonts: [fontdata.fonts.len]FontBlob = undefined;
+
+fn loadFonts() void {
+ for (fontdata.fonts, 0..) |f, i| {
+ const pages = (f.bytes.len + 4095) / 4096;
+ const phys = pmm.allocContig(pages) orelse @panic("no room for an embedded font");
+ @memcpy(@as([*]u8, @ptrFromInt(pmm.physToVirt(phys)))[0..f.bytes.len], f.bytes);
+ g_fonts[i] = .{ .name = f.name, .phys = phys, .size = f.bytes.len };
+ }
+}
+
+fn kFontCount() u64 {
+ return g_fonts.len;
+}
+
+fn kFontPhys(i: u64) u64 {
+ return g_fonts[i].phys;
+}
+
+fn kFontName(i: u64) []const u8 {
+ return g_fonts[i].name;
+}
+
+fn kFontSize(i: u64) u64 {
+ return g_fonts[i].size;
+}
+
fn kTaskState(i: u64) u64 {
return sched.taskState(i);
}
@@ -206,6 +239,7 @@ export fn _start() callconv(.c) noreturn {
ata.init();
loadPrograms();
+ loadFonts();
// the event loop below is scheduler task 0 (the kernel), running in the
// current cr3. user tasks spawned by `run` get time fucked against it
sched.init(vmm.activePml4());
@@ -236,6 +270,10 @@ export fn _start() callconv(.c) noreturn {
.disk_read = &kDiskRead,
.disk_write = &kDiskWrite,
.alloc_contig = &kAllocContig,
+ .font_count = &kFontCount,
+ .font_phys = &kFontPhys,
+ .font_name = &kFontName,
+ .font_size = &kFontSize,
};
// hand the kernel heap + framebuffer + low level ops to revo and bring up the vm
diff --git a/boot/fonts.zig b/boot/fonts.zig
new file mode 100644
index 0000000..5db8123
--- /dev/null
+++ b/boot/fonts.zig
@@ -0,0 +1,11 @@
+// js loading fonts from host disk
+pub const Font = struct { name: []const u8, bytes: []const u8 };
+pub const fonts = [_]Font{
+ .{ .name = "vga16.psf", .bytes = @embedFile("font_vga16") },
+ .{ .name = "vga8.psf", .bytes = @embedFile("font_vga8") },
+ .{ .name = "sun16.psf", .bytes = @embedFile("font_sun16") },
+ .{ .name = "acorn8.psf", .bytes = @embedFile("font_acorn8") },
+ .{ .name = "pearl8.psf", .bytes = @embedFile("font_pearl8") },
+ .{ .name = "dejavu.ttf", .bytes = @embedFile("font_dejavu") },
+ .{ .name = "terminess.ttf", .bytes = @embedFile("font_terminess") },
+};
diff --git a/boot/heap.zig b/boot/heap.zig
index badf506..b232585 100644
--- a/boot/heap.zig
+++ b/boot/heap.zig
@@ -11,6 +11,9 @@ const MIN_BLOCK: usize = 32; // header + a usable scrap, dont split below this
var heap_start: usize = 0;
var heap_end: usize = 0;
+// next fit rover optimization. essentially its resume scanning where hte last
+// alloc landed instead of heap_start everytime. it was O(n) before
+var rover: usize = 0;
inline fn word(b: usize) *usize {
return @ptrFromInt(b);
@@ -48,25 +51,38 @@ fn allocImpl(_: *anyopaque, len: usize, alignment: std.mem.Alignment, _: usize)
if (alignment.toByteUnits() > ALIGN) return null;
const need = alignUp(HEADER + len, ALIGN);
- var b = heap_start;
- while (b < heap_end) {
- if (!blkUsed(b)) {
- // lazy coalesce
- var size = blkSize(b);
- while (b + size < heap_end and !blkUsed(b + size)) size += blkSize(b + size);
- setBlk(b, size, false);
+ if (rover < heap_start or rover >= heap_end) rover = heap_start;
- if (size >= need) {
- if (size >= need + MIN_BLOCK) {
- setBlk(b, need, true);
- setBlk(b + need, size - need, false); // the leftover tail
- } else {
- setBlk(b, size, true);
+ // next fit bull shit
+ var b = rover;
+ var limit = heap_end;
+ var pass: u8 = 0;
+ while (pass < 2) : (pass += 1) {
+ while (b < limit) {
+ if (!blkUsed(b)) {
+ // lazy coalesce
+ var size = blkSize(b);
+ while (b + size < heap_end and !blkUsed(b + size)) size += blkSize(b + size);
+ setBlk(b, size, false);
+
+ if (size >= need) {
+ if (size >= need + MIN_BLOCK) {
+ setBlk(b, need, true);
+ setBlk(b + need, size - need, false); // the leftover tail
+ rover = b + need;
+ } else {
+ setBlk(b, size, true);
+ rover = b + size;
+ }
+ if (rover >= heap_end) rover = heap_start;
+ return @ptrFromInt(b + HEADER);
}
- return @ptrFromInt(b + HEADER);
}
+ b += blkSize(b);
}
- b += blkSize(b);
+ // wrap
+ b = heap_start;
+ limit = rover;
}
return null;
}
diff --git a/boot/limine.conf b/boot/limine.conf
index 7398dca..ffeb3b5 100644
--- a/boot/limine.conf
+++ b/boot/limine.conf
@@ -2,4 +2,5 @@ timeout: 0
/Revel
protocol: limine
+ resolution: 1920x1080x32
path: boot():/boot/revel
diff --git a/bridge/bridge.zig b/bridge/bridge.zig
index 87548eb..56cfc7f 100644
--- a/bridge/bridge.zig
+++ b/bridge/bridge.zig
@@ -29,7 +29,9 @@ const init_program = blk: {
for (gils_embedded) |g| s = s ++ @embedFile("k_gils_" ++ g) ++ "\n";
s = s ++ @embedFile("k_proc") ++ "\n" ++
+ @embedFile("k_ttf") ++ "\n" ++
@embedFile("k_shell") ++ "\n" ++
+ @embedFile("k_tty") ++ "\n" ++
@embedFile("k_input") ++ "\n" ++
@embedFile("k_main");
break :blk s;
diff --git a/bridge/hosts.zig b/bridge/hosts.zig
index 51362dc..5f401aa 100644
--- a/bridge/hosts.zig
+++ b/bridge/hosts.zig
@@ -67,6 +67,11 @@ pub const KernelOps = struct {
disk_read: *const fn (u64, u64) u64, // lba, phys buf -> 1 ok / 0 fail
disk_write: *const fn (u64, u64) u64, // lba, phys buf -> 1 ok / 0 fail
alloc_contig: *const fn (u64) u64, // n contiguous pages -> phys base, 0 on fail
+
+ font_count: *const fn () u64,
+ font_phys: *const fn (u64) u64,
+ font_name: *const fn (u64) []const u8,
+ font_size: *const fn (u64) u64,
};
var g_kops: ?KernelOps = null;
@@ -281,6 +286,29 @@ fn hostProgSize(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
}
// pass shit to revo
+fn hostFontCount(_: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ return HostResult.data(Data.new.num(kops().font_count()));
+}
+
+// pass shit to revo
+fn hostFontPhys(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ const i = argInt(u64, args, 0) orelse return HostResult.other("font_phys: bad index");
+ return HostResult.data(Data.new.num(kops().font_phys(i)));
+}
+
+// pass shit to revo
+fn hostFontName(args: []const revo.Value, vm: *revo.VM) anyerror!HostResult {
+ const i = argInt(u64, args, 0) orelse return HostResult.other("font_name: bad index");
+ return HostResult.data(try vm.ownValueString(kops().font_name(i)));
+}
+
+// pass shit to revo
+fn hostFontSize(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ const i = argInt(u64, args, 0) orelse return HostResult.other("font_size: bad index");
+ return HostResult.data(Data.new.num(kops().font_size(i)));
+}
+
+// pass shit to revo
fn hostTaskMax(_: []const revo.Value, _: *revo.VM) anyerror!HostResult {
return HostResult.data(Data.new.num(kops().task_max()));
}
@@ -436,6 +464,10 @@ pub fn passShitToRevo(vm: *revo.VM) !void {
try vm.registerGlobal("prog_phys", try vm.installHost("prog_phys", define(&[_]T{.number}, hostProgPhys)));
try vm.registerGlobal("prog_name", try vm.installHost("prog_name", define(&[_]T{.number}, hostProgName)));
try vm.registerGlobal("prog_size", try vm.installHost("prog_size", define(&[_]T{.number}, hostProgSize)));
+ try vm.registerGlobal("font_count", try vm.installHost("font_count", define(&[_]T{}, hostFontCount)));
+ try vm.registerGlobal("font_phys", try vm.installHost("font_phys", define(&[_]T{.number}, hostFontPhys)));
+ try vm.registerGlobal("font_name", try vm.installHost("font_name", define(&[_]T{.number}, hostFontName)));
+ try vm.registerGlobal("font_size", try vm.installHost("font_size", define(&[_]T{.number}, hostFontSize)));
try vm.registerGlobal("task_max", try vm.installHost("task_max", define(&[_]T{}, hostTaskMax)));
try vm.registerGlobal("task_state", try vm.installHost("task_state", define(&[_]T{.number}, hostTaskState)));
try vm.registerGlobal("memmap_count", try vm.installHost("memmap_count", define(&[_]T{}, hostMemmapCount)));
diff --git a/build.zig b/build.zig
index d55961f..469d513 100644
--- a/build.zig
+++ b/build.zig
@@ -70,7 +70,9 @@ pub fn build(b: *std.Build) void {
.{ "k_vfs", "kernel/vfs.rv" },
.{ "k_seed", "kernel/seed.rv" },
.{ "k_proc", "kernel/proc.rv" },
+ .{ "k_ttf", "kernel/ttf.rv" },
.{ "k_shell", "kernel/shell.rv" },
+ .{ "k_tty", "kernel/tty.rv" },
.{ "k_input", "kernel/input.rv" },
.{ "k_main", "kernel/main.rv" },
}) |e| {
@@ -84,6 +86,18 @@ pub fn build(b: *std.Build) void {
kernel.root_module.addImport("bridge", bridge_mod);
+ inline for (.{
+ .{ "font_vga16", "fonts/vga16.psf" },
+ .{ "font_vga8", "fonts/vga8.psf" },
+ .{ "font_sun16", "fonts/sun16.psf" },
+ .{ "font_acorn8", "fonts/acorn8.psf" },
+ .{ "font_pearl8", "fonts/pearl8.psf" },
+ .{ "font_dejavu", "fonts/dejavu.ttf" },
+ .{ "font_terminess", "fonts/terminess.ttf" },
+ }) |e| {
+ kernel.root_module.addAnonymousImport(e[0], .{ .root_source_file = b.path(e[1]) });
+ }
+
inline for (.{ "hexview", "calc", "primes", "save" }) |prog| {
const cc = b.addSystemCommand(&.{
"zig", "cc",
diff --git a/fonts/acorn8.psf b/fonts/acorn8.psf
new file mode 100644
index 0000000..57bc46b
--- /dev/null
+++ b/fonts/acorn8.psf
Binary files differ
diff --git a/fonts/dejavu.ttf b/fonts/dejavu.ttf
new file mode 100644
index 0000000..597b2e0
--- /dev/null
+++ b/fonts/dejavu.ttf
Binary files differ
diff --git a/fonts/pearl8.psf b/fonts/pearl8.psf
new file mode 100644
index 0000000..acf7939
--- /dev/null
+++ b/fonts/pearl8.psf
Binary files differ
diff --git a/fonts/sun16.psf b/fonts/sun16.psf
new file mode 100644
index 0000000..8042ba3
--- /dev/null
+++ b/fonts/sun16.psf
Binary files differ
diff --git a/fonts/terminess.ttf b/fonts/terminess.ttf
new file mode 100644
index 0000000..0055a2c
--- /dev/null
+++ b/fonts/terminess.ttf
Binary files differ
diff --git a/fonts/vga16.psf b/fonts/vga16.psf
new file mode 100644
index 0000000..652b07b
--- /dev/null
+++ b/fonts/vga16.psf
Binary files differ
diff --git a/fonts/vga8.psf b/fonts/vga8.psf
new file mode 100644
index 0000000..3fcce09
--- /dev/null
+++ b/fonts/vga8.psf
Binary files differ
diff --git a/gils/ed.rv b/gils/ed.rv
index 00ff50a..2572ba3 100644
--- a/gils/ed.rv
+++ b/gils/ed.rv
@@ -124,7 +124,7 @@ end
fn ed_quit(state) do
state.active = :false
- puts("% ")
+ puts(prompt())
end
global ed_state = { active = :false, input = :false, path = "", lines = {}, dot = 0, insert_at = 0, dirty = :false }
diff --git a/gils/gils.rv b/gils/gils.rv
index 1a61cc5..afd0bbd 100644
--- a/gils/gils.rv
+++ b/gils/gils.rv
@@ -24,10 +24,6 @@ end
# scripts read their args off env.ctx, stashed here before eval
global env = { ctx = :nil }
-# working dir + path resolver, shared by every gils tool and the shell. lives
-# here so its declared before anything that uses it gets concatenated
-global wd = { path = "/" }
-
global abspath = fn(arg) do
if arg:len() == 0 return wd.path
let path = arg
diff --git a/kernel/console.rv b/kernel/console.rv
index 7948a74..3bff6de 100644
--- a/kernel/console.rv
+++ b/kernel/console.rv
@@ -4,15 +4,78 @@ const FG_COLOR = 0xd0d0d0
const BG_COLOR = 0x101018
const CURSOR_COLOR = 0x50fa7b
+global wd = { path = "/" }
+global prompt = fn() "revel:#{wd.path}$ "
+
+# runtime font bs
+fn embolden(byte, row) do return (byte bor (byte shl 1)) band 0xFF end
+fn slant(byte, row) do return (byte shl ((FONT_HEIGHT - 1 - row) shr 2)) band 0xFF end
+
+fn remap_glyphs(transform) do
+ let out = {}
+ for glyph_index in 0..FONT:len() do
+ const glyph = FONT[glyph_index]
+ let rows = {}
+ for row_index in 0..FONT_HEIGHT do rows:push(transform(glyph[row_index], row_index)) end
+ out:push(rows)
+ end
+ return out
+end
+
+global reverse8 = fn(byte) do
+ let out = 0
+ for bit in 0..8 do
+ if (byte shr bit) band 1 == 1 do out = out bor (1 shl (7 - bit)) end
+ end
+ return out
+end
+
+global fonts = {}
+global add_font = fn(name, glyphs, width, height, kind, path, size) do
+ for index in 0..fonts:len() do
+ if fonts[index].name == name do
+ fonts[index].glyphs = glyphs
+ fonts[index].width = width
+ fonts[index].height = height
+ fonts[index].kind = kind
+ fonts[index].path = path
+ fonts[index].size = size
+ return :nil
+ end
+ end
+ fonts:push({ name = name, glyphs = glyphs, width = width, height = height, kind = kind, path = path, size = size })
+end
+global find_font = fn(name) do
+ for index in 0..fonts:len() do
+ if fonts[index].name == name return fonts[index]
+ end
+ return :nil
+end
+
+add_font("vga", FONT, FONT_WIDTH, FONT_HEIGHT, :bitmap, :nil, 0)
+add_font("bold", remap_glyphs(embolden), FONT_WIDTH, FONT_HEIGHT, :bitmap, :nil, 0)
+add_font("italic", remap_glyphs(slant), FONT_WIDTH, FONT_HEIGHT, :bitmap, :nil, 0)
+add_font("bolditalic", remap_glyphs(fn(byte, row) do return slant(embolden(byte, row), row) end), FONT_WIDTH, FONT_HEIGHT, :bitmap, :nil, 0)
+
global console = {
- column = 0, row = 0, scale = 1, shown = :false, cursor_x = 0, cursor_y = 0, blink = 0,
+ column = 0, row = 0, scale = 1, shown = :false, cursor_x = 0, cursor_y = 0, blink = 0, font = FONT, font_width = FONT_WIDTH, font_height = FONT_HEIGHT, font_entry = find_font("vga"),
- # cell size in pixels at the current zoom
- cell_width = fn(self) FONT_WIDTH * self.scale,
- cell_height = fn(self) FONT_HEIGHT * self.scale,
+ # this is now font dependent
+ cell_width = fn(self) self.font_width * self.scale,
+ cell_height = fn(self) self.font_height * self.scale,
columns = fn(self) fb_width() / self:cell_width(),
rows = fn(self) fb_height() / self:cell_height(),
+ set_font = fn(self, entry) do
+ self.font = entry.glyphs
+ self.font_width = entry.width
+ self.font_height = entry.height
+ self.font_entry = entry
+ # a ttf renders at its true pixel size so we js bitmap zoom scale
+ if entry.kind == :ttf do self.scale = 1 end
+ self:clear()
+ end,
+
draw_glyph = fn(self, column, row, character) do
const origin_x = column * self:cell_width()
const origin_y = row * self:cell_height()
@@ -20,10 +83,10 @@ global console = {
const code = character:ascii()
if code < 32 return :nil
if code > 127 return :nil
- const glyph = FONT[code - 32]
- for glyph_row in 0..FONT_HEIGHT do
+ const glyph = self.font[code - 32]
+ for glyph_row in 0..self.font_height do
const bits = glyph[glyph_row]
- for glyph_col in 0..FONT_WIDTH do
+ for glyph_col in 0..self.font_width do
if (bits shr glyph_col) band 1 == 1 fill_rect(origin_x + glyph_col * self.scale, origin_y + glyph_row * self.scale, self.scale, self.scale, FG_COLOR)
end
end
@@ -102,7 +165,7 @@ global on_tick = fn(ticks) do
end
if proc_exited() do
console:cursor_clear()
- puts("% ")
+ puts(prompt())
console:drain()
console.blink = 0
console:cursor_show()
diff --git a/kernel/fs.rv b/kernel/fs.rv
index 6229fd5..255797f 100644
--- a/kernel/fs.rv
+++ b/kernel/fs.rv
@@ -330,6 +330,22 @@ global fs = {
return result
end,
+ read_into = fn(self, path, dest) do
+ const entry = self:resolve(path)
+ if entry == :nil or entry.type == T_DIR return 0
+ let block = entry.first
+ let offset = 0
+ let remaining = entry.size
+ while (block != 0 and block != END and remaining > 0) do
+ const next_block = self:fat_get(block)
+ disk_read(block, dest + offset)
+ offset += BLOCK
+ remaining -= BLOCK
+ block = next_block
+ end
+ return entry.size
+ end,
+
cat = fn(self, path) do
if self:ensure() == :false return :nil
const entry = self:resolve(path)
diff --git a/kernel/input.rv b/kernel/input.rv
index 26ce545..ed0f4f7 100644
--- a/kernel/input.rv
+++ b/kernel/input.rv
@@ -25,11 +25,13 @@ end
# she stroke my keys till i handle
# no fr this is just a key stroke handler
-global shell = { line = "", shift = :false, extended = :false }
+global shell = { shift = :false, extended = :false, ctrl = :false }
fn decode_key(code) do
if code == 0x2a or code == 0x36 do shell.shift = :true return :nil end # shift down
if code == 0xaa or code == 0xb6 do shell.shift = :false return :nil end # shift up
+ if code == 0x1d do shell.ctrl = :true return :nil end # ctrl down
+ if code == 0x9d do shell.ctrl = :false return :nil end # ctrl up
if code >= 128 return :nil # other releases
if code == 28 return "\n" # enter
if code == 14 return "\x08" # backspace
@@ -37,7 +39,8 @@ fn decode_key(code) do
if code >= KEYMAP:len() return :nil
const character = if shell.shift KEYMAP_SHIFT[code] else KEYMAP[code]
if character == " " and code != 0x39 return :nil # blank cells = modifiers/unmapped
- character
+ if shell.ctrl return string.of_ascii(character:ascii() band 0x1f)
+ return character
end
global on_key = fn(code) do
@@ -49,21 +52,15 @@ global on_key = fn(code) do
else if code == 0x50 do final = 66 end # down
else if code == 0x4d do final = 67 end # right
else if code == 0x4b do final = 68 end # left
+ else if code == 0x47 do final = 72 end # home
+ else if code == 0x4f do final = 70 end # end
if final != 0 do
- stdin_deliver(27)
- stdin_deliver(91)
- stdin_deliver(final)
+ tty:feed(27) tty:feed(91) tty:feed(final)
end
return :nil
end
const character = decode_key(code)
if character == :nil return :nil
- if stdin_deliver(character:ascii()) return :nil
- if character == "\n" do puts("\n") run(shell.line) shell.line = "" return :nil end
- if character == "\x08" do
- if shell.line:len() > 0 do shell.line = shell.line:sub(0, shell.line:len() - 1) puts("\x08 \x08") end
- return :nil
- end
- shell.line ~= character
- puts(character)
+ tty:feed(character:ascii())
end
+
diff --git a/kernel/main.rv b/kernel/main.rv
index 568d695..3363bde 100644
--- a/kernel/main.rv
+++ b/kernel/main.rv
@@ -15,5 +15,12 @@ if (disk_sectors() != 0 and fs.ready == :false) do
seed_fs()
end
+# lung will be happy
+const default_face = load_ttf("/fonts/terminess.ttf", 32)
+if default_face != :nil do
+ add_font("terminess", default_face.glyphs, default_face.width, default_face.height, :ttf, "/fonts/terminess.ttf", 32)
+ console:set_font(find_font("terminess"))
+end
+
welcome()
flush_console()
diff --git a/kernel/seed.rv b/kernel/seed.rv
index ad3f4e9..058b57a 100644
--- a/kernel/seed.rv
+++ b/kernel/seed.rv
@@ -10,6 +10,13 @@ fn seed_bin() do
end
end
+fn seed_fonts() do
+ fs:mkdir("/fonts")
+ for index in 0..font_count() do
+ fs:store("/fonts/#{font_name(index)}", phys_to_str(font_phys(index), font_size(index)))
+ end
+end
+
fn seed_fs() do
fs:mkdir("/usr")
fs:mkdir("/etc")
@@ -20,4 +27,5 @@ fn seed_fs() do
fs:store("/home/i have big balls.txt", "dude my balls are actually massive")
seed_bin()
+ seed_fonts()
end
diff --git a/kernel/shell.rv b/kernel/shell.rv
index 5d0a1c2..3e24767 100644
--- a/kernel/shell.rv
+++ b/kernel/shell.rv
@@ -6,7 +6,11 @@ fn pad(text, width) do
return result
end
-fn help_row(label, description) puts(" #{pad(label, 20)}#{description}\n")
+fn help_row(label, description) do
+ let col = pad(label, 22)
+ if label:len() >= 22 do col = label ~ " " end
+ puts(" #{col}#{description}\n")
+end
fn help_group(title, group_name) do
puts("\n #{title}\n")
@@ -32,6 +36,14 @@ fn cmd_help(context) do
if (name:len() <= 3 or name[(name:len() - 3)..] != ".rv") do help_row(name, "") end
end
end
+
+ puts("\n fonts (font <name>)\n")
+ for index in 0..fonts:len() do help_row(fonts[index].name, "8x#{fonts[index].height}") end
+ const font_files = fs:entries("/fonts")
+ if font_files != :nil do
+ puts("\n fonts on disk (font load /fonts/x.psf)\n")
+ for index in 0..font_files:len() do help_row(font_files[index].name, "") end
+ end
puts("\n")
end
@@ -41,7 +53,8 @@ fn welcome() do
puts("\n")
puts(" kernel written in revo - by an alcoholic\n")
puts(" #{(mem_total() shr 20)} MiB ram | #{fs_status} | #{prog_count()} programs\n")
- puts(" type help to see what it does\n\n% ")
+ puts(" type help to see what it does\n\n")
+ puts(prompt())
end
# global so /bin scripts can call it from a nested eval fiber
@@ -84,6 +97,21 @@ fn wipe() console:clear()
fn cmd_clear(context) wipe()
fn zoom_by(delta) do
+ const entry = console.font_entry
+ # ttf is vector, stepping the size interactively is fine
+ if (entry != :nil and entry.kind == :ttf) do
+ let size = entry.size + delta * 4
+ if size < 8 do size = 8 end
+ if size > 72 do size = 72 end
+ if size == entry.size do puts("zoom #{size}px\n") return :nil end
+ const face = load_ttf(entry.path, size)
+ if face == :nil do puts("zoom: rasterize failed\n") return :nil end
+ add_font(entry.name, face.glyphs, face.width, face.height, :ttf, entry.path, size)
+ console:set_font(find_font(entry.name))
+ puts("zoom #{size}px\n")
+ return :nil
+ end
+ # bitmap fonts js use integer pixel scale
let scale = console.scale + delta
if scale < 1 do scale = 1 end
if scale > 6 do scale = 6 end
@@ -99,6 +127,119 @@ fn cmd_zoom(context) do
else do puts("usage: zoom in | zoom out\n") end
end
+fn font_basename(path) do
+ let start = 0
+ let index = 0
+ while index < path:len() do
+ if path[index] == "/" do start = index + 1 end
+ index += 1
+ end
+ let name = path[start..]
+ let dot = -1
+ for i in 0..name:len() do if name[i] == "." do dot = i end end
+ if dot > 0 do name = name[0..dot] end
+ return name
+end
+
+fn load_psf(path) do
+ const size = fs:size(path)
+ if size < 4 return :nil
+ const data = fs:read_bytes(path, 0, size)
+ if (data[0]:ascii() != 0x36 or data[1]:ascii() != 0x04) do return :nil end
+ const height = data[3]:ascii()
+ if (height == 0 or size < 4 + 128 * height) do return :nil end
+ let glyphs = {}
+ for code in 32..128 do
+ const base = 4 + code * height
+ let rows = {}
+ for row in 0..height do rows:push(reverse8(data[base + row]:ascii())) end
+ glyphs:push(rows)
+ end
+ return { glyphs = glyphs, width = 8, height = height }
+end
+
+fn has_slash(text) do
+ for index in 0..text:len() do if text[index] == "/" return :true end
+ return :false
+end
+
+fn ends_with(text, suffix) do
+ if text:len() < suffix:len() return :false
+ return text[(text:len() - suffix:len())..] == suffix
+end
+
+fn str_to_int(text) do
+ let value = 0
+ for index in 0..text:len() do
+ const code = text[index]:ascii()
+ if (code >= 48 and code <= 57) do value = value * 10 + (code - 48) end
+ end
+ return value
+end
+
+fn font_file(name) do
+ if has_slash(name) do
+ const p = abspath(name)
+ if (ends_with(p, ".ttf") and fs:size(p) >= 0) return { path = p, kind = :ttf }
+ if (ends_with(p, ".psf") and fs:size(p) >= 0) return { path = p, kind = :psf }
+ if fs:size(p ~ ".ttf") >= 0 return { path = p ~ ".ttf", kind = :ttf }
+ if fs:size(p ~ ".psf") >= 0 return { path = p ~ ".psf", kind = :psf }
+ return { path = p, kind = :none }
+ end
+ if fs:size("/fonts/#{name}.psf") >= 0 return { path = "/fonts/#{name}.psf", kind = :psf }
+ if fs:size("/fonts/#{name}.ttf") >= 0 return { path = "/fonts/#{name}.ttf", kind = :ttf }
+ return { path = name, kind = :none }
+end
+
+fn cmd_font(context) do
+ if context.argv:len() < 2 do
+ puts("fonts:")
+ for index in 0..fonts:len() do puts(" #{fonts[index].name}(#{fonts[index].width}x#{fonts[index].height})") end
+ puts("\n")
+ const font_files = fs:entries("/fonts")
+ if font_files != :nil do
+ puts("on disk:")
+ for index in 0..font_files:len() do puts(" #{font_files[index].name}") end
+ puts("\n")
+ end
+ return :nil
+ end
+ # `font load <path> [size]` still works, but a bare name/path is the same thing
+ let name = context.argv[1]
+ let arg_at = 1
+ if name == "load" do
+ if context.argv:len() < 3 do puts("usage: font load <path> [size]\n") return :nil end
+ name = context.argv[2]
+ arg_at = 2
+ end
+ # an explicit size (ttf only) forces a fresh rasterize. without one we just
+ # switch to whatever is already in the registry under that name
+ let size = 0
+ if context.argv:len() > arg_at + 1 do size = str_to_int(context.argv[arg_at + 1]) end
+ if size == 0 do
+ const existing = find_font(name)
+ if existing != :nil do console:set_font(existing) return :nil end
+ end
+ const info = font_file(name)
+ if info.kind == :none do puts("font: #{name}: no such font\n") return :nil end
+ const reg_name = font_basename(info.path)
+ if info.kind == :psf do
+ const prior = find_font(reg_name)
+ if prior != :nil do console:set_font(prior) return :nil end
+ const parsed = load_psf(info.path)
+ if parsed == :nil do puts("font: #{name}: not a psf font\n") return :nil end
+ add_font(reg_name, parsed.glyphs, parsed.width, parsed.height, :bitmap, :nil, 0)
+ console:set_font(find_font(reg_name))
+ return :nil
+ end
+ # ttf: rasterize ascii at the requested size (default 18)
+ if size == 0 do size = 18 end
+ const face = load_ttf(info.path, size)
+ if face == :nil do puts("font: #{name}: bad ttf\n") return :nil end
+ add_font(reg_name, face.glyphs, face.width, face.height, :ttf, info.path, size)
+ console:set_font(find_font(reg_name))
+end
+
fn bcd(byte) (byte shr 4) * 10 + (byte band 15)
fn rtc(cmos_reg) do
outb(0x70, cmos_reg)
@@ -151,8 +292,9 @@ end
fn run(line) do
# the fact that the shell needs to interface directly with ed isnt, very nice
+ # this is a TODO btw
if gils_ed_active() do gils_ed_feed(line) return :nil end
- if line:trim():len() == 0 do puts("% ") return :nil end
+ if line:trim():len() == 0 do puts(prompt()) return :nil end
const argv = line:split(" ")
const context = make_ctx(line, argv)
const command = lookup(argv[0])
@@ -160,13 +302,14 @@ fn run(line) do
# :hold means the command took over (modal editor / launched a program) so
# we dont draw a fresh prompt, otherwise we do
const result = command.handler(context)
- if result != :hold do puts("% ") end
+ if result != :hold do puts(prompt()) end
return :nil
end
- if run_script(argv[0], context) == :true do puts("% ") return :nil end
+ if run_script(argv[0], context) == :true do puts(prompt()) return :nil end
if run_prog(argv[0], context.rest) == :true return :nil
- puts("#{argv[0]}: command not found\n% ")
+ puts("#{argv[0]}: command not found\n")
+ puts(prompt())
end
register("help", "shell", "show this", cmd_help)
@@ -176,6 +319,7 @@ register("mem", "shell", "free / total ram", cmd_mem)
register("uptime", "shell", "seconds since boot", cmd_uptime)
register("ps", "shell", "running processes", cmd_ps)
register("zoom in|out", "shell", "scale the font", cmd_zoom)
+register("font [name] [size]", "shell", "switch/load a console font (psf or ttf)", cmd_font)
register("echo <text>", "shell", "print it back", cmd_echo)
register("mkfs", "shell", "format the disk", cmd_mkfs)
register("cd <path>", "gils", "change directory", cmd_cd)
diff --git a/kernel/ttf.rv b/kernel/ttf.rv
new file mode 100644
index 0000000..17d1a19
--- /dev/null
+++ b/kernel/ttf.rv
@@ -0,0 +1,286 @@
+# so many fucking hours wasted man i hate fonts so much
+# fuck bezier
+# fuck revo
+# fuck revel
+# fuck ttf
+# fuck lung
+# fuck me
+# im like 90% confident this works, but im like 10% confident its fucked
+fn ttf_mod(a, b) a - math.floor(a / b) * b
+
+fn ttf_u8(d, o) mem_read(d + o, 1)
+fn ttf_u16(d, o) mem_read(d + o, 1) * 256 + mem_read(d + o + 1, 1)
+fn ttf_i16(d, o) do
+ const v = ttf_u16(d, o)
+ if v >= 32768 return v - 65536
+ return v
+end
+fn ttf_u32(d, o) ((mem_read(d + o, 1) * 256 + mem_read(d + o + 1, 1)) * 256 + mem_read(d + o + 2, 1)) * 256 + mem_read(d + o + 3, 1)
+
+fn ttf_tag_eq(d, o, tag) do
+ for i in 0..4 do if mem_read(d + o + i, 1) != tag[i]:ascii() return :false end
+ return :true
+end
+
+fn ttf_table(d, tag) do
+ const n = ttf_u16(d, 4)
+ for i in 0..n do
+ const rec = 12 + i * 16
+ if ttf_tag_eq(d, rec, tag) return ttf_u32(d, rec + 8)
+ end
+ return -1
+end
+
+fn ttf_cmap_sub(d, cmap_off) do
+ const n = ttf_u16(d, cmap_off + 2)
+ let best = -1
+ for i in 0..n do
+ const rec = cmap_off + 4 + i * 8
+ const plat = ttf_u16(d, rec)
+ const enc = ttf_u16(d, rec + 2)
+ if (plat == 3 and (enc == 1 or enc == 0)) or plat == 0 do best = cmap_off + ttf_u32(d, rec + 4) end
+ end
+ return best
+end
+
+fn ttf_glyph_of(d, sub, c) do
+ if ttf_u16(d, sub) != 4 return 0
+ const segCount = ttf_u16(d, sub + 6) / 2
+ const endBase = sub + 14
+ const startBase = endBase + segCount * 2 + 2
+ const deltaBase = startBase + segCount * 2
+ const rangeBase = deltaBase + segCount * 2
+ for i in 0..segCount do
+ if c <= ttf_u16(d, endBase + i * 2) do
+ const startC = ttf_u16(d, startBase + i * 2)
+ if c < startC return 0
+ const idro = ttf_u16(d, rangeBase + i * 2)
+ if idro == 0 return (c + ttf_i16(d, deltaBase + i * 2)) band 0xFFFF
+ const g = ttf_u16(d, rangeBase + i * 2 + idro + (c - startC) * 2)
+ if g == 0 return 0
+ return (g + ttf_i16(d, deltaBase + i * 2)) band 0xFFFF
+ end
+ end
+ return 0
+end
+
+fn ttf_glyph_offset(d, loca, long, g) do
+ if long return ttf_u32(d, loca + g * 4)
+ return ttf_u16(d, loca + g * 2) * 2
+end
+
+fn ttf_advance(d, hmtx, numH, g) do
+ if g < numH return ttf_u16(d, hmtx + g * 4)
+ return ttf_u16(d, hmtx + (numH - 1) * 4)
+end
+
+fn ttf_add_line(edges, x0, y0, x1, y1) edges:push({ ax = x0, ay = y0, bx = x1, by = y1 })
+
+fn ttf_add_quad(edges, x0, y0, cx, cy, x1, y1, n) do
+ let px = x0
+ let py = y0
+ for i in 1..(n + 1) do
+ const t = i / n
+ const mt = 1 - t
+ const qx = mt * mt * x0 + 2 * mt * t * cx + t * t * x1
+ const qy = mt * mt * y0 + 2 * mt * t * cy + t * t * y1
+ ttf_add_line(edges, px, py, qx, qy)
+ px = qx
+ py = qy
+ end
+end
+
+fn ttf_contour(pts, edges, nsub) do
+ const m = pts:len()
+ if m < 2 return :nil
+ let exp = {}
+ for i in 0..m do
+ exp:push(pts[i])
+ const j = ttf_mod(i + 1, m)
+ if (pts[i].on == :false and pts[j].on == :false) do
+ exp:push({ x = (pts[i].x + pts[j].x) / 2, y = (pts[i].y + pts[j].y) / 2, on = :true })
+ end
+ end
+ const n = exp:len()
+ let s = -1
+ for i in 0..n do if (s < 0 and exp[i].on) do s = i end end
+ if s < 0 return :nil
+ let cx = exp[s].x
+ let cy = exp[s].y
+ let k = 1
+ while k <= n do
+ const a = exp[ttf_mod(s + k, n)]
+ if a.on do
+ ttf_add_line(edges, cx, cy, a.x, a.y)
+ cx = a.x
+ cy = a.y
+ k += 1
+ end
+ else do
+ const b = exp[ttf_mod(s + k + 1, n)]
+ ttf_add_quad(edges, cx, cy, a.x, a.y, b.x, b.y, nsub)
+ cx = b.x
+ cy = b.y
+ k += 2
+ end
+ end
+end
+
+# how about you contour my fucking asshole
+fn ttf_glyph_edges(d, glyf, loca, long, g, scale, ox, baseline, edges, nsub) do
+ const off = ttf_glyph_offset(d, loca, long, g)
+ const next = ttf_glyph_offset(d, loca, long, g + 1)
+ if next <= off return :nil # empty glyph
+ const base = glyf + off
+ const nc = ttf_i16(d, base)
+ if nc < 0 return :nil # skip
+ let ends = {}
+ for i in 0..nc do ends:push(ttf_u16(d, base + 10 + i * 2)) end
+ const npts = ends[nc - 1] + 1
+ let p = base + 10 + nc * 2
+ p += 2 + ttf_u16(d, p) # skip instructions
+ let flags = {}
+ while flags:len() < npts do
+ const f = ttf_u8(d, p)
+ p += 1
+ flags:push(f)
+ if (f band 0x08) != 0 do
+ let rep = ttf_u8(d, p)
+ p += 1
+ while rep > 0 do flags:push(f) rep -= 1 end
+ end
+ end
+ let xs = {}
+ let x = 0
+ for i in 0..npts do
+ const f = flags[i]
+ if (f band 0x02) != 0 do
+ const dx = ttf_u8(d, p)
+ p += 1
+ if (f band 0x10) != 0 do x += dx end else do x -= dx end
+ end
+ else do
+ if (f band 0x10) == 0 do x += ttf_i16(d, p) p += 2 end
+ end
+ xs:push(x)
+ end
+ let ys = {}
+ let y = 0
+ for i in 0..npts do
+ const f = flags[i]
+ if (f band 0x04) != 0 do
+ const dy = ttf_u8(d, p)
+ p += 1
+ if (f band 0x20) != 0 do y += dy end else do y -= dy end
+ end
+ else do
+ if (f band 0x20) == 0 do y += ttf_i16(d, p) p += 2 end
+ end
+ ys:push(y)
+ end
+ # split into contours
+ let start = 0
+ for ci in 0..nc do
+ const stop = ends[ci]
+ let pts = {}
+ for i in start..(stop + 1) do
+ pts:push({ x = xs[i] * scale + ox, y = baseline - ys[i] * scale, on = (flags[i] band 0x01) != 0 })
+ end
+ ttf_contour(pts, edges, nsub)
+ start = stop + 1
+ end
+end
+
+fn ttf_fill(edges, w, h) do
+ let rows = {}
+ for py in 0..h do
+ const yc = py + 0.5
+ let cross = {}
+ for e in 0..edges:len() do
+ const ed = edges[e]
+ const y0 = ed.ay
+ const y1 = ed.by
+ if ((y0 <= yc and yc < y1) or (y1 <= yc and yc < y0)) do
+ const t = (yc - y0) / (y1 - y0)
+ let dir = 1
+ if y1 < y0 do dir = -1 end
+ cross:push({ x = ed.ax + t * (ed.bx - ed.ax), dir = dir })
+ end
+ end
+ # sort crossings left to right
+ # insertion sort
+ for a in 1..cross:len() do
+ const key = cross[a]
+ let b = a - 1
+ while (b >= 0 and cross[b].x > key.x) do
+ cross[b + 1] = cross[b]
+ b -= 1
+ end
+ cross[b + 1] = key
+ end
+ let bits = 0
+ let wind = 0
+ for k in 0..cross:len() do
+ wind += cross[k].dir
+ if (wind != 0 and k + 1 < cross:len()) do
+ const xa = cross[k].x
+ const xb = cross[k + 1].x
+ if xb > xa do
+ let any = :false
+ for px in 0..w do
+ if (px + 0.5 >= xa and px + 0.5 < xb) do bits = bits bor (1 shl px) any = :true end
+ end
+ if any == :false do
+ const mid = math.floor((xa + xb) / 2)
+ if (mid >= 0 and mid < w) do bits = bits bor (1 shl mid) end
+ end
+ end
+ end
+ end
+ rows:push(bits)
+ end
+ return rows
+end
+
+global ttf_io = { buf = 0, pages = 512 }
+
+global load_ttf = fn(path, size) do
+ const total = fs:size(path)
+ if total < 12 return :nil
+ if ttf_io.buf == 0 do ttf_io.buf = alloc_contig(ttf_io.pages) end
+ if ttf_io.buf == 0 return :nil
+ if total > ttf_io.pages * 4096 return :nil
+ fs:read_into(path, ttf_io.buf)
+ const d = ttf_io.buf
+ const head = ttf_table(d, "head")
+ const maxp = ttf_table(d, "maxp")
+ const cmap = ttf_table(d, "cmap")
+ const loca = ttf_table(d, "loca")
+ const glyf = ttf_table(d, "glyf")
+ const hhea = ttf_table(d, "hhea")
+ const hmtx = ttf_table(d, "hmtx")
+ if (head < 0 or cmap < 0 or loca < 0 or glyf < 0 or hhea < 0) return :nil
+ const upm = ttf_u16(d, head + 18)
+ if upm == 0 return :nil
+ const long = ttf_i16(d, head + 50) == 1
+ const ascent = ttf_i16(d, hhea + 4)
+ const descent = ttf_i16(d, hhea + 6)
+ const numH = ttf_u16(d, hhea + 34)
+ const scale = size / upm
+ const baseline = math.floor(ascent * scale)
+ let height = math.floor((ascent - descent) * scale) + 1
+ const sub = ttf_cmap_sub(d, cmap)
+ if sub < 0 return :nil
+ const mgate = ttf_glyph_of(d, sub, 77) # 'M'
+ let width = math.floor(ttf_advance(d, hmtx, numH, mgate) * scale) + 1
+ if width < 2 do width = math.floor(size * 0.6) + 1 end
+ const nsub = 5
+ let glyphs = {}
+ for code in 32..128 do
+ const g = ttf_glyph_of(d, sub, code)
+ let edges = {}
+ ttf_glyph_edges(d, glyf, loca, long, g, scale, 1, baseline, edges, nsub)
+ glyphs:push(ttf_fill(edges, width, height))
+ end
+ return { glyphs = glyphs, width = width, height = height }
+end
diff --git a/kernel/tty.rv b/kernel/tty.rv
new file mode 100644
index 0000000..5fcd224
--- /dev/null
+++ b/kernel/tty.rv
@@ -0,0 +1,151 @@
+# tty layer as opposed to a future window management server bullshit thingy
+# this is just tty0 it adds nice shit like ^B and stuff
+# also has history and shit
+
+global tty = {
+ line = "", cursor = 0, esc = 0,
+ history = {}, hist_index = 0,
+
+ back = fn(self, n) do
+ let i = 0
+ while i < n do puts("\x08") i += 1 end
+ end,
+ blanks = fn(self, n) do
+ let i = 0
+ while i < n do puts(" ") i += 1 end
+ end,
+
+ insert = fn(self, ch) do
+ const pre = self.line[0..self.cursor]
+ const post = self.line[self.cursor..]
+ self.line = pre ~ ch ~ post
+ puts(ch ~ post)
+ self:back(post:len())
+ self.cursor += 1
+ end,
+
+ backspace = fn(self) do
+ if self.cursor == 0 return :nil
+ const pre = self.line[0..(self.cursor - 1)]
+ const post = self.line[self.cursor..]
+ self.line = pre ~ post
+ self.cursor -= 1
+ puts("\x08")
+ puts(post ~ " ")
+ self:back(post:len() + 1)
+ end,
+
+ move_left = fn(self) do
+ if self.cursor > 0 do puts("\x08") self.cursor -= 1 end
+ end,
+ move_right = fn(self) do
+ if self.cursor < self.line:len() do puts(self.line[self.cursor]) self.cursor += 1 end
+ end,
+ home = fn(self) do
+ self:back(self.cursor)
+ self.cursor = 0
+ end,
+ to_end = fn(self) do
+ puts(self.line[self.cursor..])
+ self.cursor = self.line:len()
+ end,
+
+ kill = fn(self) do
+ self:back(self.cursor)
+ self:blanks(self.line:len())
+ self:back(self.line:len())
+ self.line = ""
+ self.cursor = 0
+ end,
+
+ erase_word = fn(self) do
+ if self.cursor == 0 return :nil
+ let at = self.cursor
+ while (at > 0 and self.line[at - 1] == " ") do at -= 1 end
+ while (at > 0 and self.line[at - 1] != " ") do at -= 1 end
+ let count = self.cursor - at
+ while count > 0 do self:backspace() count -= 1 end
+ end,
+
+ replace_line = fn(self, text) do
+ self:kill()
+ self.line = text
+ puts(text)
+ self.cursor = text:len()
+ end,
+
+ history_prev = fn(self) do
+ if self.history:len() == 0 return :nil
+ if self.hist_index > 0 do self.hist_index -= 1 end
+ self:replace_line(self.history[self.hist_index])
+ end,
+ history_next = fn(self) do
+ if self.history:len() == 0 return :nil
+ if self.hist_index < self.history:len() - 1 do
+ self.hist_index += 1
+ self:replace_line(self.history[self.hist_index])
+ end
+ else do
+ self.hist_index = self.history:len()
+ self:replace_line("")
+ end
+ end,
+
+ submit = fn(self) do
+ puts("\n")
+ const entered = self.line
+ if entered:len() > 0 do self.history:push(entered) end
+ self.hist_index = self.history:len()
+ self.line = ""
+ self.cursor = 0
+ run(entered)
+ end,
+
+ cancel = fn(self) do
+ puts("^C\n")
+ self.line = ""
+ self.cursor = 0
+ self.hist_index = self.history:len()
+ puts(prompt())
+ end,
+
+ clear_screen = fn(self) do
+ console:clear()
+ puts(prompt())
+ puts(self.line)
+ self:back(self.line:len() - self.cursor)
+ end,
+
+ feed = fn(self, byte) do
+ if stdin_deliver(byte) return :nil
+
+ if self.esc == 1 do
+ if byte == 91 do self.esc = 2 end
+ else do self.esc = 0 end
+ return :nil
+ end
+ if self.esc == 2 do
+ self.esc = 0
+ if byte == 65 do self:history_prev() end # up
+ else if byte == 66 do self:history_next() end # down
+ else if byte == 67 do self:move_right() end # right
+ else if byte == 68 do self:move_left() end # left
+ else if byte == 72 do self:home() end # home
+ else if byte == 70 do self:to_end() end # end
+ return :nil
+ end
+ if byte == 27 do self.esc = 1 return :nil end # ESC, start a sequence
+
+ if byte == 10 do self:submit() return :nil end # enter
+ if byte == 8 or byte == 127 do self:backspace() return :nil end # bs / del
+ if byte == 1 do self:home() return :nil end # ctrl-a
+ if byte == 5 do self:to_end() return :nil end # ctrl-e
+ if byte == 3 do self:cancel() return :nil end # ctrl-c
+ if byte == 12 do self:clear_screen() return :nil end # ctrl-l
+ if byte == 21 do self:kill() return :nil end # ctrl-u
+ if byte == 23 do self:erase_word() return :nil end # ctrl-w
+ if byte < 32 return :nil # ctrl-d and other ctrls: ignore
+ if byte > 126 return :nil
+ self:insert(string.of_ascii(byte))
+ end,
+}
diff --git a/tools/genfonts.py b/tools/genfonts.py
new file mode 100644
index 0000000..e5afb7e
--- /dev/null
+++ b/tools/genfonts.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python3
+# this just turns the linxu console fonts which are C arrays into psf1 fonts
+# that i can load on disk since half of my psf1 fonts are stolen from there.
+#
+# usage: genfonts.py <font_8x16.c> <out.psf> [height]
+
+import re
+import sys
+
+
+def parse_c(path):
+ text = open(path).read()
+ cut = text.index("}, {")
+ body = text[cut + 4 :]
+ body = re.sub(r"/\*.*?\*/", "", body, flags=re.S)
+ bytes_ = [int(m, 16) for m in re.findall(r"0x([0-9a-fA-F]{2})", body)]
+ return bytes_
+
+def write_psf1(rows, height, out):
+ count = len(rows) // height
+ if count < 256:
+ rows = rows + [0] * (height * (256 - count))
+ count = 256
+ if count not in (256, 512):
+ rows = rows[: height * 256]
+ count = 256
+ mode = 0x00 if count == 256 else 0x01 # bit0 set => 512 glyphs
+ header = bytes([0x36, 0x04, mode, height])
+ with open(out, "wb") as f:
+ f.write(header)
+ f.write(bytes(rows[: height * count]))
+
+def main():
+ src, out = sys.argv[1], sys.argv[2]
+ height = int(sys.argv[3]) if len(sys.argv) > 3 else int(re.search(r"8x(\d+)", src).group(1))
+ rows = parse_c(src)
+ write_psf1(rows, height, out)
+ print(f"{out}: psf1 8x{height}, {len(rows) // height} glyphs in")
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/subset-ttf.sh b/tools/subset-ttf.sh
new file mode 100755
index 0000000..70cffa2
--- /dev/null
+++ b/tools/subset-ttf.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+# trim the ttf into a subset (U+0020..007E), its just to embed fonts that are actually
+# rendered, since we have NERD fonts and shit, they can be quite large
+
+set -e
+IN="$1"
+OUT="$2"
+python3 -m fontTools.subset "$IN" \
+ --unicodes=U+0020-007E \
+ --no-hinting --glyph-names \
+ --output-file="$OUT"
+echo "wrote $OUT"