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); 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 struct face_shared
{ {
util::blob data;
wgpu::device device; wgpu::device device;
FT_Face face;
struct face_data
{
util::blob data;
FT_Face face;
};
std::vector<face_data> faces = {};
~face_shared() ~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) , type_(type)
, size_(size) , 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", {}); font::shape("x", {});
xheight_ = -shaped_text_[0].position[1].min; xheight_ = -shaped_text_[0].position[1].min;
shaped_text_.clear(); shaped_text_.clear();
@ -101,7 +111,7 @@ namespace psemek::fonts
std::string_view name() const override 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 math::vector<int, 2> size() const override
@ -148,6 +158,12 @@ namespace psemek::fonts
std::vector<page> pages_; std::vector<page> pages_;
struct glyph_mapping_data
{
std::uint32_t font_index;
std::uint32_t glyph_index;
};
struct glyph_data struct glyph_data
{ {
int page; int page;
@ -156,8 +172,8 @@ namespace psemek::fonts
math::vector<int, 2> advance; math::vector<int, 2> advance;
}; };
util::hash_map<char32_t, std::uint32_t> glyph_mapping_; util::hash_map<char32_t, glyph_mapping_data> glyph_mapping_;
util::hash_map<std::uint32_t, glyph_data> glyphs_; util::hash_map<std::uint32_t, util::hash_map<std::uint32_t, glyph_data>> glyphs_;
std::vector<shaped_glyph> shaped_text_; std::vector<shaped_glyph> shaped_text_;
@ -168,40 +184,53 @@ namespace psemek::fonts
shaped_text_.clear(); 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; bool need_update_pages = false;
for (char32_t ch : string) 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()) if (auto it = glyph_mapping_.find(ch); it != glyph_mapping_.end())
{ {
glyph_id = it->second; mapping = it->second;
} }
else else
{ {
glyph_id = FT_Get_Char_Index(face, ch); int glyph_index = 0;
if (glyph_id == 0) for (int font_index = 0; font_index < state_->faces.size(); ++font_index)
glyph_id = FT_Get_Char_Index(face, '?'); {
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; 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); data = &(it->second);
} }
else 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); 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) 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->part = {{{0, 0}, {0, 0}}};
data->offset = {0, 0}; data->offset = {0, 0};
data->advance = {size_, 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 else
{ {
@ -319,7 +348,7 @@ namespace psemek::fonts
std::string_view name() const override 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 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) 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; std::unique_ptr<font_builder> load_combined_freetype_font(wgpu::device device, std::vector<std::filesystem::path> const & paths)
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, ": ")); {
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));
} }
} }