const limine = @import("limine"); const builtin = @import("builtin"); const psf2 = @import("psf2.zig"); pub const Font = psf2.Font; const std = @import("std"); const fontdata = @embedFile("fonts/spleen-12x24.psf"); const are_we_le = builtin.cpu.arch.endian() == .little; pub const DefaultFont = Font.new(fontdata) catch unreachable; pub const Framebuffer = struct { const Self = @This(); address: [*]u8, width: u64, height: u64, pitch: u64, bypp: u16, red_mask_size: u8, red_mask_shift: u8, green_mask_size: u8, green_mask_shift: u8, blue_mask_size: u8, blue_mask_shift: u8, pub fn from_limine(fb: *const limine.Framebuffer) Self { return .{ .address = @ptrCast(fb.address), .width = fb.width, .height = fb.height, .pitch = fb.pitch, .red_mask_size = fb.red_mask_size, .red_mask_shift = fb.red_mask_shift, .green_mask_size = fb.green_mask_size, .green_mask_shift = fb.green_mask_shift, .blue_mask_size = fb.blue_mask_size, .blue_mask_shift = fb.blue_mask_shift, .bypp = fb.bpp / 8, }; } }; pub const Console = struct { const Self = @This(); const Writer = std.io.GenericWriter(*Self, error{}, write); // framebuffer data fb: Framebuffer, // font font: psf2.Font, // state data current_x: u64 = 0, current_y: u64 = 0, fg_color: u32 = 0xFFFFFFFF, bg_color: u32 = 0, pub fn from_font(fb: Framebuffer, font: psf2.Font) Self { return .{ .fb = fb, .font = font, }; } // places a character at the given position pub fn putchar(self: *const Self, ch: u8, cx: u64, cy: u64, fg_val: u32, bg_val: u32) void { // convert colors to bytes const fg_bytes: [4]u8 = @bitCast(if (are_we_le) fg_val else @byteSwap(fg_val)); const bg_bytes: [4]u8 = @bitCast(if (are_we_le) bg_val else @byteSwap(bg_val)); // initial calculations const bytes_per_line = self.font.hdr.bytesPerLine(); const mask_shamt: u5 = @intCast(bytes_per_line * 8 - 1); const mask_initial: u32 = @as(u32, 1) << mask_shamt; const glyph = self.font.getGlyph(ch) catch return; // find the screen offset for the beignning of the character // add pitch to go to next line... var offset: u64 = (cy * self.font.hdr.height * self.fb.pitch) + (cx * (self.font.hdr.width + 0) * self.fb.bypp); // run for every line var y: u32 = 0; var mask: u32 = 0; while (y < self.font.hdr.height) : (y += 1) { // initialize the mask and the current line mask = mask_initial; // get the current line const line_value: u32 = psf2.readIntTo32(glyph[y * bytes_per_line ..][0..bytes_per_line]); var line_offset: u64 = offset; var x: u32 = 0; while (x < self.font.hdr.width) : (x += 1) { // write the pixel value to the correct position of the screen... if (line_value & mask != 0) { @memcpy(self.fb.address[line_offset..][0..self.fb.bypp], fg_bytes[0..]); } else { @memcpy(self.fb.address[line_offset..][0..self.fb.bypp], bg_bytes[0..]); } line_offset += self.fb.bypp; mask >>= 1; } offset += self.fb.pitch; } } pub fn putc(self: *Self, ch: u8) void { // input can be \r, \n, or printable. // ignore \r, move down for \n, and print char normally // if \n, check to see if we overrun then scroll // if normal, see if we overrun the end and do newline if (ch == '\r') return; if (ch == '\n') { self.current_x = 0; self.current_y += 1; // go to top if we went below the bottom of the screen if (self.current_y >= self.maxCharsHeight()) { self.scrollUp(1); self.current_y = self.maxCharsHeight() - 1; } return; } self.putchar(ch, self.current_x, self.current_y, self.fg_color, self.bg_color); self.current_x += 1; if (self.current_x < self.maxCharsWidth()) return; self.current_x = 0; self.current_y += 1; if (self.current_y >= self.maxCharsHeight()) { self.scrollUp(1); self.current_y = self.maxCharsHeight() - 1; } } pub fn puts(self: *Self, msg: []const u8) void { for (msg) |ch| { self.putc(ch); } } fn convertColor(self: *const Self, color: u32) u32 { const mult: u32 = blk: { const width: u4 = @truncate(self.fb.red_mask_size); break :blk (@as(u32, 1) << width) - 1; }; const div = 255; const red: u32 = (color >> 16) & 0xFF; const green: u32 = (color >> 8) & 0xFF; const blue: u32 = color & 0xFF; const red_shift: u5 = @truncate(self.fb.red_mask_shift); const green_shift: u5 = @truncate(self.fb.green_mask_shift); const blue_shift: u5 = @truncate(self.fb.blue_mask_shift); return (((red * mult) / div) << red_shift) | (((green * mult) / div) << green_shift) | (((blue * mult) / div) << blue_shift); } pub fn setColor(self: *Self, fg: u32, bg: u32) void { self.fg_color = self.convertColor(fg); self.bg_color = self.convertColor(bg); } pub fn writer(self: *Self) Writer { return .{ .context = self }; } pub fn write(self: *Self, buffer: []const u8) !usize { self.puts(buffer); return buffer.len; } // scroll the lines of text, without doing anything else. // erase the first line of text, and memcpy the second line and on up to the first pub fn scrollUp(self: *Self, amount: u64) void { const num_lines = self.maxCharsHeight(); const h = self.font.hdr.height; if (amount > num_lines) return; // later just clear the entire screen var i: u64 = amount; while (i < num_lines) : (i += 1) { // for each run, erase the previous line and copy the current line up a line. // const curr_line = self.fb.address[i * h * self.fb.pitch .. (i + 1) * h * self.fb.pitch]; const curr_line = self.fb.address[i * h * self.fb.pitch ..][0 .. h * self.fb.pitch]; const prev_line = self.fb.address[(i - amount) * h * self.fb.pitch ..][0 .. h * self.fb.pitch]; @memset(prev_line, 0); @memcpy(prev_line, curr_line); } // finally, delete the last line (s) // const last_line = self.fb.address[(num_lines - amount) * h * self.fb.pitch .. (num_lines) * h * self.fb.pitch]; const last_line = self.fb.address[(num_lines - amount) * h * self.fb.pitch ..][0 .. amount * h * self.fb.pitch]; @memset(last_line, 0); } fn maxCharsWidth(self: *const Self) u64 { return self.fb.width / (self.font.hdr.width + 0); } fn maxCharsHeight(self: *const Self) u64 { return self.fb.height / self.font.hdr.height; } };