Implement text rendering

main
xenia 2024-02-07 22:52:21 +01:00
parent ddbf083d1b
commit 2c507b6ecb
3 changed files with 306 additions and 38 deletions

View File

@ -2,22 +2,23 @@ import json
import struct
# generates:
# font_data.dat (binary file, expected to load at location 0xf000_0100)
# font_data.inc (fox32 assembly)
# font_data.inc defines constants
# N_CHARS: number of defined characters (u32)
# CHAR_TABLE: pointer to main char table (&Char)
# UNKNOWN_CHAR: pointer to unknown character (&Char)
# font_data.bin
# font_table.bin
# font_data.inc
#
# struct Char {
# width: u8, height: u8, baseline: u8,
# data_ptr: &[u8],
# data_offset: offset into font_data
# } (7 bytes)
DATA_FILE_ORIGIN = 0xf000_0100
CODEPAGE = {
0: None,
128: 'Ξ',
}
for x in range(32, 127):
CODEPAGE[x] = chr(x)
BASELINE = 7
N_CHARS = 127
codepoints = json.load(open("ultlf/codepoints.json"))
font_data = json.load(open("ultlf/data.json"))
@ -33,34 +34,51 @@ UNKNOWN = [
"# # #",
]
with open("xenrom/data/font_data.inc", "w") as inc, open("xenrom/data/font_data.dat", "wb") as dat:
table = b''
char_data = b''
SPACE = [" "]
for ch in [None] + list(range(N_CHARS)):
if ch in codepoints:
i = codepoints.index(ch)
glyph = font_data[i]
baseline = baselines[i]
if '?' in "".join(glyph):
print(f"Font doesn't have char {ch}")
glyph = UNKNOWN
baseline = 6
else:
SCALE = 2
def scale(glyph):
out = []
for line in glyph:
line_scaled = ""
for ch in line:
line_scaled += ch * SCALE
out.extend([line_scaled] * SCALE)
return out
with open("xenrom/data/font_table.dat", "wb") as table_file, open("xenrom/data/font_data.dat", "wb") as data_file, open("xenrom/data/font_data.inc", "w") as inc_file:
table = b''
data = b''
for i in range(256):
ch = CODEPAGE.get(i, None)
unicode_codepoint = ord(ch) if ch is not None else None
if unicode_codepoint is None:
glyph = UNKNOWN
baseline = 6
print(f"Couldn't find char {ch}")
elif unicode_codepoint == 32:
glyph = SPACE
baseline = 1
else:
ultlf_idx = codepoints.index(unicode_codepoint)
glyph = font_data[ultlf_idx]
baseline = baselines[ultlf_idx]
table += struct.pack("BBBI", len(glyph[0]), len(glyph), baseline, DATA_FILE_ORIGIN + len(char_data))
if "X" in "".join(glyph):
glyph = UNKNOWN
baseline = 6
glyph = scale(glyph)
baseline *= SCALE
table += struct.pack("<BBBI", len(glyph[0]), len(glyph), baseline, len(data))
for line in glyph:
for ch in glyph:
char_data += b'\x00' if ch == ' ' else b'\x01'
for ch in line:
data += b'\x00' if ch == ' ' else b'\x01'
dat.write(char_data + table)
table_file.write(table)
data_file.write(data)
inc.write(f'''
const N_CHARS: {N_CHARS}
const CHAR_TABLE: {hex(DATA_FILE_ORIGIN + len(char_data))}
const UNKNOWN_CHAR: {hex(DATA_FILE_ORIGIN)}
''')
inc_file.write(f"const FONT_SIZE: {SCALE}\n")
inc_file.write(f"const FONT_LINE_HEIGHT: {8 * SCALE}\n")

View File

@ -6,6 +6,9 @@ const BG_FRAMEBUFFER: 0x2000000
const SCREEN_WIDTH: 640
const SCREEN_HEIGHT: 480
const COLOR_WHITE: 0xffffffc0
const COLOR_BLUE: 0xffffff40
const COLOR_ORANGE: 0xff4080ff
; Globals
const FRAMENR: 0x00000000
@ -17,8 +20,6 @@ entry:
mcl ; disable MMU
mov rsp, SYS_STACK_START ; set stack pointer
mov r0, entry_str
call serial_print
mov r0, version_str_1
call serial_print
mov r0, version_str
@ -32,18 +33,64 @@ entry:
call show_logo
mov r0, BG_FRAMEBUFFER
mov r1, 640 ; width
mov r2, entry_str
mov r3, 20 ; x
mov r4, 20 ; y
mov r5, 20 ; l
mov r6, 620 ; r
mov r7, 100000 ; bot
mov r30, 2
mov r31, COLOR_WHITE
call draw_str_at
mov r2, version_str_1
call draw_str_at
mov r2, version_str
mov r31, COLOR_BLUE
call draw_str_at
mov r2, version_str_2
mov r31, COLOR_WHITE
call draw_str_at
mov r2, sha_str
mov r31, COLOR_ORANGE
call draw_str_at
mov r31, COLOR_WHITE
mov r2, version_str_3
call draw_str_at
mov r31, COLOR_WHITE
mov r2, credit_str_1
call draw_str_at
mov r31, COLOR_BLUE
mov r2, credit_str_2
call draw_str_at
mov r31, COLOR_WHITE
mov r2, credit_str_3
call draw_str_at
mov r31, COLOR_ORANGE
mov r2, credit_str_4
call draw_str_at
mov r0, booting_str
call serial_print
halt
entry_str: data.str "== Welcome to Xen32OS" data.8 0
entry_str: data.str "= " data.8 128 data.str "EN32OS =" data.8 10 data.8 0
; defines version_str, sha_str
#include "data/version.inc"
version_str_1: data.str " - version " data.8 0
version_str_2: data.str " (" data.8 0
version_str_3: data.str ") ==" data.8 10 data.8 0
version_str_1: data.str "version " data.8 0
version_str_2: data.str " (commit " data.8 0
version_str_3: data.str ")" data.8 10 data.8 0
credit_str_1: data.str "font by " data.8 0
credit_str_2: data.str "ultlang" data.8 0
credit_str_3: data.str ", os by " data.8 0
credit_str_4: data.str "xenia" data.8 0
booting_str: data.str "booting..." data.8 10 data.8 0
@ -96,4 +143,5 @@ serial_print_nl:
ret
#include "logo.asm"
#include "text.asm"

