From 4777c85f9a87a13a28e1d2582d22228ae61eaf08 Mon Sep 17 00:00:00 2001 From: tobid7 Date: Sun, 22 Mar 2026 21:50:53 +0100 Subject: [PATCH] Readd font support (not workign yet) --- CMakeLists.txt | 5 +- backends/source/gfx_citro3d.cpp | 1 + include/pd/core/strings.hpp | 2 +- include/pd/drivers/gfx.hpp | 4 + include/pd/lithium/drawlist.hpp | 19 +- include/pd/lithium/font.hpp | 103 +++++++++++ include/pd/lithium/lithium.hpp | 1 + source/lithium/drawlist.cpp | 12 +- source/lithium/font.cpp | 275 ++++++++++++++++++++++++++++ tests/assets/default.ttf | Bin 0 -> 53384 bytes tests/gfx/source/main.cpp | 7 +- tests/gfx/source/os/horizon-ctr.cpp | 15 +- tests/gfx/source/os/horizon-nx.cpp | 2 + 13 files changed, 427 insertions(+), 19 deletions(-) create mode 100644 include/pd/lithium/font.hpp create mode 100644 source/lithium/font.cpp create mode 100644 tests/assets/default.ttf diff --git a/CMakeLists.txt b/CMakeLists.txt index 3bc8d3b..454290c 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,9 +46,10 @@ set(PD_SOURCES source/drivers/gfx.cpp # Lithium - source/lithium/pools.cpp - source/lithium/math.cpp source/lithium/drawlist.cpp + source/lithium/font.cpp + source/lithium/math.cpp + source/lithium/pools.cpp ) if(${PD_BUILD_SHARED}) diff --git a/backends/source/gfx_citro3d.cpp b/backends/source/gfx_citro3d.cpp index 10de327..09c3962 100644 --- a/backends/source/gfx_citro3d.cpp +++ b/backends/source/gfx_citro3d.cpp @@ -119,6 +119,7 @@ void GfxCitro3D::SysInit() { if (impl) return; PDLOG("GfxCitro3D::SysInit();"); impl = new Impl(); + Flags |= PDBackendFlags_FlipUV_Y; impl->pShaderRaw = Pica::AssembleCode(LIShaderCTR); impl->pCode = DVLB_ParseFile((uint32_t*)impl->pShaderRaw.data(), impl->pShaderRaw.size()); diff --git a/include/pd/core/strings.hpp b/include/pd/core/strings.hpp index 383472e..91dc310 100644 --- a/include/pd/core/strings.hpp +++ b/include/pd/core/strings.hpp @@ -149,7 +149,7 @@ class U8Iterator { } bool PeekNext32(u32& ret) { - // if ((ptr + 1) == 0 || *(ptr + 1) == 0) return false; + if (ptr == nullptr || *(ptr + 1) == 0) return false; u8 c = *ptr; if (c < 0x80) { ret = c; diff --git a/include/pd/drivers/gfx.hpp b/include/pd/drivers/gfx.hpp index 49c5a59..657e1a4 100755 --- a/include/pd/drivers/gfx.hpp +++ b/include/pd/drivers/gfx.hpp @@ -40,6 +40,7 @@ class PD_API GfxDriver : public DriverInterface { virtual void DeleteTexture(const Li::Texture& tex) {} virtual void Draw(const Pool& commands) {} Li::Texture::Ptr GetWhiteTexture() { return &pWhite; } + PDBackendFlags GetFlags() { return Flags; } protected: virtual void SysDeinit() {} @@ -62,6 +63,7 @@ class PD_API GfxDriver : public DriverInterface { ivec2 ViewPort; std::unordered_map pTextureRegestry; Li::Texture pWhite; + PDBackendFlags Flags = 0; }; struct DefaultGfxConfig { @@ -166,6 +168,8 @@ class PD_API Gfx { return driver->GetWhiteTexture(); } + static PDBackendFlags GetFlags() { return driver->GetFlags(); } + static const char* GetDriverName() { return driver->GetName(); } private: diff --git a/include/pd/lithium/drawlist.hpp b/include/pd/lithium/drawlist.hpp index d8d0754..d9bc470 100644 --- a/include/pd/lithium/drawlist.hpp +++ b/include/pd/lithium/drawlist.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include using LiPathRectFlags = PD::u32; @@ -27,18 +28,6 @@ enum LiDrawFlags_ : PD::u32 { LiDrawFlags_Close = 1 << 0, }; -using LiTextFlags = PD::u32; -enum LiTextFlags_ : PD::u32 { - LiTextFlags_None = 0, ///< Do nothing - LiTextFlags_AlignRight = 1 << 0, ///< Align Right of position - LiTextFlags_AlignMid = 1 << 1, ///< Align in the middle of pos and box - LiTextFlags_Shaddow = 1 << 2, ///< Draws the text twice to create shaddow - LiTextFlags_Wrap = 1 << 3, ///< Wrap Text: May be runs better with TMS - LiTextFlags_Short = 1 << 4, ///< Short Text: May be runs better with TMS - LiTextFlags_Scroll = 1 << 5, ///< Not implemented [scoll text if to long] - LiTextFlags_NoOOS = 1 << 6, ///< No Out of Screen Rendering -}; - namespace PD { namespace Li { class PD_API Drawlist { @@ -78,6 +67,10 @@ class PD_API Drawlist { void BindTexture(const Texture& tex); void UnbindTexture() { pCurrentTexture = Texture(); } + /** Font Handling */ + void SetFont(Font* font) { pFont = font; } + void SetFontscale(float fontscale = 1.f) { pFontScale = fontscale; } + /** Data geters */ const Pool& Data() const { return pCommands; } operator const Pool&() const { return pCommands; } @@ -111,6 +104,8 @@ class PD_API Drawlist { Pool pPath; Pool pVertices; Pool pIndices; + Font* pFont = nullptr; + float pFontScale = 1.f; }; } // namespace Li } // namespace PD \ No newline at end of file diff --git a/include/pd/lithium/font.hpp b/include/pd/lithium/font.hpp new file mode 100644 index 0000000..9928949 --- /dev/null +++ b/include/pd/lithium/font.hpp @@ -0,0 +1,103 @@ +#pragma once + +#include + +using LiTextFlags = PD::u32; +enum LiTextFlags_ : PD::u32 { + LiTextFlags_None = 0, ///< Do nothing + LiTextFlags_AlignRight = 1 << 0, ///< Align Right of position + LiTextFlags_AlignMid = 1 << 1, ///< Align in the middle of pos and box + LiTextFlags_Shaddow = 1 << 2, ///< Draws the text twice to create shaddow + LiTextFlags_Wrap = 1 << 3, ///< Wrap Text: May be runs better with TMS + LiTextFlags_Short = 1 << 4, ///< Short Text: May be runs better with TMS + LiTextFlags_Scroll = 1 << 5, ///< Not implemented [scoll text if to long] + LiTextFlags_NoOOS = 1 << 6, ///< No Out of Screen Rendering +}; + +namespace PD { +namespace Li { +class Drawlist; +class PD_API Font { + public: + struct Codepoint { + u32 pCodepoint; + fvec4 SimpleUV; + size_t Tex; + fvec2 Size; + float Offset = 0.f; + bool pInvalid; + }; + Font() {} + ~Font() {} + + /** + * Load a TTF File + * @param path Path to the TTF file + * @param px_height Pixelheight of the codepoints (limit by 64) + */ + void LoadTTF(const std::string& path, int px_height = 32); + /** + * Load a TTF File from Memory + * @param data File data + * @param px_height Pixelheight of the codepoints (limit by 64) + */ + void LoadTTF(const std::vector& data, int px_height = 32); + /** + * Function that loads a default integrated font... + * This will only work if PD_LI_INCLUDE_FONTS was set + * on lithium build cause otherwise the font data is not included + */ + void LoadDefaultFont(int id = 0, int pixel_height = 32); + /** + * Getter for Codepoint reference + * @return codepoint dataholder reference + */ + Codepoint& GetCodepoint(u32 c); + + /** + * Get Text Bounding Box + */ + fvec2 GetTextBounds(const char* text, float scale); + /** + * Extended Draw Text Function that vreates a Command List + */ + void CmdTextEx(Drawlist& dl, const fvec2& pos, u32 color, float scale, + const char* text, LiTextFlags flags = 0, const fvec2& box = 0); + + /** + * Garbage collection for TextMapSystem + */ + void CleanupTMS(); + /** + * Utility function to create a font atlas + * During TTF loading (Internal and should not be called) + */ + void BakeAndPush(bool final, std::vector& font_tex, int texszs); + std::string pWrapText(const std::string& txt, float scale, + const PD::fvec2& max, PD::fvec2& dim); + std::string pShortText(const std::string& txt, float scale, + const PD::fvec2& max, PD::fvec2& dim); + + /** Data Section */ + int PixelHeight = 0; + int DefaultPixelHeight = 24; + std::vector Textures; + /** + * 32Bit Codepoint Dataholder reference map + * **Now using unordered map** + */ + std::unordered_map CodeMap; + /** TMS */ + struct TMELEM { + PD::u32 ID; + PD::fvec2 Size; + std::string Text; + u64 TimeStamp; + }; + std::unordered_map pTMS; + + private: + size_t pCurrentTex = 0; +}; +} // namespace Li +} // namespace PD \ No newline at end of file diff --git a/include/pd/lithium/lithium.hpp b/include/pd/lithium/lithium.hpp index 0fc3cd3..9cde277 100644 --- a/include/pd/lithium/lithium.hpp +++ b/include/pd/lithium/lithium.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include diff --git a/source/lithium/drawlist.cpp b/source/lithium/drawlist.cpp index c79e862..5362fff 100644 --- a/source/lithium/drawlist.cpp +++ b/source/lithium/drawlist.cpp @@ -203,10 +203,16 @@ PD_API void Drawlist::DrawCircleFilled(const fvec2& center, float rad, PathFill(color); } -PD_API void Drawlist::DrawText(const fvec2& p, const char* text, u32 color) {} +PD_API void Drawlist::DrawText(const fvec2& p, const char* text, u32 color) { + if (!pFont) return; + pFont->CmdTextEx(*this, p, color, pFontScale, text); +} PD_API void Drawlist::DrawTextEx(const fvec2& p, const char* text, u32 color, - LiTextFlags flags, const fvec2& box) {} + LiTextFlags flags, const fvec2& box) { + if (!pFont) return; + pFont->CmdTextEx(*this, p, color, pFontScale, text, flags, box); +} PD_API void Drawlist::DrawPolyLine(const Pool& points, u32 color, LiDrawFlags flags, int t) { @@ -270,6 +276,7 @@ PD_API void Drawlist::DrawConvexPolyFilled(const Pool& points, PD_API void Drawlist::PrimQuad(Command& cmd, const Rect& quad, const Rect& uv, u32 color) { + cmd.Reserve(4, 6); cmd.Add(2, 1, 0); cmd.Add(3, 2, 0); cmd.Add(Vertex(quad.TopLeft(), uv.TopLeft(), color)); @@ -280,6 +287,7 @@ PD_API void Drawlist::PrimQuad(Command& cmd, const Rect& quad, const Rect& uv, PD_API void Drawlist::PrimTriangle(Command& cmd, const fvec2& a, const fvec2& b, const fvec2& c, u32 color) { + cmd.Reserve(3, 3); cmd.Add(2, 1, 0); cmd.Add(Vertex(a, vec2(0.f, 1.f), color)); cmd.Add(Vertex(b, vec2(1.f, 1.f), color)); diff --git a/source/lithium/font.cpp b/source/lithium/font.cpp new file mode 100644 index 0000000..ac89aa3 --- /dev/null +++ b/source/lithium/font.cpp @@ -0,0 +1,275 @@ +#include +#include +#include +#include +#include + +#define STB_TRUETYPE_IMPLEMENTATION +#include + +#include + +namespace PD { +namespace Li { +PD_API void Font::LoadTTF(const std::string& path, int px_height) { + /** + * Just use LoadFile2Mem which looks way cleaner + * and helps not having the font loading code twice + * when adding LoadTTF with mem support + */ + PDLOG("Font: Loading {}...", path); + TT::Scope st("LI_LoadTTF_" + path); + auto font = PD::IO::LoadFile2Mem(path); + LoadTTF(font, px_height); +} + +PD_API void Font::LoadTTF(const std::vector& data, int px_height) { + /** + * Some additional Info: + * Removed the stbtt get bitmapbox as we dont need to place + * the glyps nicely in the tex. next step would be using the free + * space on the y axis to get mor glyphs inside + */ + PixelHeight = px_height; + int texszs = PD::Bits::GetPow2(px_height * 16); + if (texszs > 1024) { + texszs = 1024; // Max size + } + + stbtt_fontinfo inf; + if (!stbtt_InitFont(&inf, data.data(), 0)) { + return; + } + + float scale = stbtt_ScaleForPixelHeight(&inf, PixelHeight); + + int ascent, descent, lineGap; + stbtt_GetFontVMetrics(&inf, &ascent, &descent, &lineGap); + int baseline = static_cast(ascent * scale); + + // Cache to not render same codepoint tex twice + std::map buf_cache; + + std::vector font_tex(texszs * texszs * 4, 0); + fvec2 off; + + bool empty = true; + + for (u32 ii = 0x0000; ii <= 0xFFFF; ii++) { + int gi = stbtt_FindGlyphIndex(&inf, ii); + if (gi == 0) continue; + if (stbtt_IsGlyphEmpty(&inf, gi)) continue; + + int w = 0, h = 0, xo = 0, yo = 0; + unsigned char* bitmap = + stbtt_GetCodepointBitmap(&inf, scale, scale, ii, &w, &h, &xo, &yo); + if (!bitmap || w <= 0 || h <= 0) { + if (bitmap) free(bitmap); + continue; + } + + u32 hashed_map = IO::HashMemory(std::vector(bitmap, bitmap + (w * h))); + if (buf_cache.find(hashed_map) != buf_cache.end()) { + Codepoint c = GetCodepoint(buf_cache[hashed_map]); + c.pCodepoint = ii; + CodeMap[ii] = c; + free(bitmap); + continue; + } else { + buf_cache[hashed_map] = ii; + } + + // Next row + if (off.x + w > texszs) { + off.y += PixelHeight; + off.x = 0.0f; + } + // Bake cause we go out of the tex + if (off.y + PixelHeight > texszs) { + BakeAndPush(false, font_tex, texszs); + off = 0; + std::fill(font_tex.begin(), font_tex.end(), 0); + empty = true; + } + + // UVs & Codepoint + Codepoint c; + fvec4 uvs; + // cast the ints to floats and not the floats... + // dont know where my mind was when creating the code + uvs.x = off.x / static_cast(texszs); + uvs.y = off.y / static_cast(texszs); + uvs.z = (off.x + w) / static_cast(texszs); + uvs.w = (off.y + h) / static_cast(texszs); + // Flip if needed + if (PD::Gfx::GetFlags() & PDBackendFlags_FlipUV_Y) { + uvs.y = 1.f - uvs.y; + uvs.w = 1.f - uvs.w; + } + c.SimpleUV = uvs; + c.Tex = pCurrentTex; + c.Size = fvec2(w, h); + c.Offset = baseline + yo; + c.pCodepoint = ii; + + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + int map_pos = ((static_cast(off.y) + y) * texszs + + (static_cast(off.x) + x)) * + 4; + font_tex[map_pos + 0] = 255; + font_tex[map_pos + 1] = 255; + font_tex[map_pos + 2] = 255; + font_tex[map_pos + 3] = bitmap[x + y * w]; + } + } + + empty = false; + CodeMap[ii] = c; + free(bitmap); + + // offset by 1 (prevents visual glitches i had) + off.x += w + 1; + } + + if (!empty) { + BakeAndPush(true, font_tex, texszs); + } +} + +PD_API void Font::LoadDefaultFont(int id, int pixel_height) {} + +PD_API Font::Codepoint& Font::GetCodepoint(u32 c) { + // Check if codepoijt exist or return a static invalid one + auto res = CodeMap.find(c); + if (res == CodeMap.end()) { + static Codepoint invalid; + invalid.pInvalid = true; + return invalid; + } + return res->second; +} + +PD_API fvec2 Font::GetTextBounds(const char* text, float scale) { + // Create a temp position and offset as [0, 0] + fvec2 res; + float x = 0; + // Curent Font Scale + float cfs = (DefaultPixelHeight * scale) / (float)PixelHeight; + float lh = (float)PixelHeight * cfs; + U8Iterator it(text); + u32 c; + while (it.Decode32(c)) { + auto cp = GetCodepoint(c); + if (!cp.pInvalid && c != '\n' && c != '\t' && c != ' ') { + continue; + } + switch (c) { + case L'\n': + res.y += lh; + res.x = std::max(res.x, x); + x = 0.f; + break; + case L'\t': + x += 16 * cfs; + break; + case L' ': + x += 4 * cfs; + // Fall trough here to get the same result as in + // TextCommand if/else Section + default: + x += cp.Size.x * cfs; + // Omly passing c cause i know its not used + if (it.PeekNext32(c)) { + x += 2 * cfs; + } + break; + } + } + res.x = std::max(res.x, x); + res.y += lh; + return res; +} + +PD_API void Font::CmdTextEx(Drawlist& dl, const fvec2& pos, u32 color, + float scale, const char* text, LiTextFlags flags, + const fvec2& box) { + fvec2 off; + float cfs = (DefaultPixelHeight * scale) / (float)PixelHeight; + float lh = (float)PixelHeight * cfs; + fvec2 td; + fvec2 rpos = pos; + fvec2 rbox = box; + + if (flags & (LiTextFlags_AlignMid | LiTextFlags_AlignRight)) { + td = GetTextBounds(text, scale); + } + if (flags & LiTextFlags_AlignMid) rpos = rbox * 0.5 - td * 0.5 + pos; + if (flags & LiTextFlags_AlignRight) rpos.x -= td.x; + + U8Iterator it(text); + u32 c; + Command* cmd = nullptr; + + while (it.Decode32(c)) { + auto cp = GetCodepoint(c); + if ((!cp.pInvalid && c != '\n' && c != '\t' && c != ' ') && c != '\r') + continue; + + if (c == L'\n') { + off.y += lh; + off.x = 0.f; + continue; + } + + if (c == L'\t') { + off.x += 16 * cfs; + continue; + } + if (c == L' ') { + off.x += 4 * cfs; + continue; + } + + if (cmd == nullptr || cmd->Tex != Textures[cp.Tex]) { + if (cp.Tex >= Textures.size()) continue; + cmd = &dl.NewCommand(); + cmd->Tex = Textures[cp.Tex]; + } + + if (flags & LiTextFlags_Shaddow) { + Rect rec = Math::PrimRect(rpos + off + fvec2(1, cp.Offset * cfs + 1), + cp.Size * cfs, 0.f); + dl.PrimQuad(*cmd, rec, cp.SimpleUV, 0xff111111); + } + + Rect rec = Math::PrimRect(rpos + off + fvec2(0, cp.Offset * cfs), + cp.Size * cfs, 0.f); + dl.PrimQuad(*cmd, rec, cp.SimpleUV, color); + + off.x += cp.Size.x * cfs + 2 * cfs; + } +} + +PD_API void Font::CleanupTMS() {} + +PD_API void Font::BakeAndPush(bool final, std::vector& font_tex, + int texszs) { + auto t = PD::Gfx::LoadTexture(font_tex, texszs, texszs); + PDLOG("Font: Texture backed as 0x{:X} at {}", t.GetID(), pCurrentTex); + Textures.push_back(t.GetID()); + pCurrentTex = Textures.size(); +} + +PD_API std::string Font::pWrapText(const std::string& txt, float scale, + const PD::fvec2& max, PD::fvec2& dim) { + return ""; +} + +PD_API std::string Font::pShortText(const std::string& txt, float scale, + const PD::fvec2& max, PD::fvec2& dim) { + return ""; +} + +} // namespace Li +} // namespace PD \ No newline at end of file diff --git a/tests/assets/default.ttf b/tests/assets/default.ttf new file mode 100644 index 0000000000000000000000000000000000000000..dc9a6d5522364181c258abf8b2b074185012d396 GIT binary patch literal 53384 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 literal 0 HcmV?d00001 diff --git a/tests/gfx/source/main.cpp b/tests/gfx/source/main.cpp index e43de3c..7c4fb3b 100644 --- a/tests/gfx/source/main.cpp +++ b/tests/gfx/source/main.cpp @@ -16,7 +16,7 @@ const char* ResourcePath(const char* in) { } int main(int argc, char** argv) { - PD::LogFilter(PD::LogLevel::Warning); + // PD::LogFilter(PD::LogLevel::Warning); Driver drv = Driver::OpenGL3; if (argc == 2) { if (std::string(argv[1]) == "gl2") { @@ -39,6 +39,10 @@ int main(int argc, char** argv) { PD::Li::Drawlist pList; PD::Image img(ResourcePath("icon.png")); auto pTex = PD::Gfx::LoadTexture(img, img.Width(), img.Height()); + PD::Li::Font font; + font.LoadTTF(ResourcePath("default.ttf")); + pList.SetFont(&font); + pList.SetFontscale(0.7f); while (pOs->Mainloop()) { pOs->ClearViewPort(); PD::Li::ResetPools(); @@ -48,6 +52,7 @@ int main(int argc, char** argv) { pList.BindTexture(pTex); pList.DrawRectFilled(pOs->PositionTranslate(PD::fvec2(0.02f, 0.5f)), pOs->SizeTranslate(PD::fvec2(0.3)), 0xffffffff); + pList.DrawText(5, "Hello World!", 0xff0000ff); PD::Gfx::Reset(); PD::Gfx::Draw(pList); pList.Clear(); diff --git a/tests/gfx/source/os/horizon-ctr.cpp b/tests/gfx/source/os/horizon-ctr.cpp index 25455e6..a955534 100644 --- a/tests/gfx/source/os/horizon-ctr.cpp +++ b/tests/gfx/source/os/horizon-ctr.cpp @@ -15,11 +15,21 @@ namespace PD { struct HorizonCtr::Impl { C3D_RenderTarget* Top = nullptr; C3D_RenderTarget* Bottom = nullptr; + uint32_t* pSocBuf = nullptr; }; void HorizonCtr::Init() { if (impl) return; impl = new Impl(); + impl->pSocBuf = (uint32_t*)std::aligned_alloc(0x1000, 0x100000); + if (impl->pSocBuf) { + Result ret = socInit(impl->pSocBuf, 0x100000); + if (R_FAILED(ret)) { + free(impl->pSocBuf); + impl->pSocBuf = nullptr; + } + } + link3dsStdio(); romfsInit(); gfxInitDefault(); consoleInit(GFX_BOTTOM, nullptr); @@ -41,6 +51,7 @@ void HorizonCtr::Deinit() { C3D_Fini(); gfxExit(); romfsExit(); + socExit(); delete impl; impl = nullptr; } @@ -48,7 +59,9 @@ void HorizonCtr::Deinit() { bool HorizonCtr::Mainloop() { pViewPort = ivec2(400, 240); - return aptMainLoop(); + hidScanInput(); + bool _kill = hidKeysUp() & KEY_START; + return aptMainLoop() && !_kill; } void HorizonCtr::ClearViewPort() { diff --git a/tests/gfx/source/os/horizon-nx.cpp b/tests/gfx/source/os/horizon-nx.cpp index 5621517..d552fed 100644 --- a/tests/gfx/source/os/horizon-nx.cpp +++ b/tests/gfx/source/os/horizon-nx.cpp @@ -16,6 +16,8 @@ struct HorizonNX::Impl { void HorizonNX::Init() { if (impl) return; impl = new Impl(); + socketInitializeDefault(); + nxlinkStdio(); romfsInit(); glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);