From e55291a32a8b3f7b5c105cd79e86f3c19f19b3d4 Mon Sep 17 00:00:00 2001 From: hachem Date: Wed, 7 Oct 2026 21:43:38 +0200 Subject: feat: tty and ttf, you'd think somethn starting with tt would be fun to do... --- kernel/console.rv | 79 +++++++++++++-- kernel/fs.rv | 16 +++ kernel/input.rv | 23 ++--- kernel/main.rv | 7 ++ kernel/seed.rv | 8 ++ kernel/shell.rv | 156 +++++++++++++++++++++++++++-- kernel/ttf.rv | 286 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ kernel/tty.rv | 151 ++++++++++++++++++++++++++++ 8 files changed, 699 insertions(+), 27 deletions(-) create mode 100644 kernel/ttf.rv create mode 100644 kernel/tty.rv (limited to 'kernel') 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 )\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 [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 [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 ", "shell", "print it back", cmd_echo) register("mkfs", "shell", "format the disk", cmd_mkfs) register("cd ", "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, +} -- cgit v1.3