From d0b8068a7cfd5c1f5fb83408a4596d870c3fb149 Mon Sep 17 00:00:00 2001 From: apio Date: Fri, 4 Aug 2023 16:07:44 +0200 Subject: [PATCH] libui: Add PSF font loading and rendering --- base/usr/share/fonts/Tamsyn-Bold.psf | Bin 0 -> 4679 bytes base/usr/share/fonts/Tamsyn-Regular.psf | Bin 0 -> 4677 bytes libui/CMakeLists.txt | 2 + libui/include/ui/Font.h | 55 +++++++++++ libui/src/Font.cpp | 117 ++++++++++++++++++++++++ 5 files changed, 174 insertions(+) create mode 100644 base/usr/share/fonts/Tamsyn-Bold.psf create mode 100644 base/usr/share/fonts/Tamsyn-Regular.psf create mode 100644 libui/include/ui/Font.h create mode 100644 libui/src/Font.cpp diff --git a/base/usr/share/fonts/Tamsyn-Bold.psf b/base/usr/share/fonts/Tamsyn-Bold.psf new file mode 100644 index 0000000000000000000000000000000000000000..4eb9107d3c38b3d5f62599a1be264719a00103d7 GIT binary patch literal 4679 zcmeHJSCbS~6mDy)x_p>gee5cx7R8(gR@+6|Q4E+sMa3LON87N&&MLaB?l_B>6%!(& zqLM@<7(r1`tLFR(-mKym_{iJmcW&sg%PPe)ck15h@8omt>D%|2y*IuVaGb(<#pMP@~_N2CX}RUnjDiRl_EP1PtiZ@=#it(_SpT;_VNzH^F954 z{_#KjjP3n-`|khn6g<^l7S9wppHF807#^x|t=6g4YDe7P>Gq-|Nh12y${X(O!Er=UMH%{U2I)>P zu1;3tT!|L6UOB-$RjZXsMms{T)v8tK>kC6l(rKe%C`mdpl8%jKJsAy+(|Qzn8%auq zsMhef8&;}e7-sTe|3I8hl49)#-A1ER66TNFss*0{sWztQ3J5qR7v>ZfV!^;KEjd4F9~{k;CHB}ZPrCS8rvY17%$ zV03tY&6ShGO&yNkTrtspyns!8>CT@)CO0+U=QFS4!=L1uRUv3viL%a zM@Gr=Op@xR0en4tegjsRt1<8E=W*iqGYHrL!)$}Rtt4oA5wW@Wam%6sm(uT z)f3h{_=R}XJfCFNQMEWY(VnpZlz)62ufUWF=+P6IdjJ`HNZI^6qg(?|yH7OhakClM z^Ybi;y8QWm*H$!kGD^vvfA2G5b=Z^o%k`$EZ|>j!uxbvX~V(TH$GOx)D538jmDA{)5u@p4pVJem#`XWs%g* zyzb%>Cbc9_QFE4gd8*7#TCH}c)f#beWcLTnm;U*w$nOjEU&892gzP>+eUaByO9Fg86fcXCnSFAN$l?c1+~>yeOOBR!lsjJ(=+1cS9c zMNEGsd0PWhBl{SHd^z8c5XA~rt9W=;Hk@L=2d&+ZSl-))7tsddX&|AqU5D{#)Y z70ogA$IXtqcb@u1tolW)@fPvC>UhO|?@c{TgLiC6i}8?R`GJiu!!EuI=MCWGZM+$u zM)~@6b@788pbcR5-*g;>Ir4fGvHB}w&8LJJpTkq`SMGk3%PSu^w0D(2ucoQ^YaCeFgyI0xtAJe-dUa3L;2 z4RbLMgSZ%%AjYK_!hBqY1xRo?>bL?|Vj-@=)wl-NViB&x^|%2y;wCJ{65Nbia4VML zHr$Rounc$NF5HcKa4+t|{dfQm;vqEfFdjh@kK!?;cpOjQNj!yNw6Gi_Xk!#l;~A{L zvv>|;Sc!2wj~6h34qimpWbvO%Zxec((c7ZA_O_w-6?)s5_&4Z%%fFqRyR{IeguOj# zs<%&<{pRs6$STN7kkycvA+JDQg}erN9r6a`O~_l2w;^jF??B##ya#z7@&RNmBfH1>{S}Cdg*U7RXk}Hpo|y?U1h_-$1^F?11cq?1Jou qd%4T-YdG@`(`|h62ilaSiQ*0soIQ7CI z%ZKWL!fcI5w}wIf*uUTZ`P2(Soz-f!^MWKP6qt5;l>BKs??RgBuzgN+{1(H}-TZl; zI1fE0J9|#Qea8M-JX&Au&nP(;Pptm}Uh;)7JROEB){jj#s~amsl}IX8Wb*}!#Rlit z&2$j7g<(FOE(O-vj(X3m;EHMW@rK?}tJOMY)VsUqYOJ(-`Z7UaOa?(l5yEWNBq5s( zEn_j)$R(U0`E7PfbdOw)iX1pgE$4EEm`-Q3$uO)`^h_!0?pZVtMI|d71<>IhMSK0Gncm(tBp)Xdrz50r81nP=V@q|JIv{CSO=W5B}Gfn)q9yHNA`$z z-T6-o7Tb80ZvS)mztIk`M&>*7&Ff@ikiFkce`9jx%zP`T2y_N84&5}vcZ=H?4_nw~@lcfvQB)nWnDJMJ*_{UtR}%8wQB-j9N?0F3mR{D`4}!AA%%w_a zoWH1ATexgtt?K+!&Zy?>buI6&*SpxPd^E7AhxX!lRDM-+>!D*)3M_w2&!qK?rqi6W zS7z+FOgcSm`Bg3IX0g#eqF>`;F87>>`B?yi@%kHP zN{rZF$@H?t_Kg2-{j`7d*R6j_S2*LNW+M{&rxTh=P45U*i}jUt0l0j)_$I`3osL94 zh!g9dO6dZK*PqxFz-W1sAI`D@xWyZq&UCWq&fnQ1Q~fyC3K^ zqkRw4zK3bg!`UF^-4`hDVaj`$_2yyvR4TN$uWD~E=)w{Bvye}yG39RiW zu(p@PTp|Cy;A184(dlmzw~W7X+*Om~^-Zbxy!!(6(;qf_Uoif~*!mY^ogc;S_*H(A znEOSaPw{;)l`iQ&Af<{?N_vhlfY_&HaI?UOK?|29}!p%?Q7u z1E)4`glf&hYCN@R3;PE4_3WG2Pw3gzdfZC9i~R`uLH0FD1kIg5h;|e(8AV)!YcT~= zF%8$@dfb2;F&#Id12ZraH)9rV!EDSyC+6Z-+=dcvM;UjZf-Xe36II-WZp=dsb@bqF z%ttTo!2&Eq9~NOT?nOWD!~Ix-2k;;s!cshpNAM_?;V}$gIUdIocoI+HX*`2x@f@DV z3wRMPVGu841zy3ccnz;(C5EsHZ{SS~<1I9F6z3e8+tA#O<_;BWa~GPs(cHtp_oBIv zj|1$7R3dZ=m7?Y`H5}KkCn2jL??Bc--i5pec^|SC@&V*S$VZTMkdGnjA)i1#g?t9t z0QnrU5%L9O6XZ+CX2@5NEs(Dv-$1^FY=wLW`5y8EWE*5VWCvs?WEW&NWDn#=$X>`k y$bQHH$U(><$WM@;A%`KqKz@b%1~~#b3i%!K2jm#!Psm@8 +#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; + } + } + } +}