diff --git a/base/usr/share/fonts/Tamsyn-Bold.psf b/base/usr/share/fonts/Tamsyn-Bold.psf new file mode 100644 index 00000000..4eb9107d Binary files /dev/null and b/base/usr/share/fonts/Tamsyn-Bold.psf differ diff --git a/base/usr/share/fonts/Tamsyn-Regular.psf b/base/usr/share/fonts/Tamsyn-Regular.psf new file mode 100644 index 00000000..6fd82c17 Binary files /dev/null and b/base/usr/share/fonts/Tamsyn-Regular.psf differ diff --git a/libui/CMakeLists.txt b/libui/CMakeLists.txt index b33466c7..c28eb3e2 100644 --- a/libui/CMakeLists.txt +++ b/libui/CMakeLists.txt @@ -6,9 +6,11 @@ set(SOURCES ${HEADERS} src/Canvas.cpp src/Rect.cpp + src/Font.cpp ) add_library(ui ${SOURCES}) target_compile_options(ui PRIVATE ${COMMON_FLAGS}) target_include_directories(ui PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include/) target_include_directories(ui PUBLIC ${LUNA_BASE}/usr/include) +target_link_libraries(ui PUBLIC os) diff --git a/libui/include/ui/Font.h b/libui/include/ui/Font.h new file mode 100644 index 00000000..2f2962cb --- /dev/null +++ b/libui/include/ui/Font.h @@ -0,0 +1,55 @@ +#pragma once +#include +#include +#include +#include + +#define PSF_FONT_MAGIC 0x864ab572 + +namespace ui +{ + class Font : public Shareable + { + public: + enum FontWeight + { + Regular, + Bold, + }; + + static Result> load(const os::Path& path); + static Result> load_builtin(StringView name, FontWeight weight); + + static SharedPtr default_font(); + static SharedPtr default_bold_font(); + + void render(wchar_t codepoint, ui::Color color, ui::Canvas& canvas); + void render(const wchar_t* text, ui::Color color, ui::Canvas& canvas); + + int width() const + { + return m_psf_header.width; + } + + int height() const + { + return m_psf_header.height; + } + + private: + struct PSFHeader + { + u32 magic; + u32 version; // zero + u32 headersize; + u32 flags; // 0 if there's no unicode table + u32 numglyph; + u32 bytesperglyph; + int height; + int width; + }; + + PSFHeader m_psf_header; + Buffer m_font_data; + }; +}; diff --git a/libui/src/Font.cpp b/libui/src/Font.cpp new file mode 100644 index 00000000..c1291757 --- /dev/null +++ b/libui/src/Font.cpp @@ -0,0 +1,117 @@ +#include +#include +#include + +SharedPtr s_default_font = {}; +SharedPtr s_default_bold_font = {}; + +constexpr static int BYTES_PER_PIXEL = (int)sizeof(ui::Color); + +namespace ui +{ + Result> Font::load(const os::Path& path) + { + auto font = TRY(make_shared()); + + auto file = TRY(os::File::open(path, os::File::ReadOnly)); + + TRY(file->read_typed(font->m_psf_header)); + + if (font->m_psf_header.magic != PSF_FONT_MAGIC) + { + os::eprintln("ui::Font::load(%s) failed: font magic does not match PSF2 magic", path.name().chars()); + return err(ENOTSUP); + } + + if (font->m_psf_header.version != 0) + { + os::eprintln("ui::Font::load(%s) failed: font version is unsupported", path.name().chars()); + return err(ENOTSUP); + } + + if (font->m_psf_header.flags) + { + os::eprintln("ui::Font::load(%s) warning: font has a unicode table, which we're ignoring", + path.name().chars()); + // todo(); // Font has a unicode table, oh no! + } + + font->m_font_data = TRY(file->read_all()); // Read the rest of the file into the font data buffer. + + return font; + } + + Result> Font::load_builtin(StringView name, FontWeight weight) + { + auto path = TRY(String::format("/usr/share/fonts/%s-%s.psf"_sv, name.chars(), + weight == FontWeight::Bold ? "Bold" : "Regular")); + + return load(path.view()); + } + + SharedPtr Font::default_font() + { + if (s_default_font) return s_default_font; + s_default_font = load("/usr/share/fonts/Tamsyn-Regular.psf").release_value(); + return s_default_font; + } + + SharedPtr Font::default_bold_font() + { + if (s_default_bold_font) return s_default_bold_font; + s_default_bold_font = load("/usr/share/fonts/Tamsyn-Bold.psf").release_value(); + return s_default_bold_font; + } + + void Font::render(wchar_t codepoint, ui::Color color, ui::Canvas& canvas) + { + check(canvas.width == m_psf_header.width && canvas.height == m_psf_header.height); + + const wchar_t str[] = { codepoint, 0 }; + render(str, color, canvas); + } + + void Font::render(const wchar_t* text, ui::Color color, ui::Canvas& canvas) + { + usize len = wcslen(text); + + int height = m_psf_header.height; + int width = m_psf_header.width; + int last_char_width = width; + + if (canvas.width < (m_psf_header.width * static_cast(len))) + { + len = (canvas.width / width) + 1; + last_char_width = canvas.width % width; + } + + if (canvas.height < height) height = canvas.height; + + const int bytes_per_line = (m_psf_header.width + 7) / 8; + + for (usize i = 0; i < len; i++) + { + if (i + 1 == len) width = last_char_width; + wchar_t codepoint = text[i]; + + u8* glyph = + m_font_data.data() + (codepoint > 0 && codepoint < (wchar_t)m_psf_header.numglyph ? codepoint : 0) * + m_psf_header.bytesperglyph; + + u32 offset = (u32)i * (m_psf_header.width + 1) * BYTES_PER_PIXEL; + for (int y = 0; y < height; y++) + { + u32 line = offset; + int mask = 1 << (m_psf_header.width - 1); + for (int x = 0; x < m_psf_header.width; x++) + { + if (*((u32*)glyph) & mask) *(u32*)(canvas.ptr + line) = color.raw; + mask >>= 1; + line += BYTES_PER_PIXEL; + } + glyph += bytes_per_line; + offset += canvas.stride * BYTES_PER_PIXEL; + } + } + } +}