our_dick/include/egn/font.hpp

163 lines
5.7 KiB
C++

/**
This file is a part of our_dick
Copyright (C) 2022 rexy712
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OUR_DICK_ENGINE_FONT_HPP
#define OUR_DICK_ENGINE_FONT_HPP
#include <freetype2/ft2build.h>
#include FT_FREETYPE_H
#include <map>
#include <optional>
#include <utility> //pair
#include <memory>
#include <algorithm> //min, max
#include "gfx/ogl/texture.hpp"
#include "config.hpp"
namespace egn{
struct font_character{
rml::vec2<size_t> atlas_offset;
rml::vec2i size;
rml::vec2i bearing;
rml::vec2i advance;
rml::vec2f texture_coords[4];
public:
float aspect_ratio(void)const;
};
class font_atlas : public gfx::ogl::texture
{
public:
using map_type = std::map<int, font_character>;
private:
map_type m_atlas_data;
size_t m_requested_width, m_requested_height;
public:
font_atlas(gfx::ogl::texture&& t, map_type&& map, size_t w, size_t h);
font_atlas(const font_atlas&) = default;
font_atlas(font_atlas&&) = default;
~font_atlas(void) = default;
font_atlas& operator=(const font_atlas&) = default;
font_atlas& operator=(font_atlas&&) = default;
map_type& metadata(void);
const map_type& metadata(void)const;
map_type&& release_metadata(void);
font_character& operator[](int character);
rml::vec2<size_t> glyph_size(void)const;
};
class font
{
private:
static inline bool s_initialized = false;
static inline FT_Library s_ft;
private:
FT_Face m_face = nullptr;
int m_depth = 3;
public:
font(const char* file, int depth = 3);
~font(void);
template<class... Ts>
font_atlas generate_atlas(size_t glyph_w, size_t glyph_h, Ts&&... ranges);
std::optional<gfx::ogl::texture> generate_glyph(char character, int width, int height);
private:
using atlas_meta = std::tuple<size_t,size_t,size_t,size_t>;
template<class... Ts>
void generate_atlas_piece_(gfx::ogl::texture& atlas, font_atlas::map_type& metadata, rml::vec2<size_t>& target_pos, const atlas_meta& info, unsigned char* data_buffer, const std::pair<int,int>& range, Ts&&... ranges);
void generate_atlas_piece_impl_(gfx::ogl::texture& atlas, font_atlas::map_type& metadata, rml::vec2<size_t>& target_pos, const atlas_meta& info, unsigned char* data_buffer, const std::pair<int,int>& range);
template<class... Ts>
atlas_meta get_atlas_size_ranges_(Ts&&... ranges);
template<class... Ts>
void get_atlas_size_ranges_recurse_(size_t& avg_w, size_t& avg_h, size_t& mgw, size_t& mgh, size_t& count, const std::pair<int, int>& range, Ts&&... ranges);
void get_atlas_size_ranges_impl_(size_t& avg_w, size_t& avg_h, size_t& mgw, size_t& mgh, size_t& count, const std::pair<int, int>& range);
private:
static bool initialize_freetype_(void);
};
template<class... Ts>
void font::generate_atlas_piece_(gfx::ogl::texture& atlas, font_atlas::map_type& metadata, rml::vec2<size_t>& target_pos, const atlas_meta& info, unsigned char* data_buffer, const std::pair<int,int>& range, Ts&&... ranges){
generate_atlas_piece_impl_(atlas, metadata, target_pos, info, data_buffer, range);
if constexpr(sizeof...(ranges) > 0){
generate_atlas_piece_(atlas, metadata, target_pos, info, data_buffer, std::forward<Ts>(ranges)...);
}
}
template<class... Ts>
font_atlas font::generate_atlas(size_t glyph_w, size_t glyph_h, Ts&&... ranges){
rexy::debug::verbose::print("Generating font atlas for font '%s'\n", m_face->family_name);
FT_Set_Pixel_Sizes(m_face, glyph_w, glyph_h);
auto [atlas_width, atlas_height, max_glyph_width, max_glyph_height] = get_atlas_size_ranges_(std::forward<Ts>(ranges)...);
const auto format = (m_depth == 3) ? GL_RGB : GL_RED;
gfx::ogl::texture atlas(format, atlas_width, atlas_height, GL_UNSIGNED_BYTE, false);
font_atlas::map_type atlas_metadata;
rml::vec2<size_t> target_position = {0, 0};
std::unique_ptr<unsigned char[]> dest_data(new unsigned char[max_glyph_height * max_glyph_width * m_depth]);
generate_atlas_piece_(atlas, atlas_metadata, target_position, std::tuple{atlas_width, atlas_height, max_glyph_width, max_glyph_height}, dest_data.get(), std::forward<Ts>(ranges)...);
return {std::move(atlas), std::move(atlas_metadata), glyph_w, glyph_h};
}
template<class... Ts>
auto font::get_atlas_size_ranges_(Ts&&... ranges) -> atlas_meta{
size_t avg_width = 0;
size_t avg_height = 0;
size_t max_glyph_width = 0;
size_t max_glyph_height = 0;
size_t count = 0;
get_atlas_size_ranges_recurse_(avg_width, avg_height, max_glyph_width, max_glyph_height, count, std::forward<Ts>(ranges)...);
avg_width /= count;
avg_height /= count;
const size_t est_width = avg_width * (std::min(size_t{20}, count) + 1);
const size_t est_height = (max_glyph_height * (std::round(count / 20) + 1));
return {est_width / m_depth, est_height, max_glyph_width / m_depth, max_glyph_height};
}
template<class... Ts>
void font::get_atlas_size_ranges_recurse_(size_t& avg_w, size_t& avg_h, size_t& mgw, size_t& mgh, size_t& count, const std::pair<int, int>& range, Ts&&... ranges){
get_atlas_size_ranges_impl_(avg_w, avg_h, mgw, mgh, count, range);
if constexpr(sizeof...(ranges) > 0){
get_atlas_size_ranges_recurse_(avg_w, avg_h, mgw, mgh, count, std::forward<Ts>(ranges)...);
}
}
}
#endif