View File

@ -1 +1,203 @@
font_table: #include_bin "data/font_table.dat"
font_data: #include_bin "data/font_data.dat"
; defines FONT_SIZE and FONT_LINE_HEIGHT
#include "data/font_data.inc"
; input:
; r0: pointer to frame buffer
; r1: frame buffer width
; r2: pointer to string to draw
; r3: x coordinate to draw at
; r4: y coordinate to draw at
; r5: text box left
; r6: text box right
; r7: text box bottom
; r30: text delay (ms, blocking)
; r31: text color
; returns:
; r0, r1, r5, r6, r7, r30, r31 kept
; r2: pointer to last char drawn
; r3: x coordinate to next char to draw
; r4: y coordinate to next char to draw
draw_str_at:
; store char pointer in r8
mov r8, r2
draw_str_at_loop:
cmp r30, 0
ifnz call font_sleep
; r2: current char
movz.8 r2, [r8]
cmp r2, 0 ; check for null terminator
ifz jmp draw_str_at_loop_end
; check for newline
cmp r2, 10
ifz jmp draw_str_at_newline
; r9: char entry
mov r9, r2
mul r9, 7
add r9, font_table
; r10: width, r11: end x
movz.8 r10, [r9]
mov r11, r3
add r11, r10
; are we after the edge?
cmp r11, r6
iflt jmp draw_str_at_loop_draw
; if we have a space, don't reset
cmp r2, 32
ifz jmp draw_str_at_loop_draw
draw_str_at_linebreak:
mov r3, r5 ; reset x
; update end x
mov r11, r5
add r11, r10
; step to next line, check if we've overrun the box
add r4, FONT_LINE_HEIGHT
cmp r4, r7
ifgteq jmp draw_str_at_loop_end
draw_str_at_loop_draw:
push r0
push r1
push r2
push r3
push r4
call draw_char_at
pop r4
pop r3
pop r2
pop r1
pop r0
draw_str_at_spaceskip:
inc r8 ; update string index
mov r3, r11 ; update x coordinate
add r3, FONT_SIZE ; padding
jmp draw_str_at_loop
draw_str_at_newline:
inc r8 ; update string index
mov r3, r5 ; reset x
; step to next line, check if we've overrur the box
add r4, FONT_LINE_HEIGHT
cmp r4, r7
ifgteq jmp draw_str_at_loop_end
jmp draw_str_at_loop
draw_str_at_loop_end:
; restore char pointer into r2
mov r2, r8
ret
; input:
; r0: pointer to frame buffer
; r1: frame buffer width
; r2: char to draw
; r3: x coordinate to draw at
; r4: y coordinate to draw at
; r31: color
; returns:
; nothing
draw_char_at:
push r5
push r6
push r7
push r8
push r9
; r5: width
; r7: baseline
; r8: char data pointer
; r9: number of pixels to draw
mul r2, 7 ; size of char
add r2, font_table
movz.8 r5, [r2]
inc r2
movz.8 r9, [r2]
mul r9, r5
inc r2
movz.8 r7, [r2]
inc r2
mov r8, [r2]
add r8, font_data
; adjust y for baseline
sub r4, r7
; calculate init framebuffer address into r4
mul r4, r1
add r4, r3
mul r4, 4
add r4, r0
; r6: x counter in glyph
; r9: countdown
mov r6, 0
draw_char_at_loop:
cmp r9, 0 ; zero terminator
ifz jmp draw_char_at_loop_done
cmp.8 [r8], 0
ifnz mov [r4], r31
inc r8 ; advance glyph data pointer
dec r9 ; lower count
add r4, 4 ; advance screen pointer
inc r6 ; advance x counter
cmp r6, r5 ; are we at the end of the glyph line?
ifnz jmp draw_char_at_loop
; move screen pointer down one (x4 for 4 bytes per pixel)
add r4, r1
add r4, r1
add r4, r1
add r4, r1
sub r4, r5
sub r4, r5
sub r4, r5
sub r4, r5
mov r6, 0
jmp draw_char_at_loop
draw_char_at_loop_done:
pop r9
pop r8
pop r7
pop r6
pop r5
ret
; input
; r30: time to sleep (ms)
font_sleep:
push r0
push r1
; r0: end time
; r1: current time
in r0, 0x8000_0706 ; RTC uptime (ms)
add r0, 30
font_sleep_busyloop:
in r1, 0x8000_0706
cmp r1, r0
iflt jmp font_sleep_busyloop
pop r1
pop r0
ret