#pragma once /* * Copyright (C) 2024 Brett Terpstra * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef BLT_WITH_GRAPHICS_FONT_RENDERER_H #define BLT_WITH_GRAPHICS_FONT_RENDERER_H #include #include #include #include #include #include #include namespace blt::gfx { class font_texture_atlas : public texture_gl2D { public: struct bounded_t { font::glyph_t glyph; // starting point inside texture atlas blt::vec2ui min; // ending point of the glyph, inside texture atlas blt::vec2ui max; }; struct added_font_results_t { blt::u64 last_inserted_index; blt::u64 min_index_used; blt::u64 max_index_used; }; public: explicit font_texture_atlas(blt::i32 dimensions, blt::u32 padding); added_font_results_t add_font(const font::font_file_t& file, float size); added_font_results_t add_font(const font::font_file_t& file, blt::u64 start, float size); bounded_t& get_glyph(blt::u64 c, std::string_view font) { return glyphs.at(font).at(c); } [[nodiscard]] const bounded_t& get_glyph(blt::u64 c, std::string_view font) const { return glyphs.at(font).at(c); } private: blt::hashmap_t> glyphs; blt::u32 used_width = 0; blt::u32 used_height = 0; blt::u32 padding; }; class font_generator_t { public: struct size_bounds_t { float min_size, max_size; blt::u64 min_char, max_char; }; struct bounded_font_t { font_texture_atlas* atlas; blt::u64 min_char, max_char; bounded_font_t(font_texture_atlas* atlas, u64 minChar, u64 maxChar): atlas(atlas), min_char(minChar), max_char(maxChar) {} // replace with font -> char range. use scaling. }; public: explicit font_generator_t(blt::i32 dimensions, blt::u32 padding, float gen_size); void add_font(const font::font_file_t& file); inline font_texture_atlas& get_texture(const std::string& font, blt::u64 c) { for (auto& atlas : font_associations[font]) { if (c >= atlas.min_char && c <= atlas.max_char) return *atlas.atlas; } throw std::runtime_error("Character '" + std::to_string(c) + "' not found inside atlases with font '" + font + "'"); } [[nodiscard]] blt::i32 get_dimensions() const { return dimensions; } [[nodiscard]] float get_generated_size() const { return gen_size; } private: blt::i32 dimensions; blt::u32 padding; std::vector> atlases; blt::hashmap_t> font_associations; float gen_size; }; class font_renderer_t { public: static constexpr blt::vec2f DEFAULT_SCALE{1, 1}; static constexpr blt::vec4 DEFAULT_COLOR = blt::make_color(0.8, 0.8, 0.8); struct compiled_text_t; struct render_context_t { blt::vec2f position, bounds; blt::vec4 color = DEFAULT_COLOR; float z_index = 0; float current_size = 0; float rotation = 0; compiled_text_t* text_ptr; explicit render_context_t(float current_size, compiled_text_t* associated_text): current_size(current_size), text_ptr(associated_text) {} [[nodiscard]] compiled_text_t& getAssociatedText() const { return *text_ptr; } render_context_t& setPosition(float x, float y) { position = {x, y}; return *this; } render_context_t& setPosition(const blt::vec2f& new_position) { position = new_position; return *this; } render_context_t& setRotation(float r) { rotation = r; return *this; } render_context_t& setColor(const blt::vec4f& new_color) { color = new_color; return *this; } render_context_t& setColor(float r, float g, float b, float a = 1) { color = {r, g, b, a}; return *this; } render_context_t& setBounds(const blt::vec2f& new_bounds) { bounds = new_bounds; return *this; } render_context_t& setBounds(float width, float height) { return setBounds({width, height}); } render_context_t& setUnrestrictedBounds() { bounds = {}; return *this; } render_context_t& setSize(const float new_scale) { current_size = new_scale; return *this; } render_context_t& setZIndex(float new_z_index) { z_index = new_z_index; return *this; } [[nodiscard]] const vec2f& getPosition() const { return position; } [[nodiscard]] const vec2f& getBounds() const { return bounds; } [[nodiscard]] float getSize() const { return current_size; } [[nodiscard]] float getRotation() const { return rotation; } [[nodiscard]] const vec4& getColor() const { return color; } }; struct text_index_t { std::string text; std::string font; bool operator==(const text_index_t& rhs) const; bool operator!=(const text_index_t& rhs) const; }; struct compiled_text_t { friend font_renderer_t; public: compiled_text_t& setText(std::string_view str, float size, std::string_view font = "__default"); compiled_text_t& recompile(); compiled_text_t& setPosition(float x, float y) { position = {x, y}; return *this; } compiled_text_t& setPosition(const blt::vec2f& pos) { position = pos; return *this; } compiled_text_t& setRotation(float r) { rotation = r; return *this; } compiled_text_t& setBounds(const blt::vec2f& limit) { if (limit == bounds) return *this; bounds = limit; recompile(); return *this; } compiled_text_t& setBounds(float width, float height) { return setBounds({width, height}); } compiled_text_t& setUnrestrictedBounds() { bounds = {}; return *this; } compiled_text_t& setColor(const blt::vec4f& c) { color = c; return *this; } compiled_text_t& setColor(float r, float g, float b, float a = 1) { color = {r, g, b, a}; return *this; } compiled_text_t& setSize(float size) { if (current_size == size) return *this; current_size = size; scale = {size / generator.get_generated_size(), size / generator.get_generated_size()}; // size only changes computation if there are bounds on the text. if (bounds.x() != 0 || bounds.y() != 0) recompile(); return *this; } compiled_text_t& setZIndex(float s) { z_index = s; return *this; } [[nodiscard]] const vec2f& getBounds() const { return bounds; } [[nodiscard]] const vec2f& getPosition() const { return position; } [[nodiscard]] const vec2f& getScale() const { return scale; } [[nodiscard]] const vec4f& getColor() const { return color; } [[nodiscard]] float getZIndex() const { return z_index; } [[nodiscard]] float getRotation() const { return rotation; } [[nodiscard]] float getTextWidth() const { return text_width; } [[nodiscard]] float getTextHeight() const { return text_height; } void save_to(render_context_t& save) { save.position = position; save.bounds = bounds; save.color = color; save.z_index = z_index; save.current_size = current_size; save.rotation = rotation; } void restore_from(const render_context_t& restore) { setPosition(restore.position); setBounds(restore.bounds); setColor(restore.color); setZIndex(restore.z_index); setSize(restore.current_size); setRotation(restore.rotation); } private: explicit compiled_text_t(font_generator_t& generator); void bind() const { vao.bind(); } void draw(); struct text_render_info_t { font_texture_atlas* texture = nullptr; blt::size_t render_start = 0; blt::size_t render_count = 0; text_render_info_t(font_texture_atlas* texture, size_t renderStart, size_t renderCount): texture(texture), render_start(renderStart), render_count(renderCount) {} }; font_generator_t& generator; std::string contents, font; vertex_array_t vao{}; std::vector renders; blt::vec2f position, bounds; blt::vec2f scale = DEFAULT_SCALE; blt::vec4 color = DEFAULT_COLOR; float z_index = 0; float current_size = 0; float rotation = 0; float text_width = 0; float text_height = 0; }; public: explicit font_renderer_t(); void create_default(float gen_size = 250, blt::i32 dimensions = 0); void create(float gen_size, blt::i32 dimensions = 0); void cleanup(); void add_font(const font::font_file_t& file) { generator->add_font(file); } void add_default_font(std::string_view path, std::string_view name = "__default"); void add_default_font(const blt::u8* data, blt::size_t size, bool compressed = false, std::string_view name = "__default"); compiled_text_t* create_text(std::string_view str, float size, std::string_view font = "__default"); render_context_t& render_text(std::string_view str, float size, std::string_view font = "__default"); void destroy_text(compiled_text_t* text); void render(); private: std::unique_ptr allocate_text(); std::unique_ptr generator; std::unique_ptr font_shader; blt::hashmap_t rendered_strings; blt::hashmap_t text_ptr_to_text_index; blt::hashmap_t> render_list; blt::hashset_t last_rendered_strings; blt::hashset_t currently_rendered_strings; blt::hashmap_t text_ptr_to_index; std::vector> added_texts; std::vector> cached_deallocated_text_objects; }; } namespace blt::gfx::detail { inline void hash_combine(std::size_t& seed, const std::size_t& value) { seed ^= value + 0x9e3779b9 + (seed << 6) + (seed >> 2); } } // Specialization of std::hash for text_index_t template<> struct std::hash { std::size_t operator()(const blt::gfx::font_renderer_t::text_index_t& key) const { blt::size_t hash = 0; blt::gfx::detail::hash_combine(hash, std::hash{}(key.text)); blt::gfx::detail::hash_combine(hash, std::hash{}(key.font)); return hash; } }; #endif //BLT_WITH_GRAPHICS_FONT_RENDERER_H