From 0553927dadf916dcc2f538c7a7225011153db9a5 Mon Sep 17 00:00:00 2001 From: Simon Hardt Date: Sat, 5 Mar 2022 02:37:08 +0100 Subject: [PATCH] add: Basic Font Rendering --- .gitignore | 1 + CMakeLists.txt | 2 + fontConverter/CMakeLists.txt | 10 +++ fontConverter/src/main.cpp | 114 ++++++++++++++++++++++++++++++ frame/CMakeLists.txt | 21 ++++-- frame/src/Image.cpp | 16 +++++ frame/src/Image.hpp | 7 ++ frame/src/font/Font.cpp | 54 ++++++++++++++ frame/src/font/Font.hpp | 29 ++++++++ frame/src/font/Glyph.cpp | 39 ++++++++++ frame/src/font/Glyph.hpp | 28 ++++++++ frame/src/main.cpp | 5 ++ frame/src/render/RenderTarget.cpp | 40 +++++++++++ frame/src/render/RenderTarget.hpp | 27 +++++-- vcpkg.json | 2 +- 15 files changed, 386 insertions(+), 9 deletions(-) create mode 100644 fontConverter/CMakeLists.txt create mode 100644 fontConverter/src/main.cpp create mode 100644 frame/src/font/Font.cpp create mode 100644 frame/src/font/Font.hpp create mode 100644 frame/src/font/Glyph.cpp create mode 100644 frame/src/font/Glyph.hpp diff --git a/.gitignore b/.gitignore index 54be2b3..380dfe8 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,4 @@ build/* .vscode/* +frame/src/Config.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e714d0..407614d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,7 @@ option(BUILD_EPD ) find_package(fmt CONFIG REQUIRED) +find_package(nlohmann_json CONFIG REQUIRED) option(BUILD_VIRTUAL_DISPLAY "Build virtual sfml based display" @@ -28,6 +29,7 @@ endif (BUILD_EPD) if (BUILD_VIRTUAL_DISPLAY) find_package(SFML COMPONENTS system window graphics CONFIG REQUIRED) + add_subdirectory(fontConverter) endif(BUILD_VIRTUAL_DISPLAY) add_subdirectory(frame) diff --git a/fontConverter/CMakeLists.txt b/fontConverter/CMakeLists.txt new file mode 100644 index 0000000..193a96f --- /dev/null +++ b/fontConverter/CMakeLists.txt @@ -0,0 +1,10 @@ + + +add_executable(font src/main.cpp) +target_link_libraries(font PUBLIC + sfml-system + sfml-window + sfml-graphics + fmt::fmt + nlohmann_json::nlohmann_json +) \ No newline at end of file diff --git a/fontConverter/src/main.cpp b/fontConverter/src/main.cpp new file mode 100644 index 0000000..431178f --- /dev/null +++ b/fontConverter/src/main.cpp @@ -0,0 +1,114 @@ +#include +#include +#include +#include + +using json = nlohmann::json; + +void PrintGlyph(sf::Font const& font, sf::Glyph const& g, unsigned int size); + +int main() +{ + sf::Font font; + + auto ok = + font.loadFromFile(R"(C:\Users\s-har\Downloads\FiraCode-Regular.ttf)"); + + fmt::print("Loaded {}\n", ok); + + std::vector chars = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', + 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', + 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}; + + fmt::print("{}\n", font.getInfo().family); + fmt::print("Line Spacing: {}\n", font.getLineSpacing(14)); + + std::map glyphs; + + for(auto el : chars) + { + auto a = font.getGlyph(el, 14, false); + + fmt::print("{}: w {} h {}\n", + el, + a.textureRect.width, + a.textureRect.height); + + fmt::print("- t: {} l: {} w: {} h: {}\n", + a.bounds.top, + a.bounds.left, + a.bounds.width, + a.bounds.height); + + fmt::print("offset: {}\n\n", a.advance); + + glyphs[el] = a; + } + + auto font_image = font.getTexture(14).copyToImage(); + auto size = font_image.getSize(); + + json export_font; + export_font["name"] = font.getInfo().family; + export_font["sizes"] = json::object(); + + auto& size_14 = export_font["sizes"]["14"]; + + size_14["LineSpacing"] = font.getLineSpacing(14); + auto& Glyphs = size_14["Glyphs"]; + + for(auto&& [c, g] : glyphs) + { + auto& current = Glyphs[std::string{c}]; + + current["advance"] = g.advance; + current["x_offset"] = g.bounds.left; + current["y_offset"] = g.bounds.top; + current["width"] = g.bounds.width; + current["height"] = g.bounds.height; + + std::vector data; + + for(int y = 0; y < (int)g.bounds.height; ++y) + { + for(int x = 0; x < (int)g.bounds.width; ++x) + { + auto c = font_image.getPixel(g.textureRect.left + x, + g.textureRect.top + y); + + data.push_back(c.a > 100 ? 1 : 0); + } + } + + current["data"] = data; + } + + std::fstream out(fmt::format("{}.json", font.getInfo().family), + std::ios::out); + out << export_font.dump(); + + fmt::print("Size x: {} y: {}\n", size.x, size.y); + font_image.saveToFile("Test.png"); +} + +void PrintGlyph(sf::Font const& font, sf::Glyph const& g, unsigned int size) +{ + auto font_image = font.getTexture(size).copyToImage(); + for(int y = 0; y < (int)g.bounds.height; ++y) + { + for(int x = 0; x < (int)g.bounds.width; ++x) + { + auto c = font_image.getPixel(g.textureRect.left + x, + g.textureRect.top + y); + + if(c.a > 100) + { + fmt::print("X"); + } else + { + fmt::print(" "); + } + } + fmt::print("\n"); + } +} \ No newline at end of file diff --git a/frame/CMakeLists.txt b/frame/CMakeLists.txt index 42188fd..ef15bf6 100644 --- a/frame/CMakeLists.txt +++ b/frame/CMakeLists.txt @@ -15,7 +15,10 @@ set(src src/display/Display.cpp src/render/RenderTarget.hpp src/render/RenderTarget.cpp - + src/font/Font.hpp + src/font/Font.cpp + src/font/Glyph.hpp + src/font/Glyph.cpp ) if (BUILD_EPD) @@ -33,12 +36,22 @@ if (BUILD_VIRTUAL_DISPLAY) endif (BUILD_VIRTUAL_DISPLAY) add_executable(${target} ${src}) -target_link_libraries(${target} PRIVATE fmt::fmt) +target_link_libraries(${target} PRIVATE + fmt::fmt +) if (BUILD_EPD) - target_link_libraries(${target} PRIVATE waveshare) + target_link_libraries(${target} PRIVATE + waveshare + ) endif (BUILD_EPD) if (BUILD_VIRTUAL_DISPLAY) - target_link_libraries(${target} PRIVATE sfml-system sfml-window sfml-graphics) + target_link_libraries(${target} PRIVATE + sfml-system + sfml-window + sfml-graphics + fmt::fmt + nlohmann_json::nlohmann_json + ) endif(BUILD_VIRTUAL_DISPLAY) \ No newline at end of file diff --git a/frame/src/Image.cpp b/frame/src/Image.cpp index 96facb1..c23376e 100644 --- a/frame/src/Image.cpp +++ b/frame/src/Image.cpp @@ -17,6 +17,15 @@ namespace frame { } + void Image::Create(uint32_t pWidth, uint32_t pHeight) + { + mWidth = pWidth; + mHeight = pHeight; + + mBuffer.clear(); + mBuffer.resize(mWidth * mHeight); + } + uint8_t& Image::at(uint32_t x, uint32_t y) { return mBuffer.at(toInternal(x, y)); @@ -27,6 +36,13 @@ namespace frame return mBuffer.at(toInternal(x, y)); } + void Image::operator=(Image const& image) + { + mHeight = image.mHeight; + mWidth = image.mWidth; + mBuffer = image.mBuffer; + } + size_t Image::toInternal(uint32_t x, uint32_t y) const { return x + mWidth * y; diff --git a/frame/src/Image.hpp b/frame/src/Image.hpp index af0de2d..82a8642 100644 --- a/frame/src/Image.hpp +++ b/frame/src/Image.hpp @@ -14,8 +14,13 @@ namespace frame std::vector mBuffer; public: + Image() = default; Image(uint32_t pWidth, uint32_t pHeight); Image(Size size); + Image(Image const&) = default; + Image(Image&&) = default; + + void Create(uint32_t pWidth, uint32_t pHeight); uint8_t& at(uint32_t x, uint32_t y); uint8_t const& at(uint32_t x, uint32_t y) const; @@ -29,6 +34,8 @@ namespace frame return mHeight; } + void operator=(Image const& image); + private: size_t toInternal(uint32_t x, uint32_t y) const; }; diff --git a/frame/src/font/Font.cpp b/frame/src/font/Font.cpp new file mode 100644 index 0000000..87e84b2 --- /dev/null +++ b/frame/src/font/Font.cpp @@ -0,0 +1,54 @@ +#include "Font.hpp" + +#include +#include + +using json = nlohmann::json; + +namespace frame::font +{ + + std::shared_ptr Font::LoadFromFile(std::string_view file) + { + try + { + std::fstream file_data(std::string{file}, std::ios::in); + + auto out = std::make_shared(); + out->LoadFromStream(file_data); + return out; + } + catch(std::exception const& e) + { + return nullptr; + } + } + + void Font::LoadFromStream(std::istream& os) + { + json file_data = json::parse(os); + + name = file_data["name"]; + + for(auto& [key, el] : file_data["sizes"].items()) + { + auto size = std::atoi(key.c_str()); + + Glyphs curren_glyphs; + lineSpacing[size] = el["LineSpacing"]; + + for(auto& [glyph_c, glyph_d] : el["Glyphs"].items()) + { + curren_glyphs[glyph_c[0]] = std::move(Glyph{glyph_d}); + } + + sizes[size] = std::move(curren_glyphs); + } + } + + Glyph const& Font::getGlyph(uint32_t size, char c) const + { + return sizes.at(size).at(c); + } + +} // namespace frame::font \ No newline at end of file diff --git a/frame/src/font/Font.hpp b/frame/src/font/Font.hpp new file mode 100644 index 0000000..8f9b488 --- /dev/null +++ b/frame/src/font/Font.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "Glyph.hpp" + +#include +#include +#include + +namespace frame::font +{ + using Glyphs = std::map; + using LineSpacing = std::map; + + class Font + { + std::string name; + std::map sizes; + LineSpacing lineSpacing; + + public: + Font() = default; + static std::shared_ptr LoadFromFile(std::string_view file); + + Glyph const& getGlyph(uint32_t size, char c) const; + + protected: + void LoadFromStream(std::istream& os); + }; +} // namespace frame::font diff --git a/frame/src/font/Glyph.cpp b/frame/src/font/Glyph.cpp new file mode 100644 index 0000000..32c436d --- /dev/null +++ b/frame/src/font/Glyph.cpp @@ -0,0 +1,39 @@ +#include "Glyph.hpp" + +namespace frame::font +{ + Glyph::Glyph(json const& data) + { + height = data["height"]; + width = data["width"]; + x_offset = data["x_offset"]; + y_offset = data["y_offset"]; + advance = data["advance"]; + + image.Create(width, height); + + auto& image_data = data["data"]; + + int index = 0; + for(auto y = 0; y < height; ++y) + { + for(auto x = 0; x < width; ++x) + { + uint8_t i = image_data[index]; + image.at(x, y) = i * 0xFF; + ++index; + } + } + } + + void Glyph::operator=(Glyph const& glyph) + { + image = glyph.image; + height = glyph.height; + width = glyph.width; + x_offset = glyph.x_offset; + y_offset = glyph.y_offset; + advance = glyph.advance; + } + +} // namespace frame::font \ No newline at end of file diff --git a/frame/src/font/Glyph.hpp b/frame/src/font/Glyph.hpp new file mode 100644 index 0000000..39c2b3e --- /dev/null +++ b/frame/src/font/Glyph.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "../Image.hpp" + +#include + +using json = nlohmann::json; + +namespace frame::font +{ + struct Glyph + { + Image image; + uint8_t height; + uint8_t width; + int8_t x_offset; + int8_t y_offset; + int8_t advance; + + public: + Glyph() = default; + Glyph(json const& data); + Glyph(Glyph const&) = default; + Glyph(Glyph&&) = default; + + void operator=(Glyph const& glyph); + }; +} // namespace frame::font \ No newline at end of file diff --git a/frame/src/main.cpp b/frame/src/main.cpp index 8618ddf..2ebfa6b 100644 --- a/frame/src/main.cpp +++ b/frame/src/main.cpp @@ -7,12 +7,15 @@ using namespace std::chrono_literals; #include "display/Display.hpp" +#include "font/Font.hpp" #include "render/RenderTarget.hpp" int main() { constexpr double pi = 3.14159; + auto font = frame::font::Font::LoadFromFile("Fira Code.json"); + auto display = frame::display::Create(); if(!display) @@ -62,6 +65,8 @@ int main() {110, (int)display->getSize().height - 10}, 5); + target.DrawText("abcdefghijklmnopqrstuvwxyz", {100, 50}, *font, 14); + display->Display(target.getImage()); std::this_thread::sleep_for(10s); diff --git a/frame/src/render/RenderTarget.cpp b/frame/src/render/RenderTarget.cpp index 627a986..329af6f 100644 --- a/frame/src/render/RenderTarget.cpp +++ b/frame/src/render/RenderTarget.cpp @@ -142,6 +142,46 @@ namespace frame::render {BottomRight.x, BottomRight.y - lw}); } + void RenderTarget::DrawImage(Vector topLeft, Image const& image) + { + auto pos = topLeft; + + for(auto y = 0; y < image.getHeight(); ++y) + { + for(auto x = 0; x < image.getWidth(); ++x) + { + DrawPixel({pos.x + x, pos.y + y}, (Color)image.at(x, y)); + } + } + } + + Vector RenderTarget::DrawGlyph(Vector centerLine, + font::Glyph const& g, + Color color) + { + auto const& height = g.height; + + Vector topleft = centerLine; + topleft.x += g.x_offset; + topleft.y += g.y_offset; + + DrawImage(topleft, g.image); + centerLine.x += g.advance; + return centerLine; + } + + void RenderTarget::DrawText(std::string_view pText, + Vector pCenterLineStart, + font::Font const& pFont, + uint8_t size) + { + auto pos = pCenterLineStart; + for(auto c : pText) + { + pos = DrawGlyph(pos, pFont.getGlyph(size, c)); + } + } + void RenderTarget::DrawPointsMirrorCircle(Vector const& center, Vector const& pos, Color color) diff --git a/frame/src/render/RenderTarget.hpp b/frame/src/render/RenderTarget.hpp index 5a47dad..1d783e0 100644 --- a/frame/src/render/RenderTarget.hpp +++ b/frame/src/render/RenderTarget.hpp @@ -3,6 +3,7 @@ #include "../Color.hpp" #include "../Image.hpp" #include "../Vector.hpp" +#include "../font/Font.hpp" namespace frame::render { @@ -24,17 +25,35 @@ namespace frame::render int32_t radius, Color color = BLACK); - void DrawRectFilled(Vector const& TopLeft, Vector const& BottomRight, Color color = BLACK); + void DrawRectFilled(Vector const& TopLeft, + Vector const& BottomRight, + Color color = BLACK); - void DrawRect(Vector const& TopLeft, Vector const& BottomRight, uint8_t LineWidth, Color color = BLACK); + void DrawRect(Vector const& TopLeft, + Vector const& BottomRight, + uint8_t LineWidth, + Color color = BLACK); + + void DrawImage(Vector topLeft, Image const& image); + + Vector DrawGlyph(Vector centerLine, + font::Glyph const& g, + Color color = BLACK); + + void DrawText(std::string_view pText, + Vector pCenterLineStart, + font::Font const& pFont, + uint8_t size); Image const& getImage() { return image; } - private: - void DrawPointsMirrorCircle(Vector const& center, Vector const& pos, Color color); + private: + void DrawPointsMirrorCircle(Vector const& center, + Vector const& pos, + Color color); }; } // namespace frame::render diff --git a/vcpkg.json b/vcpkg.json index c937e32..02c302e 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,7 +1,7 @@ { "name": "frame", "version": "1.0", - "dependencies": ["fmt"], + "dependencies": ["fmt", "nlohmann-json"], "features": { "virtual": { "description": "Build Virtual Display",