hachem.wtf
aboutsummaryrefslogtreecommitdiff
path: root/kernel
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-10-07 21:43:38 +0200
committerhachem <im@hachem.wtf>2026-10-07 21:43:38 +0200
commite55291a32a8b3f7b5c105cd79e86f3c19f19b3d4 (patch)
tree0a52300c63a011683ceba1642e6f7c24e4d367ef /kernel
parent5d4a723a5f157c0ed38a348099a600d1d21c3c2b (diff)
feat: tty and ttf, you'd think somethn starting with tt would be fun to do...
Diffstat (limited to 'kernel')
-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
8 files changed, 699 insertions, 27 deletions
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,
+}