Support freetype font fallbacks by storing a sequence of fonts

This commit is contained in:
Nikita Lisitsa 2025-04-13 12:44:44 +03:00
parent e4222f35ff
commit 6a8563cce9
2 changed files with 69 additions and 27 deletions

View file

@ -92,4 +92,7 @@ namespace psemek::fonts
std::unique_ptr<font_builder> load_freetype_font(wgpu::device device, std::filesystem::path const & path);
// Font paths are specified in order
std::unique_ptr<font_builder> load_combined_freetype_font(wgpu::device device, std::vector<std::filesystem::path> const & paths);
}

View file

@ -71,13 +71,20 @@ namespace psemek::fonts
struct face_shared
{
util::blob data;
wgpu::device device;
FT_Face face;
struct face_data
{
util::blob data;
FT_Face face;
};
std::vector<face_data> faces = {};
~face_shared()
{
FT_Done_Face(face);
for (auto const & face : faces)
FT_Done_Face(face.face);
}
};
@ -89,6 +96,9 @@ namespace psemek::fonts
, type_(type)
, size_(size)
{
for (auto const & face : state_->faces)
ft_check_result(FT_Set_Char_Size(face.face, 0, size_ << 6, 0, 0), "Failed to select freetype face size: ");
font::shape("x", {});
xheight_ = -shaped_text_[0].position[1].min;
shaped_text_.clear();
@ -101,7 +111,7 @@ namespace psemek::fonts
std::string_view name() const override
{
return FT_Get_Postscript_Name(state_->face);
return FT_Get_Postscript_Name(state_->faces[0].face);
}
math::vector<int, 2> size() const override
@ -148,6 +158,12 @@ namespace psemek::fonts
std::vector<page> pages_;
struct glyph_mapping_data
{
std::uint32_t font_index;
std::uint32_t glyph_index;
};
struct glyph_data
{
int page;
@ -156,8 +172,8 @@ namespace psemek::fonts
math::vector<int, 2> advance;
};
util::hash_map<char32_t, std::uint32_t> glyph_mapping_;
util::hash_map<std::uint32_t, glyph_data> glyphs_;
util::hash_map<char32_t, glyph_mapping_data> glyph_mapping_;
util::hash_map<std::uint32_t, util::hash_map<std::uint32_t, glyph_data>> glyphs_;
std::vector<shaped_glyph> shaped_text_;
@ -168,40 +184,53 @@ namespace psemek::fonts
shaped_text_.clear();
auto face = state_->face;
ft_check_result(FT_Set_Char_Size(face, 0, size_ << 6, 0, 0), "Failed to select freetype face size: ");
bool need_update_pages = false;
for (char32_t ch : string)
{
std::uint32_t glyph_id;
glyph_mapping_data mapping{0, 0};
if (auto it = glyph_mapping_.find(ch); it != glyph_mapping_.end())
{
glyph_id = it->second;
mapping = it->second;
}
else
{
glyph_id = FT_Get_Char_Index(face, ch);
if (glyph_id == 0)
glyph_id = FT_Get_Char_Index(face, '?');
int glyph_index = 0;
for (int font_index = 0; font_index < state_->faces.size(); ++font_index)
{
glyph_index = FT_Get_Char_Index(state_->faces[font_index].face, ch);
if (glyph_index != 0)
{
mapping = {font_index, glyph_index};
break;
}
}
glyph_mapping_[ch] = glyph_id;
if (glyph_index == 0)
{
mapping.font_index = 0;
mapping.glyph_index = FT_Get_Char_Index(state_->faces[0].face, '?');
}
glyph_mapping_[ch] = mapping;
}
auto face = state_->faces[mapping.font_index].face;
glyph_data * data = nullptr;
if (auto it = glyphs_.find(glyph_id); it != glyphs_.end())
auto & glyphs = glyphs_[mapping.font_index];
if (auto it = glyphs.find(mapping.glyph_index); it != glyphs.end())
{
data = &(it->second);
}
else
{
data = &glyphs_[glyph_id];
data = &glyphs[mapping.glyph_index];
FT_Load_Glyph(face, glyph_id, type_ == font_type::bitmap ? FT_LOAD_TARGET_LIGHT : FT_LOAD_DEFAULT);
FT_Load_Glyph(face, mapping.glyph_index, type_ == font_type::bitmap ? FT_LOAD_TARGET_LIGHT : FT_LOAD_DEFAULT);
FT_Render_Glyph(face->glyph, type_ == font_type::bitmap ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_SDF);
if (face->glyph->bitmap.width + 2 * padding > page_size || face->glyph->bitmap.rows + 2 * padding > page_size)
@ -210,7 +239,7 @@ namespace psemek::fonts
data->part = {{{0, 0}, {0, 0}}};
data->offset = {0, 0};
data->advance = {size_, 0};
log::warning() << "Glyph with ID " << glyph_id << " (U+" << std::hex << (int)ch << ") is larger than font atlas page: " << face->glyph->bitmap.width << "x" << face->glyph->bitmap.rows;
log::warning() << "Glyph with ID " << mapping.glyph_index << " (U+" << std::hex << (int)ch << ") is larger than font atlas page: " << face->glyph->bitmap.width << "x" << face->glyph->bitmap.rows;
}
else
{
@ -319,7 +348,7 @@ namespace psemek::fonts
std::string_view name() const override
{
return FT_Get_Postscript_Name(state_->face);
return FT_Get_Postscript_Name(state_->faces[0].face);
}
std::unique_ptr<font> create(font_type type, int size) override
@ -337,16 +366,26 @@ namespace psemek::fonts
std::unique_ptr<font_builder> load_freetype_font(wgpu::device device, std::filesystem::path const & path)
{
auto font_data = io::read_full(io::file_istream{path});
return load_combined_freetype_font(device, {path});
}
FT_Face face;
ft_check_result(FT_New_Memory_Face(ft_library(), reinterpret_cast<FT_Byte const *>(font_data.data()), font_data.size(), 0, &face), util::to_string("Failed to load font ", path, ": "));
std::unique_ptr<font_builder> load_combined_freetype_font(wgpu::device device, std::vector<std::filesystem::path> const & paths)
{
auto result = std::make_shared<face_shared>(device);
auto result = std::make_unique<freetype_font_builder>(std::make_shared<face_shared>(std::move(font_data), device, face));
for (auto const & path : paths)
{
auto font_data = io::read_full(io::file_istream{path});
log::debug() << "Loaded font " << result->name() << " (" << path << ")";
FT_Face face = nullptr;
ft_check_result(FT_New_Memory_Face(ft_library(), reinterpret_cast<FT_Byte const *>(font_data.data()), font_data.size(), 0, &face), util::to_string("Failed to load font ", path, ": "));
return result;
result->faces.push_back({std::move(font_data), face});
log::debug() << "Loaded font " << FT_Get_Postscript_Name(face) << " (" << path << ")";
}
return std::make_unique<freetype_font_builder>(std::move(result));
}
}