diff --git a/libs/fonts/include/psemek/fonts/font_v2.hpp b/libs/fonts/include/psemek/fonts/font_v2.hpp index ed2976b8..53639b11 100644 --- a/libs/fonts/include/psemek/fonts/font_v2.hpp +++ b/libs/fonts/include/psemek/fonts/font_v2.hpp @@ -92,4 +92,7 @@ namespace psemek::fonts std::unique_ptr load_freetype_font(wgpu::device device, std::filesystem::path const & path); + // Font paths are specified in order + std::unique_ptr load_combined_freetype_font(wgpu::device device, std::vector const & paths); + } diff --git a/libs/fonts/source/freetype.cpp b/libs/fonts/source/freetype.cpp index 373636fe..30795f96 100644 --- a/libs/fonts/source/freetype.cpp +++ b/libs/fonts/source/freetype.cpp @@ -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 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 size() const override @@ -148,6 +158,12 @@ namespace psemek::fonts std::vector 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 advance; }; - util::hash_map glyph_mapping_; - util::hash_map glyphs_; + util::hash_map glyph_mapping_; + util::hash_map> glyphs_; std::vector 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 create(font_type type, int size) override @@ -337,16 +366,26 @@ namespace psemek::fonts std::unique_ptr 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(font_data.data()), font_data.size(), 0, &face), util::to_string("Failed to load font ", path, ": ")); + std::unique_ptr load_combined_freetype_font(wgpu::device device, std::vector const & paths) + { + auto result = std::make_shared(device); - auto result = std::make_unique(std::make_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(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(std::move(result)); } }