diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index 689e61aa2e..69d3cf460d 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -22,10 +22,10 @@ class Private public: Private() = delete; - static std::optional load_font_info(const Emboss::Font &font); + static std::optional load_font_info(const Emboss::FontFile &font); static std::optional load_font_info(const unsigned char *data, unsigned int index = 0); static std::optional get_glyph(stbtt_fontinfo &font_info, int unicode_letter, float flatness); - static std::optional get_glyph(int unicode, const Emboss::Font &font, const FontProp &font_prop, + static std::optional get_glyph(int unicode, const Emboss::FontFile &font, const FontProp &font_prop, Emboss::Glyphs &cache, std::optional &font_info_opt); static FontItem create_font_item(std::wstring name, std::wstring path); @@ -42,7 +42,7 @@ public: static Point to_point(const stbtt__point &point); }; -std::optional Private::load_font_info(const Emboss::Font &font) +std::optional Private::load_font_info(const Emboss::FontFile &font) { return load_font_info(font.buffer.data(), font.index); } @@ -125,7 +125,7 @@ std::optional Private::get_glyph(stbtt_fontinfo &font_info, int u std::optional Private::get_glyph( int unicode, - const Emboss::Font & font, + const Emboss::FontFile & font, const FontProp & font_prop, Emboss::Glyphs & cache, std::optional &font_info_opt) @@ -465,7 +465,8 @@ std::optional Emboss::get_font_path(const std::wstring &font_face_ } #endif -std::unique_ptr Emboss::load_font(std::vector&& data) +std::unique_ptr Emboss::load_font( + std::vector &&data) { unsigned int collection_size = 0; int font_offset = 0; @@ -485,11 +486,11 @@ std::unique_ptr Emboss::load_font(std::vector&& dat // load information about line gap int ascent, descent, linegap; stbtt_GetFontVMetrics(info, &ascent, &descent, &linegap); - return std::make_unique( + return std::make_unique( std::move(data), collection_size, ascent, descent, linegap); } -std::unique_ptr Emboss::load_font(const char *file_path) +std::unique_ptr Emboss::load_font(const char *file_path) { FILE *file = fopen(file_path, "rb"); if (file == nullptr) { @@ -520,7 +521,7 @@ std::unique_ptr Emboss::load_font(const char *file_path) #ifdef _WIN32 -std::unique_ptr Emboss::load_font(HFONT hfont) +std::unique_ptr Emboss::load_font(HFONT hfont) { HDC hdc = ::CreateCompatibleDC(NULL); if (hdc == NULL) { @@ -559,16 +560,16 @@ std::unique_ptr Emboss::load_font(HFONT hfont) } #endif // _WIN32 -std::optional Emboss::letter2glyph(const Font &font, - int letter, - float flatness) +std::optional Emboss::letter2glyph(const FontFile &font, + int letter, + float flatness) { auto font_info_opt = Private::load_font_info(font); if (!font_info_opt.has_value()) return {}; return Private::get_glyph(*font_info_opt, letter, flatness); } -ExPolygons Emboss::text2shapes(Font & font, +ExPolygons Emboss::text2shapes(FontFile & font, const char * text, const FontProp &font_prop) { @@ -614,7 +615,7 @@ ExPolygons Emboss::text2shapes(Font & font, return Private::dilate_to_unique_points(result); } -bool Emboss::is_italic(Font &font) { +bool Emboss::is_italic(FontFile &font) { std::optional font_info_opt = Private::load_font_info(font); diff --git a/src/libslic3r/Emboss.hpp b/src/libslic3r/Emboss.hpp index 0b03846f15..f4d30ba7a4 100644 --- a/src/libslic3r/Emboss.hpp +++ b/src/libslic3r/Emboss.hpp @@ -52,11 +52,12 @@ public: using Glyphs = std::map; /// - /// keep information from file about font + /// keep information from file about font + /// (store file data itself) + /// + cache data readed from buffer /// + cache shape of glyphs (optionaly modified) - /// + user defined modification of font /// - struct Font + struct FontFile { // loaded data from font file const std::vector buffer; @@ -67,50 +68,34 @@ public: // vertical position is "scale*(ascent - descent + lineGap)" const int ascent, descent, linegap; - // user defined font modification - // + emboss parameter - FontProp prop; - Emboss::Glyphs cache; // cache of glyphs - Font(std::vector &&buffer, - unsigned int count, - int ascent, - int descent, - int linegap) + FontFile(std::vector &&buffer, + unsigned int count, + int ascent, + int descent, + int linegap) : buffer(std::move(buffer)) , index(0) // select default font on index 0 , count(count) , ascent(ascent) , descent(descent) , linegap(linegap) - , prop(7.f, 1.f) {} }; - - struct UserFont - { - // description of file - Font file_font; - // user defined font modification - FontProp prop; - // cache of glyphs - Emboss::Glyphs cache; - }; - /// /// Load font file into buffer /// /// Location of .ttf or .ttc font file /// Font object when loaded. - static std::unique_ptr load_font(const char *file_path); + static std::unique_ptr load_font(const char *file_path); // data = raw file data - static std::unique_ptr load_font(std::vector&& data); + static std::unique_ptr load_font(std::vector&& data); #ifdef _WIN32 // fix for unknown pointer HFONT using HFONT = void*; - static std::unique_ptr load_font(HFONT hfont); + static std::unique_ptr load_font(HFONT hfont); #endif // _WIN32 /// @@ -120,7 +105,7 @@ public: /// One character defined by unicode codepoint /// Precision of lettter outline curve in conversion to lines /// inner polygon cw(outer ccw) - static std::optional letter2glyph(const Font &font, int letter, float flatness); + static std::optional letter2glyph(const FontFile &font, int letter, float flatness); /// /// Convert text into polygons @@ -129,7 +114,7 @@ public: /// Characters to convert /// User defined property of the font /// Inner polygon cw(outer ccw) - static ExPolygons text2shapes(Font & font, + static ExPolygons text2shapes(FontFile & font, const char * text, const FontProp &font_prop); @@ -139,7 +124,7 @@ public: /// /// Selector of font /// True when the font description contains italic/obligue otherwise False - static bool is_italic(Font &font); + static bool is_italic(FontFile &font); /// /// Project 2d point into space diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index e8b08ed94b..494c8befec 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -873,6 +873,11 @@ private: this->calculate_convex_hull(); } else m_convex_hull.reset(); + //TextConfiguration tc; + //cereal::load_by_value(ar, tc); + //if (tc.font_item.type != FontItem::Type::undefined) { + // text_configuration = tc; + //} } template void save(Archive &ar) const { bool has_convex_hull = m_convex_hull.get() != nullptr; @@ -883,6 +888,8 @@ private: cereal::save_by_value(ar, config); if (has_convex_hull) cereal::save_optional(ar, m_convex_hull); + //if (text_configuration.has_value()) + // cereal::save_by_value(ar, *text_configuration); } }; diff --git a/src/libslic3r/TextConfiguration.hpp b/src/libslic3r/TextConfiguration.hpp index 9879e61575..0e0a2d6e36 100644 --- a/src/libslic3r/TextConfiguration.hpp +++ b/src/libslic3r/TextConfiguration.hpp @@ -44,6 +44,30 @@ struct FontProp FontProp(float line_height = 10.f, float depth = 2.f) : emboss(depth), size_in_mm(line_height) {} + + bool operator==(const FontProp& other) const { + auto is_equal = [](const float &v1, const float &v2) { + return fabs(v1 - v2) < std::numeric_limits::epsilon(); + }; + auto is_equal_ = [&is_equal](const std::optional &v1, + const std::optional &v2) { + return (!v1.has_value() && !v2.has_value()) || + (v1.has_value() && v2.has_value() && is_equal(*v1, *v2)); + }; + return + char_gap == other.char_gap && + line_gap == other.line_gap && + is_equal(emboss, other.emboss) && + is_equal(size_in_mm, other.size_in_mm) && + is_equal_(boldness, other.boldness) && + is_equal_(skew, other.skew); + } + + // undo / redo stack recovery + template void serialize(Archive &ar) + { + ar(char_gap, line_gap, emboss, boldness, skew, size_in_mm, family, face_name, style, weight); + } }; // represent selected font @@ -78,6 +102,12 @@ struct FontItem wx_lin_font_descr, // path is font descriptor generated by wxWidgets on windows wx_mac_font_descr // path is font descriptor generated by wxWidgets on windows }; + + // undo / redo stack recovery + template void serialize(Archive &ar) + { + ar(name, path, (int) type, prop); + } }; // Font item name inside list is unique @@ -99,6 +129,9 @@ struct TextConfiguration TextConfiguration(const FontItem &font_item, const std::string &text) : font_item(font_item), text(text) {} + + // undo / redo stack recovery + template void serialize(Archive &ar){ ar(text, font_item); } }; } // namespace Slic3r diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 96a09d175f..35e5000663 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -234,6 +234,10 @@ set(SLIC3R_GUI_SOURCES Utils/FlashAir.hpp Utils/FontConfigHelp.cpp Utils/FontConfigHelp.hpp + Utils/FontListSerializable.cpp + Utils/FontListSerializable.hpp + Utils/FontManager.cpp + Utils/FontManager.hpp Utils/AstroBox.cpp Utils/AstroBox.hpp Utils/Repetier.cpp diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index e411722dfb..3fc8d09b31 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -12,6 +12,7 @@ #include "slic3r/GUI/Jobs/EmbossJob.hpp" #include "slic3r/GUI/Jobs/NotificationProgressIndicator.hpp" #include "slic3r/Utils/WxFontUtils.hpp" +#include "slic3r/Utils/FontListSerializable.hpp" // TODO: remove include #include "libslic3r/SVG.hpp" // debug store @@ -36,19 +37,16 @@ // uncomment for easier debug //#define ALLOW_DEBUG_MODE - - using namespace Slic3r; using namespace Slic3r::GUI; GLGizmoEmboss::GLGizmoEmboss(GLCanvas3D &parent) : GLGizmoBase(parent, M_ICON_FILENAME, -2) - , m_font_selected(0) - , m_font(nullptr) , m_volume(nullptr) , m_exist_notification(false) , m_is_initialized(false) // initialize on first opening gizmo , m_rotate_gizmo(parent, GLGizmoRotate::Axis::Z) // grab id = 2 (Z axis) + , m_font_manager(m_imgui->get_glyph_ranges()) { m_rotate_gizmo.set_group_id(0); // TODO: add suggestion to use https://fontawesome.com/ @@ -118,7 +116,7 @@ void GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mous const Selection &selection = m_parent.get_selection(); if(selection.is_empty()) return; - set_default_configuration(); + set_default_text(); // By position of cursor create transformation to put text on surface of model Transform3d transformation; @@ -355,7 +353,7 @@ void GLGizmoEmboss::on_set_state() bool create_new_object = selection.is_empty(); // When add Text on empty plate, Create new object with volume if (create_new_object) { - set_default_configuration(); + set_default_text(); create_emboss_object(create_mesh(), create_volume_name(), create_configuration()); // gizmo will open when successfuly create new object @@ -428,26 +426,26 @@ void GLGizmoEmboss::initialize() m_gui_cfg->minimal_window_size_with_advance = ImVec2(window_width, window_height + advance_height); - // TODO: What to do when icon was NOT loaded? + // TODO: What to do when icon was NOT loaded? Generate them? bool success = init_icons(); assert(success); - load_font_list_from_app_config(); - // try to load valid font - m_font_selected = 0; - bool is_font_loaded = load_font(); - while (!is_font_loaded && !m_font_list.empty()) { - // can't load so erase it from list - m_font_list.erase(m_font_list.begin()); - is_font_loaded = load_font(); + const AppConfig *app_cfg = wxGetApp().app_config; + FontList font_list = load_font_list_from_app_config(app_cfg); + m_font_manager.add_fonts(font_list); + if (!m_font_manager.load_first_valid_font()) { + FontList font_list = FontListSerializable::create_default_font_list(); + m_font_manager.add_fonts(font_list); + // TODO: What to do when default fonts are not loadable? + bool success = m_font_manager.load_first_valid_font(); + assert(success); } - set_default_configuration(); + set_default_text(); } -void GLGizmoEmboss::set_default_configuration() +void GLGizmoEmboss::set_default_text() { m_text = _u8L("Embossed text"); - //load_font(); // reload actual font - because of font size } Slic3r::TriangleMesh GLGizmoEmboss::create_default_mesh() @@ -466,16 +464,18 @@ Slic3r::TriangleMesh GLGizmoEmboss::create_mesh() { // It is neccessary to create some shape // Emboss text window is opened by creation new embosstext object - if (m_font == nullptr) return create_default_mesh(); - const FontItem &fi = m_font_list[m_font_selected]; - TriangleMesh result = create_mesh(m_text.c_str(), *m_font, fi.prop); + std::shared_ptr& font_file = m_font_manager.get_font_file(); + if (font_file == nullptr || m_font_manager.get_fonts().empty()) + return create_default_mesh(); + const FontItem &fi = m_font_manager.get_font_item(); + TriangleMesh result = create_mesh(m_text.c_str(), *font_file, fi.prop); if (result.its.empty()) return create_default_mesh(); return result; } -Slic3r::TriangleMesh GLGizmoEmboss::create_mesh(const char * text, - Emboss::Font &font, - const FontProp& font_prop) +Slic3r::TriangleMesh GLGizmoEmboss::create_mesh(const char * text, + Emboss::FontFile &font, + const FontProp & font_prop) { ExPolygons shapes = Emboss::text2shapes(font, text, font_prop); float scale = font_prop.size_in_mm / font.ascent; @@ -505,7 +505,7 @@ void GLGizmoEmboss::check_selection() // behave like adding new text m_volume = nullptr; - set_default_configuration(); + set_default_text(); } ModelVolume *GLGizmoEmboss::get_selected_volume() @@ -551,9 +551,11 @@ bool GLGizmoEmboss::process() if (m_volume == nullptr) return false; // exist loaded font? - if (m_font == nullptr) return false; - auto data = std::make_unique( - m_font, create_configuration(), create_volume_name(), m_volume); + std::shared_ptr& font_file = m_font_manager.get_font_file(); + if (font_file == nullptr) return false; + auto data = std::make_unique(font_file, + create_configuration(), + create_volume_name(), m_volume); auto &worker = wxGetApp().plater()->get_ui_job_worker(); replace_job(worker, std::make_unique(std::move(data))); @@ -580,7 +582,8 @@ void GLGizmoEmboss::draw_window() bool loaded = load_font(font_index); } #endif // ALLOW_DEBUG_MODE - if (m_font == nullptr) { + bool exist_font_file = m_font_manager.get_font_file() != nullptr; + if (!exist_font_file) { ImGui::Text("%s",_u8L("Warning: No font is selected. Select correct one.").c_str()); } draw_font_list(); @@ -599,7 +602,7 @@ void GLGizmoEmboss::draw_window() if (ImGui::Button(_u8L("Close").c_str())) close(); // Option to create text volume when reselecting volumes - m_imgui->disabled_begin(m_font == nullptr); + m_imgui->disabled_begin(!exist_font_file); if (m_volume == nullptr) { ImGui::SameLine(); if (ImGui::Button(_u8L("Generate preview").c_str())) { @@ -620,8 +623,10 @@ void GLGizmoEmboss::draw_font_list() const float &max_width = m_gui_cfg->max_font_name_width; std::optional rename_index, delete_index, duplicate_index; - const std::string& current_name = m_font_list[m_font_selected].name; + const FontItem &actual_font_item = m_font_manager.get_font_item(); + const std::string ¤t_name = actual_font_item.name; std::string trunc_name = ImGuiWrapper::trunc(current_name, max_width); + const auto &fonts = m_font_manager.get_fonts(); ImGui::SetNextItemWidth(m_gui_cfg->combo_font_width); if (ImGui::BeginCombo("##font_selector", trunc_name.c_str())) { // first line @@ -645,25 +650,30 @@ void GLGizmoEmboss::draw_font_list() #endif // ALLOW_DEBUG_MODE ImGui::Separator(); - for (FontItem &f : m_font_list) { - ImGui::PushID(f.name.c_str()); - std::string name = ImGuiWrapper::trunc(f.name, max_width); - size_t index = &f - &m_font_list.front(); - bool is_selected = index == m_font_selected; + for (const auto &item : fonts) { + size_t index = &item - &fonts.front(); + const FontItem &fi = item.font_item; + ImGui::PushID(fi.name.c_str()); + std::string name = ImGuiWrapper::trunc(fi.name, max_width); + + bool is_selected = (&fi == &actual_font_item); ImGuiSelectableFlags_ flags = ImGuiSelectableFlags_AllowItemOverlap; // allow click buttons if (ImGui::Selectable(name.c_str(), is_selected, flags) ) { - if (load_font(index)) process(); + if (m_font_manager.load_font(index)) process(); } else if (ImGui::IsItemHovered()) - ImGui::SetTooltip("%s", f.name.c_str()); + ImGui::SetTooltip("%s", fi.name.c_str()); // reorder items if (ImGui::IsItemActive() && !ImGui::IsItemHovered()) { int other_index = index + (ImGui::GetMouseDragDelta(0).y < 0.f ? -1 : 1); - if (other_index >= 0 && other_index < m_font_list.size()) { - std::swap(m_font_list[index], m_font_list[other_index]); + if (other_index >= 0 && other_index < fonts.size()) { + std::swap(m_font_manager.get_font(index), + m_font_manager.get_font(other_index)); // fix selected index - if (m_font_selected == other_index) m_font_selected = index; - else if (m_font_selected == index) m_font_selected = other_index; + if (is_selected) + m_font_manager.select(other_index); + else if (&fonts[other_index].font_item == &actual_font_item) + m_font_manager.select(index); ImGui::ResetMouseDragDelta(); } } @@ -682,21 +692,13 @@ void GLGizmoEmboss::draw_font_list() // duplicate font item if (duplicate_index.has_value()) { - size_t index = *duplicate_index; - FontItem fi = m_font_list[index]; // copy - make_unique_name(fi.name, m_font_list); - m_font_list.insert(m_font_list.begin() + index, fi); - // fix selected index - if (index < m_font_selected) ++m_font_selected; + m_font_manager.duplicate(*duplicate_index); store_font_list_to_app_config(); } // delete font item if (delete_index.has_value()) { - size_t index = *delete_index; - m_font_list.erase(m_font_list.begin() + index); - // fix selected index - if (index < m_font_selected) --m_font_selected; + m_font_manager.erase(*delete_index); store_font_list_to_app_config(); } @@ -706,7 +708,7 @@ void GLGizmoEmboss::draw_font_list() static std::string new_name; if (rename_index.has_value() && !ImGui::IsPopupOpen(rename_popup_id)) { ImGui::OpenPopup(rename_popup_id); - rename_item = &m_font_list[*rename_index]; + rename_item = &m_font_manager.get_font(*rename_index).font_item; new_name = rename_item->name; // initialize with original copy } @@ -718,7 +720,8 @@ void GLGizmoEmboss::draw_font_list() ImGui::SetNextItemWidth(m_gui_cfg->combo_font_width); bool is_unique = true; - for (const FontItem &fi : m_font_list) { + for (const auto &item : m_font_manager.get_fonts()) { + const FontItem &fi = item.font_item; if (&fi == rename_item) continue; // could be same as original name if (fi.name == new_name) is_unique = false; } @@ -740,8 +743,7 @@ void GLGizmoEmboss::draw_text_input() static const ImGuiInputTextFlags flags = ImGuiInputTextFlags_AllowTabInput | ImGuiInputTextFlags_AutoSelectAll; - ImVector &fonts = m_imgui_font_atlas.Fonts; - ImFont * imgui_font = fonts.empty() ? nullptr : fonts.front(); + ImFont *imgui_font = m_font_manager.get_imgui_font(); bool exist_font = imgui_font != nullptr && imgui_font->IsLoaded(); if (exist_font) ImGui::PushFont(imgui_font); @@ -761,17 +763,18 @@ void GLGizmoEmboss::draw_text_input() if (exist_font) ImGui::PopFont(); // imgui_font has to be unused - if (exist_change) check_imgui_font_range(); + if (exist_change) m_font_manager.check_imgui_font_range(m_text); } void GLGizmoEmboss::draw_advanced() { - if (m_font != nullptr) { - ImGui::Text("%s", _u8L("Advanced font options could be change only for corect font.\nStart with select correct font.")); + std::shared_ptr& font_file = m_font_manager.get_font_file(); + if (font_file == nullptr) { + ImGui::Text("%s", _u8L("Advanced font options could be change only for corect font.\nStart with select correct font.").c_str()); return; } - FontItem &fi = m_font_list[m_font_selected]; + FontItem &fi = m_font_manager.get_font_item(); FontProp &font_prop = fi.prop; bool exist_change = false; @@ -787,8 +790,8 @@ void GLGizmoEmboss::draw_advanced() fi.path = WxFontUtils::store_wxFont(*wx_font); } } - load_imgui_font(); - m_font->cache.clear(); + m_font_manager.load_imgui_font(); + font_file->cache.clear(); exist_change = true; } @@ -798,7 +801,7 @@ void GLGizmoEmboss::draw_advanced() ImGui::SetNextItemWidth(2 * m_gui_cfg->advanced_input_width); if (ImGuiWrapper::input_optional_int(_u8L("CharGap[in font points]").c_str(), font_prop.char_gap)) { - m_font->cache.clear(); + font_file->cache.clear(); exist_change = true; } @@ -808,27 +811,27 @@ void GLGizmoEmboss::draw_advanced() ImGui::SetNextItemWidth(2 * m_gui_cfg->advanced_input_width); if (m_imgui->slider_optional_float(_u8L("Boldness[in font points]").c_str(), font_prop.boldness, -200.f, 200.f, "%.0f", 1.f, false, _L("tiny / wide chars"))){ - m_font->cache.clear(); + font_file->cache.clear(); exist_change = true; } ImGui::SetNextItemWidth(2 * m_gui_cfg->advanced_input_width); if (m_imgui->slider_optional_float(_u8L("Skew ratio").c_str(), font_prop.skew, -1.f, 1.f, "%.2f", 1.f, false, _L("italic strength"))){ - m_font->cache.clear(); + font_file->cache.clear(); exist_change = true; } // when more collection add selector - if (m_font != nullptr && m_font->count > 1) { + if (font_file != nullptr && font_file->count > 1) { ImGui::SetNextItemWidth(m_gui_cfg->advanced_input_width); if (ImGui::BeginCombo(_u8L("Font collection").c_str(), - std::to_string(m_font->index).c_str())) { - for (unsigned int i = 0; i < m_font->count; ++i) { + std::to_string(font_file->index).c_str())) { + for (unsigned int i = 0; i < font_file->count; ++i) { ImGui::PushID(1 << (10 + i)); if (ImGui::Selectable(std::to_string(i).c_str(), - i == m_font->index)) { - m_font->index = i; - m_font->cache.clear(); + i == font_file->index)) { + font_file->index = i; + font_file->cache.clear(); exist_change = true; } ImGui::PopID(); @@ -844,7 +847,6 @@ void GLGizmoEmboss::draw_advanced() #ifdef ALLOW_DEBUG_MODE - std::string descriptor = m_font_list[m_font_selected].path; ImGui::Text("family = %s", (font_prop.family.has_value() ? font_prop.family->c_str() : " --- ")); @@ -857,6 +859,8 @@ void GLGizmoEmboss::draw_advanced() ImGui::Text("weight = %s", (font_prop.weight.has_value() ? font_prop.weight->c_str() : " --- ")); + + std::string descriptor = fi.path; ImGui::Text("descriptor = %s", descriptor.c_str()); ImGui::Image(m_imgui_font_atlas.TexID, ImVec2(m_imgui_font_atlas.TexWidth, @@ -864,171 +868,13 @@ void GLGizmoEmboss::draw_advanced() #endif // ALLOW_DEBUG_MODE } -bool GLGizmoEmboss::load_font(size_t font_index) -{ - if (font_index >= m_font_list.size()) return false; - std::swap(font_index, m_font_selected); - bool is_loaded = load_font(); - if (!is_loaded) std::swap(font_index, m_font_selected); - return is_loaded; -} - -bool GLGizmoEmboss::load_font() -{ - if (m_font_selected >= m_font_list.size()) return false; - FontItem &fi = m_font_list[m_font_selected]; - if (fi.type == FontItem::Type::file_path) { - // fill font name after load from .3mf - if (fi.name.empty()) - fi.name = Slic3r::GUI::GLGizmoEmboss::get_file_name(fi.path); - std::unique_ptr font_ptr = Emboss::load_font(fi.path.c_str()); - if (font_ptr == nullptr) return false; - m_font = std::move(font_ptr); - load_imgui_font(); - return true; - } - if (fi.type != WxFontUtils::get_actual_type()) return false; - std::optional wx_font = WxFontUtils::load_wxFont(fi.path); - if (!wx_font.has_value()) return false; - - // fill font name after load from .3mf - if (fi.name.empty()) - fi.name = WxFontUtils::get_human_readable_name(*wx_font); - return load_font(*wx_font); -} - -bool GLGizmoEmboss::load_font(const wxFont &font) -{ - auto font_ptr = WxFontUtils::load_font(font); - if (font_ptr == nullptr) return false; - m_font = std::move(font_ptr); - load_imgui_font(); - return true; -} - -void GLGizmoEmboss::check_imgui_font_range() -{ - const char *text = m_text.c_str(); - - const ImFont *font = m_imgui_font_atlas.Fonts.front(); - if (!font->IsLoaded()) { - // when create font no one letter in text was inside font - // check text again - load_imgui_font(); - return; - } - if (font->ConfigData == nullptr) return; - const ImWchar *ranges = font->ConfigData->GlyphRanges; - auto is_in_ranges = [ranges](unsigned int letter) -> bool { - for (const ImWchar *range = ranges; range[0] && range[1]; range += 2) { - ImWchar from = range[0]; - ImWchar to = range[1]; - if (from <= letter && letter <= to) return true; - if (letter < to) return false; // ranges should be sorted - } - return false; - }; - - bool exist_unknown = false; - while (*text) { - unsigned int c = 0; - int c_len = ImTextCharFromUtf8(&c, text, NULL); - text += c_len; - if (c_len == 0) break; - if (!is_in_ranges(c)) { - exist_unknown = true; - break; - } - } - if (exist_unknown) load_imgui_font(); -} - -void GLGizmoEmboss::load_imgui_font() -{ - if (m_font == nullptr) return; - - ImFontGlyphRangesBuilder builder; - builder.AddRanges(m_imgui->get_glyph_ranges()); - builder.AddText(m_text.c_str()); - - m_imgui_font_ranges.clear(); - - builder.BuildRanges(&m_imgui_font_ranges); - const FontProp &font_prop = m_font_list[m_font_selected].prop; - int font_size = static_cast( - std::round(std::abs(font_prop.size_in_mm / 0.3528))); - if (font_size < m_gui_cfg->min_imgui_font_size) - font_size = m_gui_cfg->min_imgui_font_size; - if (font_size > m_gui_cfg->max_imgui_font_size) - font_size = m_gui_cfg->max_imgui_font_size; - - ImFontConfig font_config; - font_config.FontDataOwnedByAtlas = false; - m_imgui_font_atlas.Flags |= ImFontAtlasFlags_NoMouseCursors | - ImFontAtlasFlags_NoPowerOfTwoHeight; - m_imgui_font_atlas.Clear(); - m_imgui_font_atlas.AddFontFromMemoryTTF((void *) m_font->buffer.data(), - m_font->buffer.size(), font_size, - &font_config, - m_imgui_font_ranges.Data); - - unsigned char *pixels; - int width, height; - m_imgui_font_atlas.GetTexDataAsAlpha8(&pixels, &width, &height); - - // Upload texture to graphics system - GLint last_texture; - glsafe(::glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture)); - ScopeGuard sg([last_texture]() { - glsafe(::glBindTexture(GL_TEXTURE_2D, last_texture)); - }); - - GLuint font_texture; - glsafe(::glGenTextures(1, &font_texture)); - glsafe(::glBindTexture(GL_TEXTURE_2D, font_texture)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); - glsafe(::glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)); - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, width, height, 0, - GL_ALPHA, GL_UNSIGNED_BYTE, pixels)); - - // Store our identifier - m_imgui_font_atlas.TexID = (ImTextureID) (intptr_t) font_texture; -} - -void GLGizmoEmboss::make_unique_name(std::string &name, const FontList &list) -{ - auto is_unique = [&list](const std::string &name)->bool { - for (const FontItem &fi : list) - if (fi.name == name) return false; - return true; - }; - - if (name.empty()) name = "font"; - if (is_unique(name)) return; - - auto pos = name.find(" ("); - if (pos != std::string::npos && - name.find(")", pos) != std::string::npos) { - // short name by ord number - name = name.substr(0, pos); - } - - int order = 1; // start with value 2 to represents same font name - std::string new_name; - do { - new_name = name + " (" + std::to_string(++order) + ")"; - } while (!is_unique(new_name)); - name = new_name; -} - bool GLGizmoEmboss::choose_font_by_wxdialog() { wxFontData data; data.EnableEffects(false); data.RestrictSelection(wxFONTRESTRICT_SCALABLE); // set previous selected font - FontItem &selected_font_item = m_font_list[m_font_selected]; + FontItem &selected_font_item = m_font_manager.get_font_item(); if (selected_font_item.type == WxFontUtils::get_actual_type()) { std::optional selected_font = WxFontUtils::load_wxFont( selected_font_item.path); @@ -1040,32 +886,20 @@ bool GLGizmoEmboss::choose_font_by_wxdialog() data = font_dialog.GetFontData(); wxFont font = data.GetChosenFont(); - size_t font_index = m_font_list.size(); + size_t font_index = m_font_manager.get_fonts().size(); FontItem font_item = WxFontUtils::get_font_item(font); - - // fix dynamic creation of italic font - wxFontStyle wx_style = font.GetStyle(); - if ((wx_style == wxFONTSTYLE_ITALIC || wx_style == wxFONTSTYLE_SLANT) && - !Emboss::is_italic(*m_font)) { - font_item.prop.skew = 0.2; - } - - make_unique_name(font_item.name, m_font_list); - m_font_list.emplace_back(font_item); + m_font_manager.add_font(font_item); // Check that deserialization NOT influence font // false - use direct selected wxFont in dialog // true - use font item (serialize and deserialize wxFont) bool use_deserialized_font = false; - if (!use_deserialized_font) m_font_selected = font_index; + // Try load and use new added font - if ((!use_deserialized_font && !load_font(font)) || - (use_deserialized_font && !load_font(font_index)) || + if ((use_deserialized_font && !m_font_manager.load_font(font_index)) || + (!use_deserialized_font && !m_font_manager.load_font(font_index, font)) || !process()) { - // reverse index for font_selected - std::swap(font_index, m_font_selected); // when not process - // remove form font list - m_font_list.pop_back(); + m_font_manager.erase(font_index); wxString message = GUI::format_wxstr( _L("Font '%1%' can't be used. Please select another."), font_item.name); @@ -1074,6 +908,14 @@ bool GLGizmoEmboss::choose_font_by_wxdialog() not_loaded_font_message.ShowModal(); return choose_font_by_wxdialog(); } + + // fix dynamic creation of italic font + wxFontStyle wx_style = font.GetStyle(); + if ((wx_style == wxFONTSTYLE_ITALIC || wx_style == wxFONTSTYLE_SLANT) && + !Emboss::is_italic(*m_font_manager.get_font_file())) { + m_font_manager.get_font_item().prop.skew = 0.2; + } + return true; } @@ -1091,14 +933,15 @@ bool GLGizmoEmboss::choose_true_type_file() for (auto &input_file : input_files) { std::string path = std::string(input_file.c_str()); std::string name = get_file_name(path); - make_unique_name(name, m_font_list); - m_font_list.emplace_back(name, path, FontItem::Type::file_path, FontProp()); + //make_unique_name(name, m_font_list); + FontItem fi(name, path, FontItem::Type::file_path, FontProp()); + m_font_manager.add_font(fi); // set first valid added font as active if (!font_loaded) { - if (!load_font(m_font_list.size() - 1)) - m_font_list.pop_back(); - else + if (!m_font_manager.load_font(m_font_manager.get_fonts().size() - 1)) { + //m_font_list.pop_back(); + } else font_loaded = true; } } @@ -1127,7 +970,7 @@ bool GLGizmoEmboss::choose_svg_file() BoundingBox bb; for (const auto &p : polys) bb.merge(p.contour.points); - const FontProp &fp = m_font_list[m_font_selected].prop; + const FontProp &fp = m_font_manager.get_font_item().prop; float scale = fp.size_in_mm / std::max(bb.max.x(), bb.max.y()); auto project = std::make_unique( std::make_unique(fp.emboss / scale), scale); @@ -1142,7 +985,7 @@ bool GLGizmoEmboss::choose_svg_file() TextConfiguration GLGizmoEmboss::create_configuration() { - return TextConfiguration(m_font_list[m_font_selected], m_text); + return TextConfiguration(m_font_manager.get_font_item(), m_text); } bool GLGizmoEmboss::load_configuration(ModelVolume *volume) @@ -1154,43 +997,41 @@ bool GLGizmoEmboss::load_configuration(ModelVolume *volume) FontItem & c_font_item = configuration.font_item; // try to find font in local font list - auto is_config = [&c_font_item](const FontItem &font_item) -> bool { - return font_item.path == c_font_item.path; + auto is_config = [&c_font_item](const FontManager::Item &font_item) -> bool { + const FontItem &fi = font_item.font_item; + return fi.path == c_font_item.path && fi.prop == c_font_item.prop; }; - auto it = std::find_if(m_font_list.begin(), m_font_list.end(), is_config); - - size_t prev_font_selected = m_font_selected; - - if (it == m_font_list.end()) { + const auto& fonts = m_font_manager.get_fonts(); + auto it = std::find_if(fonts.begin(), fonts.end(), is_config); + bool found_font = it != fonts.end(); + size_t font_index; + if (!found_font) { // font is not in list // add font to list - m_font_selected = m_font_list.size(); - make_unique_name(c_font_item.name, m_font_list); - m_font_list.emplace_back(c_font_item); + font_index = fonts.size(); + m_font_manager.add_font(c_font_item); } else { // font is found in list - m_font_selected = it - m_font_list.begin(); + font_index = it - fonts.begin(); } m_text = configuration.text; m_volume = volume; - if (!load_font()) { + if (!m_font_manager.load_font(font_index)) { // create similar font auto wx_font = WxFontUtils::create_wxFont(c_font_item, configuration.font_item.prop); if (wx_font.has_value()) { // fix not loadable font item - FontItem &fi = m_font_list[m_font_selected]; + FontItem &fi = m_font_manager.get_font_item(); FontItem fi_new = WxFontUtils::get_font_item(*wx_font); fi_new.name = fi.name; // use previous name fi = fi_new; // rewrite font item fi.prop = configuration.font_item.prop; - if (!load_font(*wx_font)) return false; + if (!m_font_manager.load_font(font_index, *wx_font)) return false; } else { // can't create similar font use previous - m_font_list.erase(m_font_list.begin() + m_font_selected); - m_font_selected = prev_font_selected; - if (!load_font()) return false; + m_font_manager.erase(font_index); } create_notification_not_valid_font(configuration); } @@ -1208,7 +1049,7 @@ void GLGizmoEmboss::create_notification_not_valid_font( auto level = NotificationManager::NotificationLevel::WarningNotificationLevel; - const auto &fi = m_font_list[m_font_selected]; + const auto &fi = m_font_manager.get_font_item(); const auto &origin_family = tc.font_item.prop.face_name; const auto &actual_family = fi.prop.face_name; @@ -1341,54 +1182,28 @@ bool GLGizmoEmboss::draw_button(IconType icon, bool disable) return false; } -class FontListSerializable +FontList GLGizmoEmboss::load_font_list_from_app_config(const AppConfig *cfg) { - static const std::string APP_CONFIG_FONT_NAME; - static const std::string APP_CONFIG_FONT_DESCRIPTOR; - static const std::string APP_CONFIG_FONT_LINE_HEIGHT; - static const std::string APP_CONFIG_FONT_DEPTH; - static const std::string APP_CONFIG_FONT_BOLDNESS; - static const std::string APP_CONFIG_FONT_SKEW; - static const std::string APP_CONFIG_FONT_CHAR_GAP; - static const std::string APP_CONFIG_FONT_LINE_GAP; -public: - FontListSerializable() = delete; - - static FontList create_default_font_list(); - static std::string create_section_name(unsigned index); - static std::optional load_font_item(const std::map &app_cfg_section); - static void store_font_item(AppConfig &cfg, const FontItem &fi, unsigned index); - -private: - // TODO: move to app config like read from section - static bool read(const std::map& section, const std::string& key, float& value); - static bool read(const std::map& section, const std::string& key, std::optional& value); - static bool read(const std::map& section, const std::string& key, std::optional& value); -}; - -void GLGizmoEmboss::load_font_list_from_app_config() -{ - const AppConfig *cfg = wxGetApp().app_config; - unsigned index = 1; - std::string section_name = FontListSerializable::create_section_name(index++); + FontList result; + unsigned index = 1; + std::string section_name = FontListSerializable::create_section_name(index++); while (cfg->has_section(section_name)) { std::optional fi = FontListSerializable::load_font_item(cfg->get_section(section_name)); - if (fi.has_value()) { - make_unique_name(fi->name, m_font_list); - m_font_list.emplace_back(*fi); - } + if (fi.has_value()) result.emplace_back(*fi); section_name = FontListSerializable::create_section_name(index++); } - if (m_font_list.empty()) - m_font_list = FontListSerializable::create_default_font_list(); + if (result.empty()) + return FontListSerializable::create_default_font_list(); + return result; } void GLGizmoEmboss::store_font_list_to_app_config() const { AppConfig *cfg = wxGetApp().app_config; unsigned index = 1; - for (const FontItem &fi : m_font_list) { - // skip file paths + fonts from other OS + for (const auto& item : m_font_manager.get_fonts()) { + const FontItem &fi = item.font_item; + // skip file paths + fonts from other OS(loaded from .3mf) if (fi.type != WxFontUtils::get_actual_type()) continue; FontListSerializable::store_font_item(*cfg, fi, index++); } @@ -1404,119 +1219,12 @@ void GLGizmoEmboss::store_font_list_to_app_config() const void GLGizmoEmboss::store_font_item_to_app_config() const { AppConfig *cfg = wxGetApp().app_config; - unsigned index = m_font_selected + 1; - FontListSerializable::store_font_item( - *cfg, m_font_list[m_font_selected], index); -} - -const std::string FontListSerializable::APP_CONFIG_FONT_NAME = "name"; -const std::string FontListSerializable::APP_CONFIG_FONT_DESCRIPTOR = "descriptor"; -const std::string FontListSerializable::APP_CONFIG_FONT_LINE_HEIGHT = "line_height"; -const std::string FontListSerializable::APP_CONFIG_FONT_DEPTH = "depth"; -const std::string FontListSerializable::APP_CONFIG_FONT_BOLDNESS = "boldness"; -const std::string FontListSerializable::APP_CONFIG_FONT_SKEW = "skew"; -const std::string FontListSerializable::APP_CONFIG_FONT_CHAR_GAP = "char_gap"; -const std::string FontListSerializable::APP_CONFIG_FONT_LINE_GAP = "line_gap"; - -FontList FontListSerializable::create_default_font_list() -{ - return { - WxFontUtils::get_font_item(wxFont(5, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL)) - , WxFontUtils::get_font_item(wxFont(10, wxFONTFAMILY_MODERN, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD)) - , WxFontUtils::get_os_font() - }; -} - -std::string FontListSerializable::create_section_name(unsigned index) -{ - return AppConfig::SECTION_FONT + ':' + std::to_string(index); -} - -#include "fast_float/fast_float.h" -bool FontListSerializable::read(const std::map& section, const std::string& key, float& value){ - auto item = section.find(key); - if (item == section.end()) return false; - const std::string &data = item->second; - if (data.empty()) return false; - float value_; - fast_float::from_chars(data.c_str(), data.c_str() + data.length(), value_); - // read only non zero value - if (fabs(value_) <= std::numeric_limits::epsilon()) return false; - - value = value_; - return true; -} - -bool FontListSerializable::read(const std::map& section, const std::string& key, std::optional& value){ - auto item = section.find(key); - if (item == section.end()) return false; - const std::string &data = item->second; - if (data.empty()) return false; - int value_ = std::atoi(data.c_str()); - if (value_ == 0) return false; - - value = value_; - return true; -} - -bool FontListSerializable::read(const std::map& section, const std::string& key, std::optional& value){ - auto item = section.find(key); - if (item == section.end()) return false; - const std::string &data = item->second; - if (data.empty()) return false; - float value_; - fast_float::from_chars(data.c_str(), data.c_str() + data.length(), value_); - // read only non zero value - if (fabs(value_) <= std::numeric_limits::epsilon()) return false; - - value = value_; - return true; -} - -std::optional FontListSerializable::load_font_item( - const std::map &app_cfg_section) -{ - auto path_it = app_cfg_section.find(APP_CONFIG_FONT_DESCRIPTOR); - if (path_it == app_cfg_section.end()) return {}; - const std::string &path = path_it->second; - - auto name_it = app_cfg_section.find(APP_CONFIG_FONT_NAME); - static const std::string default_name = "font_name"; - const std::string &name = - (name_it == app_cfg_section.end()) ? - default_name : name_it->second; - - FontProp fp; - read(app_cfg_section, APP_CONFIG_FONT_LINE_HEIGHT, fp.size_in_mm); - read(app_cfg_section, APP_CONFIG_FONT_DEPTH, fp.emboss); - read(app_cfg_section, APP_CONFIG_FONT_BOLDNESS, fp.boldness); - read(app_cfg_section, APP_CONFIG_FONT_SKEW, fp.skew); - read(app_cfg_section, APP_CONFIG_FONT_CHAR_GAP, fp.char_gap); - read(app_cfg_section, APP_CONFIG_FONT_LINE_GAP, fp.line_gap); - - FontItem::Type type = WxFontUtils::get_actual_type(); - return FontItem(name, path, type, fp); -} - -void FontListSerializable::store_font_item(AppConfig & cfg, - const FontItem &fi, - unsigned index) -{ - std::string section_name = create_section_name(index); - cfg.clear_section(section_name); - cfg.set(section_name, APP_CONFIG_FONT_NAME, fi.name); - cfg.set(section_name, APP_CONFIG_FONT_DESCRIPTOR, fi.path); - const FontProp &fp = fi.prop; - cfg.set(section_name, APP_CONFIG_FONT_LINE_HEIGHT, std::to_string(fp.size_in_mm)); - cfg.set(section_name, APP_CONFIG_FONT_DEPTH, std::to_string(fp.emboss)); - if (fp.boldness.has_value()) - cfg.set(section_name, APP_CONFIG_FONT_BOLDNESS, std::to_string(*fp.boldness)); - if (fp.skew.has_value()) - cfg.set(section_name, APP_CONFIG_FONT_SKEW, std::to_string(*fp.skew)); - if (fp.char_gap.has_value()) - cfg.set(section_name, APP_CONFIG_FONT_CHAR_GAP, std::to_string(*fp.char_gap)); - if (fp.line_gap.has_value()) - cfg.set(section_name, APP_CONFIG_FONT_LINE_GAP, std::to_string(*fp.line_gap)); + // index of section start from 1 + const FontItem &fi = m_font_manager.get_font_item(); + size_t index = &m_font_manager.get_font() - + &m_font_manager.get_fonts().front(); + // TODO: fix index when, not serialized font is in list + FontListSerializable::store_font_item(*cfg, fi, index); } std::string GLGizmoEmboss::get_file_name(const std::string &file_path) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index 5a9b54ba9a..77ad9480b5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -7,6 +7,7 @@ #include "GLGizmoRotate.hpp" #include "slic3r/GUI/GLTexture.hpp" #include "slic3r/Utils/RaycastManager.hpp" +#include "slic3r/Utils/FontManager.hpp" #include "admesh/stl.h" // indexed_triangle_set #include @@ -74,7 +75,7 @@ protected: private: void initialize(); - void set_default_configuration(); + void set_default_text(); TriangleMesh create_default_mesh(); TriangleMesh create_mesh(); @@ -87,7 +88,7 @@ private: /// Property of font /// Triangle mesh model static TriangleMesh create_mesh(const char * text, - Emboss::Font & font, + Emboss::FontFile & font, const FontProp &font_prop); void check_selection(); @@ -106,15 +107,6 @@ private: bool on_mouse_for_rotation(const wxMouseEvent &mouse_event); bool on_mouse_for_translate(const wxMouseEvent &mouse_event); - bool load_font(); - // try to set font_index - bool load_font(size_t font_index); - bool load_font(const wxFont &font); - void load_imgui_font(); - void check_imgui_font_range(); - - // TODO: move to fontList utils - static void make_unique_name(std::string &name, const FontList &list); bool choose_font_by_wxdialog(); bool choose_true_type_file(); bool choose_svg_file(); @@ -137,11 +129,6 @@ private: { size_t max_count_char_in_volume_name = 20; int count_line_of_text = 6; - // limits for font size inside gizmo - // When out of limits no change in size will appear in text input - int min_imgui_font_size = 18; - int max_imgui_font_size = 60; - bool draw_advanced = false; // setted only when wanted to use - not all the time @@ -162,11 +149,11 @@ private: }; std::optional m_gui_cfg; - FontList m_font_list; - size_t m_font_selected;// index to m_font_list + FontManager m_font_manager; - // to share data with job thread - std::shared_ptr m_font; + //FontList m_font_list; + //size_t m_font_selected;// index to m_font_list + std::string m_text; // actual volume @@ -185,11 +172,6 @@ private: // initialize when GL is accessible bool m_is_initialized; - // imgui font - ImFontAtlas m_imgui_font_atlas; - // must live same as font in atlas - ImVector m_imgui_font_ranges; - // drawing icons GLTexture m_icons_texture; bool init_icons(); @@ -199,7 +181,7 @@ private: bool draw_button(IconType icon, bool disable = false); // load / store appConfig - void load_font_list_from_app_config(); + static FontList load_font_list_from_app_config(const AppConfig *cfg); void store_font_list_to_app_config() const; void store_font_item_to_app_config() const; diff --git a/src/slic3r/GUI/Jobs/EmbossJob.hpp b/src/slic3r/GUI/Jobs/EmbossJob.hpp index 282be02222..bf7c03b87f 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.hpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.hpp @@ -14,7 +14,7 @@ namespace Slic3r::GUI { struct EmbossData { // Pointer on Data of font (glyph shapes) - std::shared_ptr font; + std::shared_ptr font; // font item is not used for create object TextConfiguration text_configuration; // new volume name created from text @@ -28,10 +28,10 @@ struct EmbossData // Change of volume change id, last change could disapear //ObjectID volume_id; - EmbossData(std::shared_ptr font, - TextConfiguration text_configuration, - std::string volume_name, - ModelVolume * volume) + EmbossData(std::shared_ptr font, + TextConfiguration text_configuration, + std::string volume_name, + ModelVolume * volume) : font(std::move(font)) , text_configuration(text_configuration) , volume_name(volume_name) diff --git a/src/slic3r/Utils/FontListSerializable.cpp b/src/slic3r/Utils/FontListSerializable.cpp new file mode 100644 index 0000000000..cf6c4fcbf3 --- /dev/null +++ b/src/slic3r/Utils/FontListSerializable.cpp @@ -0,0 +1,117 @@ +#include "FontListSerializable.hpp" + +#include +#include "WxFontUtils.hpp" + +using namespace Slic3r; +using namespace Slic3r::GUI; + +const std::string FontListSerializable::APP_CONFIG_FONT_NAME = "name"; +const std::string FontListSerializable::APP_CONFIG_FONT_DESCRIPTOR = "descriptor"; +const std::string FontListSerializable::APP_CONFIG_FONT_LINE_HEIGHT = "line_height"; +const std::string FontListSerializable::APP_CONFIG_FONT_DEPTH = "depth"; +const std::string FontListSerializable::APP_CONFIG_FONT_BOLDNESS = "boldness"; +const std::string FontListSerializable::APP_CONFIG_FONT_SKEW = "skew"; +const std::string FontListSerializable::APP_CONFIG_FONT_CHAR_GAP = "char_gap"; +const std::string FontListSerializable::APP_CONFIG_FONT_LINE_GAP = "line_gap"; + +FontList FontListSerializable::create_default_font_list() +{ + return { + WxFontUtils::get_font_item(wxFont(5, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL)) + , WxFontUtils::get_font_item(wxFont(10, wxFONTFAMILY_MODERN, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD)) + , WxFontUtils::get_os_font() + }; +} + +std::string FontListSerializable::create_section_name(unsigned index) +{ + return AppConfig::SECTION_FONT + ':' + std::to_string(index); +} + +#include "fast_float/fast_float.h" +bool FontListSerializable::read(const std::map& section, const std::string& key, float& value){ + auto item = section.find(key); + if (item == section.end()) return false; + const std::string &data = item->second; + if (data.empty()) return false; + float value_; + fast_float::from_chars(data.c_str(), data.c_str() + data.length(), value_); + // read only non zero value + if (fabs(value_) <= std::numeric_limits::epsilon()) return false; + + value = value_; + return true; +} + +bool FontListSerializable::read(const std::map& section, const std::string& key, std::optional& value){ + auto item = section.find(key); + if (item == section.end()) return false; + const std::string &data = item->second; + if (data.empty()) return false; + int value_ = std::atoi(data.c_str()); + if (value_ == 0) return false; + + value = value_; + return true; +} + +bool FontListSerializable::read(const std::map& section, const std::string& key, std::optional& value){ + auto item = section.find(key); + if (item == section.end()) return false; + const std::string &data = item->second; + if (data.empty()) return false; + float value_; + fast_float::from_chars(data.c_str(), data.c_str() + data.length(), value_); + // read only non zero value + if (fabs(value_) <= std::numeric_limits::epsilon()) return false; + + value = value_; + return true; +} + +std::optional FontListSerializable::load_font_item( + const std::map &app_cfg_section) +{ + auto path_it = app_cfg_section.find(APP_CONFIG_FONT_DESCRIPTOR); + if (path_it == app_cfg_section.end()) return {}; + const std::string &path = path_it->second; + + auto name_it = app_cfg_section.find(APP_CONFIG_FONT_NAME); + static const std::string default_name = "font_name"; + const std::string &name = + (name_it == app_cfg_section.end()) ? + default_name : name_it->second; + + FontProp fp; + read(app_cfg_section, APP_CONFIG_FONT_LINE_HEIGHT, fp.size_in_mm); + read(app_cfg_section, APP_CONFIG_FONT_DEPTH, fp.emboss); + read(app_cfg_section, APP_CONFIG_FONT_BOLDNESS, fp.boldness); + read(app_cfg_section, APP_CONFIG_FONT_SKEW, fp.skew); + read(app_cfg_section, APP_CONFIG_FONT_CHAR_GAP, fp.char_gap); + read(app_cfg_section, APP_CONFIG_FONT_LINE_GAP, fp.line_gap); + + FontItem::Type type = WxFontUtils::get_actual_type(); + return FontItem(name, path, type, fp); +} + +void FontListSerializable::store_font_item(AppConfig & cfg, + const FontItem &fi, + unsigned index) +{ + std::string section_name = create_section_name(index); + cfg.clear_section(section_name); + cfg.set(section_name, APP_CONFIG_FONT_NAME, fi.name); + cfg.set(section_name, APP_CONFIG_FONT_DESCRIPTOR, fi.path); + const FontProp &fp = fi.prop; + cfg.set(section_name, APP_CONFIG_FONT_LINE_HEIGHT, std::to_string(fp.size_in_mm)); + cfg.set(section_name, APP_CONFIG_FONT_DEPTH, std::to_string(fp.emboss)); + if (fp.boldness.has_value()) + cfg.set(section_name, APP_CONFIG_FONT_BOLDNESS, std::to_string(*fp.boldness)); + if (fp.skew.has_value()) + cfg.set(section_name, APP_CONFIG_FONT_SKEW, std::to_string(*fp.skew)); + if (fp.char_gap.has_value()) + cfg.set(section_name, APP_CONFIG_FONT_CHAR_GAP, std::to_string(*fp.char_gap)); + if (fp.line_gap.has_value()) + cfg.set(section_name, APP_CONFIG_FONT_LINE_GAP, std::to_string(*fp.line_gap)); +} \ No newline at end of file diff --git a/src/slic3r/Utils/FontListSerializable.hpp b/src/slic3r/Utils/FontListSerializable.hpp new file mode 100644 index 0000000000..4efcffbb8d --- /dev/null +++ b/src/slic3r/Utils/FontListSerializable.hpp @@ -0,0 +1,45 @@ +#ifndef slic3r_FontListSerializable_hpp_ +#define slic3r_FontListSerializable_hpp_ + +#include +#include +#include +#include // FontList+FontItem + +namespace Slic3r { +class AppConfig; +} + +namespace Slic3r::GUI { + +/// +/// For store/load font list to/from AppConfig +/// +class FontListSerializable +{ + static const std::string APP_CONFIG_FONT_NAME; + static const std::string APP_CONFIG_FONT_DESCRIPTOR; + static const std::string APP_CONFIG_FONT_LINE_HEIGHT; + static const std::string APP_CONFIG_FONT_DEPTH; + static const std::string APP_CONFIG_FONT_BOLDNESS; + static const std::string APP_CONFIG_FONT_SKEW; + static const std::string APP_CONFIG_FONT_CHAR_GAP; + static const std::string APP_CONFIG_FONT_LINE_GAP; +public: + FontListSerializable() = delete; + + static FontList create_default_font_list(); + static std::string create_section_name(unsigned index); + static std::optional load_font_item(const std::map &app_cfg_section); + static void store_font_item(AppConfig &cfg, const FontItem &fi, unsigned index); + +private: + // TODO: move to app config like read from section + static bool read(const std::map& section, const std::string& key, float& value); + static bool read(const std::map& section, const std::string& key, std::optional& value); + static bool read(const std::map& section, const std::string& key, std::optional& value); +}; +} // namespace Slic3r + +#endif // #define slic3r_FontListSerializable_hpp_ + diff --git a/src/slic3r/Utils/FontManager.cpp b/src/slic3r/Utils/FontManager.cpp new file mode 100644 index 0000000000..99ee342376 --- /dev/null +++ b/src/slic3r/Utils/FontManager.cpp @@ -0,0 +1,291 @@ +#include "FontManager.hpp" +#include +#include // Imgui texture +#include // ImTextCharFromUtf8 +#include "WxFontUtils.hpp" +#include "libslic3r/Utils.hpp" // ScopeGuard +#include "slic3r/GUI/3DScene.hpp" // ::glsafe + +using namespace Slic3r; +using namespace Slic3r::GUI; + +FontManager::FontManager(const ImWchar *language_glyph_range) + : m_imgui_init_glyph_range(language_glyph_range), + m_font_selected(0) +{} + +void FontManager::select(size_t index) +{ + if (index < m_font_list.size()) + m_font_selected = index; +} + +void FontManager::duplicate(size_t index) { + if (index >= m_font_list.size()) return; + Item item = m_font_list[index]; // copy + make_unique_name(item.font_item.name); + + m_font_list.insert(m_font_list.begin() + index, item); + // fix selected index + if (index < m_font_selected) ++m_font_selected; +} + +void FontManager::erase(size_t index) { + if (index >= m_font_list.size()) return; + m_font_list.erase(m_font_list.begin() + index); + // fix selected index + if (index < m_font_selected) --m_font_selected; +} + +bool FontManager::load_font(size_t font_index) +{ + if (font_index >= m_font_list.size()) return false; + std::swap(font_index, m_font_selected); + bool is_loaded = load_font(); + if (!is_loaded) std::swap(font_index, m_font_selected); + return is_loaded; +} + +bool FontManager::load_font(size_t font_index, const wxFont &font) +{ + if (font_index >= m_font_list.size()) return false; + std::swap(font_index, m_font_selected); + bool is_loaded = load_font(font); + if (!is_loaded) std::swap(font_index, m_font_selected); + return is_loaded; +} + + +static std::string get_file_name(const std::string &file_path) +{ + size_t pos_last_delimiter = file_path.find_last_of('\\'); + size_t pos_point = file_path.find_last_of('.'); + size_t offset = pos_last_delimiter + 1; + size_t count = pos_point - pos_last_delimiter - 1; + return file_path.substr(offset, count); +} + +bool FontManager::load_font() +{ + // next condition may be safely removed + if (m_font_selected >= m_font_list.size()) return false; + + Item &item = m_font_list[m_font_selected]; + FontItem &fi = item.font_item; + if (fi.type == FontItem::Type::file_path) { + // fill font name after load from .3mf + if (fi.name.empty()) + fi.name = get_file_name(fi.path); + std::unique_ptr font_ptr = Emboss::load_font( + fi.path.c_str()); + if (font_ptr == nullptr) return false; + item.font_file = std::move(font_ptr); + load_imgui_font(); + return true; + } + if (fi.type != WxFontUtils::get_actual_type()) return false; + std::optional wx_font = WxFontUtils::load_wxFont(fi.path); + if (!wx_font.has_value()) return false; + + // fill font name after load from .3mf + if (fi.name.empty()) + fi.name = WxFontUtils::get_human_readable_name(*wx_font); + return load_font(*wx_font); +} + +bool FontManager::load_first_valid_font() { + // try to load valid font + m_font_selected = 0; + bool is_font_loaded = load_font(); + while (!is_font_loaded && !m_font_list.empty()) { + // can't load so erase it from list + m_font_list.erase(m_font_list.begin()); + is_font_loaded = load_font(); + } + return !m_font_list.empty(); +} + +void FontManager::add_font(FontItem font_item) +{ + make_unique_name(font_item.name); + Item item; + item.font_item = font_item; + m_font_list.push_back(item); +} + +void FontManager::add_fonts(FontList font_list) +{ + for (const FontItem &fi : font_list) + add_font(fi); +} + +std::shared_ptr &FontManager::get_font_file() +{ + return m_font_list[m_font_selected].font_file; +} + +const FontItem &FontManager::get_font_item() const +{ + return m_font_list[m_font_selected].font_item; +} + +FontItem &FontManager::get_font_item() +{ + return m_font_list[m_font_selected].font_item; +} + +ImFont *FontManager::get_imgui_font() +{ + return m_font_list[m_font_selected].imgui_font; +} + +const std::vector &Slic3r::GUI::FontManager::get_fonts() const +{ + return m_font_list; +} + +std::vector &FontManager::get_fonts() +{ + return m_font_list; +} + +const FontManager::Item &FontManager::get_font() const +{ + return m_font_list[m_font_selected]; +} + +FontManager::Item &FontManager::get_font(size_t index) +{ + return m_font_list[index]; +} + +const FontManager::Item &FontManager::get_font(size_t index) const +{ + return m_font_list[index]; +} + +void FontManager::make_unique_name(std::string &name) { + auto is_unique = [&](const std::string &name) -> bool { + for (const Item &it : m_font_list) + if (it.font_item.name == name) return false; + return true; + }; + + if (name.empty()) name = "font"; + if (is_unique(name)) return; + + auto pos = name.find(" ("); + if (pos != std::string::npos && name.find(")", pos) != std::string::npos) { + // short name by ord number + name = name.substr(0, pos); + } + + int order = 1; // start with value 2 to represents same font name + std::string new_name; + do { + new_name = name + " (" + std::to_string(++order) + ")"; + } while (!is_unique(new_name)); + name = new_name; +} + +void FontManager::check_imgui_font_range(const std::string& text) +{ + const ImFont *font = m_imgui_font_atlas.Fonts.front(); + if (!font->IsLoaded()) { + // when create font no one letter in text was inside font + // check text again + load_imgui_font(); + return; + } + if (font->ConfigData == nullptr) return; + const ImWchar *ranges = font->ConfigData->GlyphRanges; + auto is_in_ranges = [ranges](unsigned int letter) -> bool { + for (const ImWchar *range = ranges; range[0] && range[1]; range += 2) { + ImWchar from = range[0]; + ImWchar to = range[1]; + if (from <= letter && letter <= to) return true; + if (letter < to) return false; // ranges should be sorted + } + return false; + }; + + bool exist_unknown = false; + const char *text_char_ptr = text.c_str(); + while (*text_char_ptr) { + unsigned int c = 0; + // UTF-8 to 32-bit character need imgui_internal + int c_len = ImTextCharFromUtf8(&c, text_char_ptr, NULL); + text_char_ptr += c_len; + if (c_len == 0) break; + if (!is_in_ranges(c)) { + exist_unknown = true; + break; + } + } + if (exist_unknown) load_imgui_font(); +} + +bool FontManager::load_font(const wxFont &font) +{ + auto font_ptr = WxFontUtils::load_font(font); + if (font_ptr == nullptr) return false; + m_font_list[m_font_selected].font_file = std::move(font_ptr); + load_imgui_font(); + return true; +} + +void FontManager::load_imgui_font(const std::string &text) +{ + Item &item = m_font_list[m_font_selected]; + if (item.font_file == nullptr) return; + + // TODO: Create glyph range + ImFontGlyphRangesBuilder builder; + builder.AddRanges(m_imgui_init_glyph_range); + if (!text.empty()) + builder.AddText(text.c_str()); + item.font_ranges.clear(); + + builder.BuildRanges(&item.font_ranges); + const FontProp &font_prop = item.font_item.prop; + int font_size = static_cast( + std::round(std::abs(font_prop.size_in_mm / 0.3528))); + if (font_size < m_cfg.min_imgui_font_size) + font_size = m_cfg.min_imgui_font_size; + if (font_size > m_cfg.max_imgui_font_size) + font_size = m_cfg.max_imgui_font_size; + + ImFontConfig font_config; + font_config.FontDataOwnedByAtlas = false; + m_imgui_font_atlas.Flags |= ImFontAtlasFlags_NoMouseCursors | + ImFontAtlasFlags_NoPowerOfTwoHeight; + m_imgui_font_atlas.Clear(); + + const std::vector &buffer = item.font_file->buffer; + m_imgui_font_atlas.AddFontFromMemoryTTF( + (void *) buffer.data(), buffer.size(), font_size, &font_config, item.font_ranges.Data); + + unsigned char *pixels; + int width, height; + m_imgui_font_atlas.GetTexDataAsAlpha8(&pixels, &width, &height); + + // Upload texture to graphics system + GLint last_texture; + glsafe(::glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture)); + ScopeGuard sg([last_texture]() { + glsafe(::glBindTexture(GL_TEXTURE_2D, last_texture)); + }); + + GLuint font_texture; + glsafe(::glGenTextures(1, &font_texture)); + glsafe(::glBindTexture(GL_TEXTURE_2D, font_texture)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + glsafe(::glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)); + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, width, height, 0, + GL_ALPHA, GL_UNSIGNED_BYTE, pixels)); + + // Store our identifier + m_imgui_font_atlas.TexID = (ImTextureID) (intptr_t) font_texture; + item.imgui_font = m_imgui_font_atlas.Fonts.front(); +} diff --git a/src/slic3r/Utils/FontManager.hpp b/src/slic3r/Utils/FontManager.hpp new file mode 100644 index 0000000000..a4b7529aeb --- /dev/null +++ b/src/slic3r/Utils/FontManager.hpp @@ -0,0 +1,109 @@ +#ifndef slic3r_FontManager_hpp_ +#define slic3r_FontManager_hpp_ + +#include +#include "libslic3r/Emboss.hpp" + +class wxFont; + +namespace Slic3r::GUI { + +/// +/// GUI list of loaded fonts +/// Keep pointer to ImGui font pointers +/// Keep file data of TTF files +/// +class FontManager +{ +public: + FontManager(const ImWchar *language_glyph_range); + void select(size_t index); + void duplicate(size_t index); + void erase(size_t index); + + // load actual selected font + bool load_font(); + // try to select and load font_index + bool load_font(size_t font_index); + // fastering load font on index by wxFont + bool load_font(size_t font_index, const wxFont &font); + + void load_imgui_font(const std::string &text = ""); + + // extend actual imgui font when exist unknown char in text + // NOTE: imgui_font has to be unused + void check_imgui_font_range(const std::string &text); + + // erase font when not possible to load + bool load_first_valid_font(); + + // add font into manager + void add_font(FontItem font_item); + // add multiple font into manager + void add_fonts(FontList font_list); + + // getter on active font file for access to glyphs + std::shared_ptr &get_font_file(); + + // getter on active font item for access to font property + const FontItem &get_font_item() const; + FontItem &get_font_item(); + + // getter on acitve font pointer for imgui + ImFont *get_imgui_font(); + + // free used memory and font file data + void free_except_active_font(); + + struct Item; + // access to all managed fonts + const std::vector &get_fonts() const; + + std::vector &get_fonts(); + const Item &get_font() const; + const Item &get_font(size_t index) const; + Item &get_font(size_t index); + + struct Item + { + FontItem font_item; + + // cache for view font name with maximal width in imgui + std::string truncated_name; + + // share font file data with emboss job thread + std::shared_ptr font_file = nullptr; + + // ImGui font + ImFont *imgui_font; + + // must live same as imgui_font inside of atlas + ImVector font_ranges; + }; + +private: + struct Configuration + { + // limits for imgui loaded font + // Value out of limits is crop + int min_imgui_font_size = 18; + int max_imgui_font_size = 60; + } m_cfg; + + // load actual font by wx font + bool load_font(const wxFont &font); + + void make_unique_name(std::string &name); + + // Privat member + std::vector m_font_list; + size_t m_font_selected; // index to m_font_list + + // store all font GLImages + ImFontAtlas m_imgui_font_atlas; + const ImWchar *m_imgui_init_glyph_range; +}; + +} // namespace Slic3r + +#endif // slic3r_FontManager_hpp_ diff --git a/src/slic3r/Utils/WxFontUtils.cpp b/src/slic3r/Utils/WxFontUtils.cpp index 81e9f6a755..fc294ff17b 100644 --- a/src/slic3r/Utils/WxFontUtils.cpp +++ b/src/slic3r/Utils/WxFontUtils.cpp @@ -12,7 +12,7 @@ using namespace Slic3r; using namespace Slic3r::GUI; -std::unique_ptr WxFontUtils::load_font(const wxFont &font) +std::unique_ptr WxFontUtils::load_font(const wxFont &font) { if (!font.IsOk()) return nullptr; #ifdef _WIN32 diff --git a/src/slic3r/Utils/WxFontUtils.hpp b/src/slic3r/Utils/WxFontUtils.hpp index 7d9a9cf0eb..a42b2d93cc 100644 --- a/src/slic3r/Utils/WxFontUtils.hpp +++ b/src/slic3r/Utils/WxFontUtils.hpp @@ -16,7 +16,7 @@ public: WxFontUtils() = delete; // os specific load of wxFont - static std::unique_ptr load_font(const wxFont &font); + static std::unique_ptr load_font(const wxFont &font); static FontItem::Type get_actual_type(); static FontItem get_font_item(const wxFont &font);