From 331d4d455751ebacb6129a2548802e7c5a1011cf Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Thu, 7 Apr 2022 16:34:14 +0200 Subject: [PATCH] Show user warning about unknown symbols by font inside of input text Fix correct selection of collection ascent, descent, ... Remove boost log from emboss --> not work properly on thread --- src/libslic3r/Emboss.cpp | 148 +++++++++++++----- src/libslic3r/Emboss.hpp | 61 +++++--- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 123 +++++++++------ src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 4 + src/slic3r/GUI/ImGuiWrapper.cpp | 38 +++++ src/slic3r/GUI/ImGuiWrapper.hpp | 11 ++ .../GUI/Jobs/CreateFontStyleImagesJob.cpp | 4 +- src/slic3r/GUI/Jobs/EmbossJob.cpp | 4 +- src/slic3r/Utils/FontManager.cpp | 92 +++++------ src/slic3r/Utils/FontManager.hpp | 13 +- tests/libslic3r/test_emboss.cpp | 14 +- 11 files changed, 329 insertions(+), 183 deletions(-) diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index 2d4e67180..dc4c33e4a 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -22,6 +22,7 @@ class Private { public: Private() = delete; + static bool is_valid(const Emboss::FontFile &font, unsigned int index); static std::optional load_font_info(const unsigned char *data, unsigned int index = 0); static std::optional get_glyph(const stbtt_fontinfo &font_info, int unicode_letter, float flatness); @@ -43,17 +44,27 @@ public: static Point to_point(const stbtt__point &point); }; +bool Private::is_valid(const Emboss::FontFile &font, unsigned int index) { + if (font.data == nullptr) return false; + if (font.data->empty()) return false; + if (font.count == 0) return false; + if (index >= font.count) return false; + return true; +} + std::optional Private::load_font_info( const unsigned char *data, unsigned int index) { int font_offset = stbtt_GetFontOffsetForIndex(data, index); if (font_offset < 0) { - BOOST_LOG_TRIVIAL(error) << "Font index(" << index << ") doesn't exist." << std::endl; + assert(false); + // "Font index(" << index << ") doesn't exist."; return {}; } stbtt_fontinfo font_info; if (stbtt_InitFont(&font_info, data, font_offset) == 0) { - BOOST_LOG_TRIVIAL(error) << "Can't initialize font." << std::endl; + // Can't initialize font. + assert(false); return {}; } return font_info; @@ -63,12 +74,12 @@ std::optional Private::get_glyph(const stbtt_fontinfo &font_info, { int glyph_index = stbtt_FindGlyphIndex(&font_info, unicode_letter); if (glyph_index == 0) { - wchar_t wchar = static_cast(unicode_letter); - BOOST_LOG_TRIVIAL(error) << "Character unicode letter (" - << "decimal value = " << std::dec << unicode_letter << ", " - << "hexadecimal value = U+" << std::hex << unicode_letter << std::dec << ", " - << "wchar value = " << wchar - << ") is NOT defined inside of the font. \n"; + //wchar_t wchar = static_cast(unicode_letter); + //<< "Character unicode letter (" + //<< "decimal value = " << std::dec << unicode_letter << ", " + //<< "hexadecimal value = U+" << std::hex << unicode_letter << std::dec << ", " + //<< "wchar value = " << wchar + //<< ") is NOT defined inside of the font. \n"; return {}; } @@ -136,17 +147,19 @@ const Emboss::Glyph* Private::get_glyph( const double RESOLUTION = 0.0125; // TODO: read from printer configuration auto glyph_item = cache.find(unicode); if (glyph_item != cache.end()) return &glyph_item->second; - - if (!font_info_opt.has_value()) { - unsigned int font_index = font_prop.collection_number.has_value()? + + unsigned int font_index = font_prop.collection_number.has_value()? *font_prop.collection_number : 0; - if (font_index >= font.count) return nullptr; + if (!is_valid(font, font_index)) return nullptr; + + if (!font_info_opt.has_value()) { + font_info_opt = Private::load_font_info(font.data->data(), font_index); // can load font info? if (!font_info_opt.has_value()) return nullptr; } - float flatness = static_cast( - font.ascent * RESOLUTION / font_prop.size_in_mm); + + float flatness = static_cast(font.infos[font_index].ascent * RESOLUTION / font_prop.size_in_mm); std::optional glyph_opt = Private::get_glyph(*font_info_opt, unicode, flatness); @@ -350,18 +363,19 @@ FontList Emboss::get_font_list_by_register() { // Open Windows font registry key result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, fontRegistryPath, 0, KEY_READ, &hKey); - if (result != ERROR_SUCCESS) { - std::wcerr << L"Can not Open register key (" << fontRegistryPath << ")" - << L", function 'RegOpenKeyEx' return code: " << result << std::endl; + if (result != ERROR_SUCCESS) { + assert(false); + //std::wcerr << L"Can not Open register key (" << fontRegistryPath << ")" + // << L", function 'RegOpenKeyEx' return code: " << result << std::endl; return {}; } DWORD maxValueNameSize, maxValueDataSize; result = RegQueryInfoKey(hKey, 0, 0, 0, 0, 0, 0, 0, &maxValueNameSize, &maxValueDataSize, 0, 0); - if (result != ERROR_SUCCESS) { - BOOST_LOG_TRIVIAL(error) << "Can not earn query key, function 'RegQueryInfoKey' return code: " - << result << std::endl; + if (result != ERROR_SUCCESS) { + assert(false); + // Can not earn query key, function 'RegQueryInfoKey' return code: result return {}; } @@ -475,47 +489,61 @@ std::unique_ptr Emboss::create_font_file( int collection_size = stbtt_GetNumberOfFonts(data->data()); // at least one font must be inside collection if (collection_size < 1) { - BOOST_LOG_TRIVIAL(error) << "There is no font collection inside data." << std::endl; + assert(false); + // There is no font collection inside font data return nullptr; } - auto font_info = Private::load_font_info(data->data()); - if (!font_info.has_value()) return nullptr; - const stbtt_fontinfo *info = &(*font_info); - // load information about line gap - int ascent, descent, linegap; - stbtt_GetFontVMetrics(info, &ascent, &descent, &linegap); + unsigned int c_size = static_cast(collection_size); + std::vector infos; + infos.reserve(c_size); + for (unsigned int i = 0; i < c_size; ++i) { + auto font_info = Private::load_font_info(data->data(), i); + if (!font_info.has_value()) return nullptr; + + const stbtt_fontinfo *info = &(*font_info); + // load information about line gap + int ascent, descent, linegap; + stbtt_GetFontVMetrics(info, &ascent, &descent, &linegap); + + float pixels = 1000.; // value is irelevant + float em_pixels = stbtt_ScaleForMappingEmToPixels(info, pixels); + int units_per_em = static_cast(std::round(pixels / em_pixels)); + + infos.emplace_back(FontFile::Info{ascent, descent, linegap, units_per_em}); + } - float pixels = 1000.; // value is irelevant - float em_pixels = stbtt_ScaleForMappingEmToPixels(info, pixels); - int units_per_em = static_cast(std::round(pixels / em_pixels)); return std::make_unique( - std::move(data), collection_size, ascent, descent, linegap, units_per_em); + std::move(data), collection_size, std::move(infos)); } std::unique_ptr Emboss::create_font_file(const char *file_path) { FILE *file = fopen(file_path, "rb"); if (file == nullptr) { - BOOST_LOG_TRIVIAL(error) << "Couldn't open " << file_path << " for reading." << std::endl; + assert(false); + // BOOST_LOG_TRIVIAL(error) << "Couldn't open " << file_path << " for reading." << std::endl; return nullptr; } // find size of file if (fseek(file, 0L, SEEK_END) != 0) { - BOOST_LOG_TRIVIAL(error) << "Couldn't fseek file " << file_path << " for size measure." << std::endl; + assert(false); + // BOOST_LOG_TRIVIAL(error) << "Couldn't fseek file " << file_path << " for size measure." << std::endl; return nullptr; } size_t size = ftell(file); if (size == 0) { - BOOST_LOG_TRIVIAL(error) << "Size of font file is zero. Can't read." << std::endl; + assert(false); + // BOOST_LOG_TRIVIAL(error) << "Size of font file is zero. Can't read." << std::endl; return nullptr; } rewind(file); auto buffer = std::make_unique>(size); size_t count_loaded_bytes = fread((void *) &buffer->front(), 1, size, file); if (count_loaded_bytes != size) { - BOOST_LOG_TRIVIAL(error) << "Different loaded(from file) data size." << std::endl; + assert(false); + // BOOST_LOG_TRIVIAL(error) << "Different loaded(from file) data size." << std::endl; return nullptr; } return create_font_file(std::move(buffer)); @@ -563,7 +591,8 @@ std::unique_ptr Emboss::create_font_file(HFONT hfont) { HDC hdc = ::CreateCompatibleDC(NULL); if (hdc == NULL) { - BOOST_LOG_TRIVIAL(error) << "Can't create HDC by CreateCompatibleDC(NULL)." << std::endl; + assert(false); + // BOOST_LOG_TRIVIAL(error) << "Can't create HDC by CreateCompatibleDC(NULL)." << std::endl; return nullptr; } @@ -577,7 +606,8 @@ std::unique_ptr Emboss::create_font_file(HFONT hfont) size_t loaded_size = ::GetFontData(hdc, dwTable, dwOffset, buffer->data(), size); ::DeleteDC(hdc); if (size != loaded_size) { - BOOST_LOG_TRIVIAL(error) << "Different loaded(from HFONT) data size." << std::endl; + assert(false); + // BOOST_LOG_TRIVIAL(error) << "Different loaded(from HFONT) data size." << std::endl; return nullptr; } return create_font_file(std::move(buffer)); @@ -585,10 +615,12 @@ std::unique_ptr Emboss::create_font_file(HFONT hfont) #endif // _WIN32 std::optional Emboss::letter2glyph(const FontFile &font, + unsigned int font_index, int letter, float flatness) { - auto font_info_opt = Private::load_font_info(font.data->data(), 0); + if (!Private::is_valid(font, font_index)) return {}; + auto font_info_opt = Private::load_font_info(font.data->data(), font_index); if (!font_info_opt.has_value()) return {}; return Private::get_glyph(*font_info_opt, letter, flatness); } @@ -603,11 +635,15 @@ ExPolygons Emboss::text2shapes(FontFileWithCache &font_with_cache, Point cursor(0, 0); ExPolygons result; const FontFile& font = *font_with_cache.font_file; + unsigned int font_index = font_prop.collection_number.has_value()? + *font_prop.collection_number : 0; + if (!Private::is_valid(font, font_index)) return {}; + const FontFile::Info& info = font.infos[font_index]; Emboss::Glyphs& cache = *font_with_cache.cache; std::wstring ws = boost::nowide::widen(text); for (wchar_t wc: ws){ if (wc == '\n') { - int line_height = font.ascent - font.descent + font.linegap; + int line_height = info.ascent - info.descent + info.linegap; if (font_prop.line_gap.has_value()) line_height += *font_prop.line_gap; line_height = static_cast(line_height / SHAPE_SCALE); @@ -693,6 +729,40 @@ bool Emboss::is_italic(const FontFile &font, unsigned int font_index) return false; } +std::string Emboss::create_range_text(const std::string &text, + const FontFile &font, + unsigned int font_index, + bool *exist_unknown) +{ + if (!Private::is_valid(font, font_index)) return {}; + + std::wstring ws = boost::nowide::widen(text); + + // need remove symbols not contained in font + std::sort(ws.begin(), ws.end()); + + auto font_info_opt = Private::load_font_info(font.data->data(), 0); + if (!font_info_opt.has_value()) return {}; + const stbtt_fontinfo *font_info = &(*font_info_opt); + + if (exist_unknown != nullptr) *exist_unknown = false; + int prev_unicode = -1; + ws.erase(std::remove_if(ws.begin(), ws.end(), + [&prev_unicode, font_info, exist_unknown](wchar_t wc) -> bool { + int unicode = static_cast(wc); + // is duplicit + if (prev_unicode == unicode) return true; + prev_unicode = unicode; + bool is_unknown = !stbtt_FindGlyphIndex(font_info, unicode); + if (is_unknown && exist_unknown != nullptr) + *exist_unknown = true; + return is_unknown; + }), ws.end()); + + return boost::nowide::narrow(ws); +} + + indexed_triangle_set Emboss::polygons2model(const ExPolygons &shape2d, const IProject &projection) { diff --git a/src/libslic3r/Emboss.hpp b/src/libslic3r/Emboss.hpp index 4449bfa46..edc093b97 100644 --- a/src/libslic3r/Emboss.hpp +++ b/src/libslic3r/Emboss.hpp @@ -71,32 +71,39 @@ public: // count of fonts when data are collection of fonts unsigned int count; - // vertical position is "scale*(ascent - descent + lineGap)" - int ascent, descent, linegap; + struct Info + { + // vertical position is "scale*(ascent - descent + lineGap)" + int ascent, descent, linegap; - // for convert font units to pixel - int unit_per_em; + // for convert font units to pixel + int unit_per_em; + }; + // info for each font in data + std::vector infos; FontFile(std::unique_ptr> data, - unsigned int count, - int ascent, - int descent, - int linegap, - int unit_per_em) + unsigned int count, + std::vector &&infos) : data(std::move(data)) , count(count) - , ascent(ascent) - , descent(descent) - , linegap(linegap) - , unit_per_em(unit_per_em) + , infos(std::move(infos)) { assert(this->data != nullptr); + assert(!this->data->empty()); + assert(count == this->infos.size()); } bool operator==(const FontFile &other) const { - return count == other.count && ascent == other.ascent && - descent == other.descent && linegap == other.linegap && - data->size() == other.data->size(); - //&& *data == *other.data; + if (count != other.count || data->size() != other.data->size()) + return false; + //if(*data != *other.data) return false; + for (unsigned int i = 0; i < count; i++) + if (infos[i].ascent != other.infos[i].ascent || + infos[i].descent == other.infos[i].descent || + infos[i].linegap == other.infos[i].linegap) + return false; + + return true; } }; @@ -140,10 +147,11 @@ public: /// convert letter into polygons /// /// Define fonts + /// Index of font in collection /// 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 FontFile &font, int letter, float flatness); + static std::optional letter2glyph(const FontFile &font, unsigned int font_index, int letter, float flatness); /// /// Convert text into polygons @@ -152,9 +160,7 @@ public: /// Characters to convert /// User defined property of the font /// Inner polygon cw(outer ccw) - static ExPolygons text2shapes(FontFileWithCache &font, - const char * text, - const FontProp &font_prop); + static ExPolygons text2shapes(FontFileWithCache &font, const char *text, const FontProp &font_prop); /// /// Use data from font property to modify transformation @@ -162,8 +168,7 @@ public: /// Z-move as surface distance(FontProp::distance) /// Z-rotation as angle to Y axis(FontProp::angle) /// In / Out transformation to modify by property - static void apply_transformation(const FontProp &font_prop, - Transform3d &transformation); + static void apply_transformation(const FontProp &font_prop, Transform3d &transformation); /// /// Read information from naming table of font file @@ -174,6 +179,16 @@ public: /// True when the font description contains italic/obligue otherwise False static bool is_italic(const FontFile &font, unsigned int font_index); + /// + /// Create unique character set from string with filtered from text with only character from font + /// + /// Source vector of glyphs + /// Font descriptor + /// Define font in collection + /// True when text contain glyph unknown in font + /// Unique set of character from text contained in font + static std::string create_range_text(const std::string &text, const FontFile &font, unsigned int font_index, bool* exist_unknown = nullptr); + /// /// Project 2d point into space /// Could be plane, sphere, cylindric, ... diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 95af9fbc3..08d08ec48 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -186,10 +186,8 @@ bool GLGizmoEmboss::on_mouse_for_rotation(const wxMouseEvent &mouse_event) { if (mouse_event.Moving()) return false; - m_rotate_gizmo.on_mouse(mouse_event); - use_grabbers(mouse_event); - - if (!m_dragging) return false; + bool used = use_grabbers(mouse_event); + if (!m_dragging) return used; assert(m_volume != nullptr); assert(m_volume->text_configuration.has_value()); @@ -220,14 +218,12 @@ bool GLGizmoEmboss::on_mouse_for_rotation(const wxMouseEvent &mouse_event) if (m_font_manager.is_activ_font()) { m_font_manager.get_font_prop().angle = angle_opt; } - return true; } else if (mouse_event.LeftUp()) { // apply rotation m_parent.do_rotate(L("Text-Rotate")); start_angle.reset(); - return true; } - return false; + return used; } bool GLGizmoEmboss::on_mouse_for_translate(const wxMouseEvent &mouse_event) @@ -515,6 +511,7 @@ void GLGizmoEmboss::on_stop_dragging() // When fixing, move grabber above text (not on side) m_rotate_gizmo.set_angle(0); } +void GLGizmoEmboss::on_dragging(const UpdateData &data) { m_rotate_gizmo.dragging(data); } void GLGizmoEmboss::initialize() { @@ -768,6 +765,24 @@ ModelVolume *GLGizmoEmboss::get_selected_volume(const Selection &selection, return get_model_volume(vol_gl, objects); } +static inline void execute_job(std::shared_ptr j) +{ + struct MyCtl : public Job::Ctl + { + void update_status(int st, const std::string &msg = "") override{}; + bool was_canceled() const override { return false; } + std::future call_on_main_thread(std::function fn) override + { + return std::future{}; + } + } ctl; + j->process(ctl); + wxGetApp().plater()->CallAfter([j]() { + std::exception_ptr e_ptr = nullptr; + j->finalize(false, e_ptr); + }); +} + bool GLGizmoEmboss::process() { // no volume is selected -> selection from right panel @@ -795,17 +810,8 @@ bool GLGizmoEmboss::process() //* auto &worker = wxGetApp().plater()->get_ui_job_worker(); queue_job(worker, std::make_unique(std::move(data))); - /*/ - // Run Job on main thread (blocking) - EmbossUpdateJob j(std::move(data)); - struct MyCtl:public Job::Ctl{ - void update_status(int st, const std::string &msg = "") override{}; - bool was_canceled() const override { return false; } - std::future call_on_main_thread(std::function fn) override{return std::future{};} - } ctl; - j.process(ctl); - std::exception_ptr e_ptr = nullptr; - j.finalize(false, e_ptr); + /*/ // Run Job on main thread (blocking) - ONLY DEBUG + execute_job(std::make_shared(std::move(data))); // */ // notification is removed befor object is changed by job @@ -932,15 +938,28 @@ void GLGizmoEmboss::draw_window() void GLGizmoEmboss::draw_text_input() { + auto create_range_text = [&manager = m_font_manager, &text = m_text, &exist_unknown = m_text_contain_unknown_glyph]() { + auto &font_file = manager.get_font_file(); + const auto &cn = manager.get_font_prop().collection_number; + unsigned int font_index = (cn.has_value()) ? *cn : 0; + return Emboss::create_range_text(text, *font_file, font_index, &exist_unknown); + }; + static const ImGuiInputTextFlags flags = ImGuiInputTextFlags_AllowTabInput | ImGuiInputTextFlags_AutoSelectAll; - ImFont *imgui_font = (m_font_manager.is_activ_font())? - m_font_manager.get_imgui_font(m_text) : nullptr; - bool exist_font = imgui_font != nullptr && imgui_font->IsLoaded(); + ImFont *imgui_font = m_font_manager.get_imgui_font(); + if (imgui_font == nullptr) { + // try create new imgui font + imgui_font = m_font_manager.create_imgui_font(create_range_text()); + } + bool exist_font = imgui_font != nullptr; + assert(!exist_font || imgui_font->IsLoaded()); if (exist_font) ImGui::PushFont(imgui_font); - bool exist_change = false; + // flag for extend font ranges if neccessary + // ranges can't be extend during font is activ(pushed) + std::string range_text; float window_height = ImGui::GetWindowHeight(); float minimal_height = get_minimal_window_size().y; float extra_height = window_height - minimal_height; @@ -948,7 +967,7 @@ void GLGizmoEmboss::draw_text_input() m_gui_cfg->text_size.y + extra_height); if (ImGui::InputTextMultiline("##Text", &m_text, text_size, flags)) { process(); - exist_change = true; + range_text = create_range_text(); } if (exist_font) ImGui::PopFont(); @@ -971,34 +990,34 @@ void GLGizmoEmboss::draw_text_input() tool_tip += t; } }; + if (m_text_contain_unknown_glyph) + append_warning(_u8L("Bad symbol"), + _u8L("Text contain character glyph (represented by '?') unknown by font.")); + const FontProp &prop = m_font_manager.get_font_prop(); - if (prop.skew.has_value()) { + if (prop.skew.has_value()) append_warning(_u8L("Skew"), _u8L("Unsupported visualization of font skew for text input.")); - } - if (prop.boldness.has_value()) { + if (prop.boldness.has_value()) append_warning(_u8L("Boldness"), _u8L("Unsupported visualization of font boldness for text input.")); - } - if (prop.line_gap.has_value()) { + if (prop.line_gap.has_value()) append_warning(_u8L("Line gap"), _u8L("Unsupported visualization of gap between lines inside text input.")); - } float imgui_size = FontManager::get_imgui_font_size(prop, *m_font_manager.get_font_file()); - if (imgui_size > FontManager::max_imgui_font_size) { + if (imgui_size > FontManager::max_imgui_font_size) append_warning(_u8L("To tall"), _u8L("Diminished font height inside text input.")); - } - if (imgui_size < FontManager::min_imgui_font_size) { + if (imgui_size < FontManager::min_imgui_font_size) append_warning(_u8L("To small"), _u8L("Enlarged font height inside text input.")); - } - if (!who.empty()) { + if (!who.empty()) warning = GUI::format(_u8L("%1% is NOT shown."), who); - } } if (!warning.empty()) { + if (ImGui::IsItemHovered() && !tool_tip.empty()) + ImGui::SetTooltip("%s", tool_tip.c_str()); ImVec2 cursor = ImGui::GetCursorPos(); float width = ImGui::GetContentRegionAvailWidth(); ImVec2 size = ImGui::CalcTextSize(warning.c_str()); @@ -1006,14 +1025,17 @@ void GLGizmoEmboss::draw_text_input() ImGui::SetCursorPos(ImVec2(width - size.x + padding.x, cursor.y - size.y - padding.y)); m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, warning); - if (ImGui::IsItemHovered() && !tool_tip.empty()) - ImGui::SetTooltip("%s", tool_tip.c_str()); ImGui::SetCursorPos(cursor); } + // IMPROVE: only extend not clear // Extend font ranges // imgui_font has to be unused - if (exist_change) m_font_manager.clear_imgui_font(); + if (!range_text.empty() && + !m_imgui->contain_all_glyphs(imgui_font, range_text) ) { + m_font_manager.clear_imgui_font(); + m_font_manager.create_imgui_font(range_text); + } } //#define DEBUG_NOT_LOADABLE_FONTS @@ -1057,8 +1079,8 @@ protected: /*/ // Slow copy of font files to try load font // After this all files are loadable - auto ff = WxFontUtils::create_font_file(wx_font); - if (ff == nullptr) { + auto font_file = WxFontUtils::create_font_file(wx_font); + if (font_file == nullptr) { #ifdef DEBUG_NOT_LOADABLE_FONTS m_efacenames.emplace_back(facename.c_str()); #endif // DEBUG_NOT_LOADABLE_FONTS @@ -1802,16 +1824,19 @@ void GLGizmoEmboss::draw_advanced() } FontProp &font_prop = m_font_manager.get_font_item().prop; + const auto &cn = m_font_manager.get_font_prop().collection_number; + unsigned int font_index = (cn.has_value()) ? *cn : 0; + const auto &font_info = font_file->infos[font_index]; #ifdef SHOW_FONT_FILE_PROPERTY ImGui::SameLine(); auto& ff = m_font_manager.get_font().font_file_with_cache; - int cache_size = ff.has_value()? (int)ff.cache->size() : 0; + int cache_size = ff.has_value()? (int)ff.cache->size() : 0; std::string ff_property = - "ascent=" + std::to_string(font_file->ascent) + - ", descent=" + std::to_string(font_file->descent) + - ", lineGap=" + std::to_string(font_file->linegap) + - ", unitPerEm=" + std::to_string(font_file->unit_per_em) + + "ascent=" + std::to_string(font_info.ascent) + + ", descent=" + std::to_string(font_info.descent) + + ", lineGap=" + std::to_string(font_info.linegap) + + ", unitPerEm=" + std::to_string(font_info.unit_per_em) + ", cache(" + std::to_string(cache_size) + " glyphs)"; if (font_file->count > 1) { unsigned int collection = font_prop.collection_number.has_value() ? @@ -1830,8 +1855,9 @@ void GLGizmoEmboss::draw_advanced() // input gap between letters auto def_char_gap = m_stored_font_item.has_value() ? &m_stored_font_item->prop.char_gap : nullptr; - int min_char_gap = -font_file->ascent / 2, - max_char_gap = font_file->ascent / 2; + + int half_ascent = font_info.ascent / 2; + int min_char_gap = -half_ascent, max_char_gap = half_ascent; if (rev_slider(tr.char_gap, font_prop.char_gap, def_char_gap, _u8L("Revert gap between letters"), min_char_gap, max_char_gap, units_fmt, _L("Distance between letters"))){ // char gap is stored inside of imgui font atlas @@ -1842,8 +1868,7 @@ void GLGizmoEmboss::draw_advanced() // input gap between lines auto def_line_gap = m_stored_font_item.has_value() ? &m_stored_font_item->prop.line_gap : nullptr; - int min_line_gap = -font_file->ascent / 2, - max_line_gap = font_file->ascent / 2; + int min_line_gap = -half_ascent, max_line_gap = half_ascent; if (rev_slider(tr.line_gap, font_prop.line_gap, def_line_gap, _u8L("Revert gap between lines"), min_line_gap, max_line_gap, units_fmt, _L("Distance between lines"))){ // char gap is stored inside of imgui font atlas diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index 0e8b02d49..fc4b9b232 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -65,6 +65,7 @@ protected: void on_disable_grabber(unsigned int id) override { m_rotate_gizmo.disable_grabber(); } void on_start_dragging() override; void on_stop_dragging() override; + void on_dragging(const UpdateData &data) override; /// /// Rotate by text on dragging rotate grabers @@ -217,7 +218,10 @@ private: void fill_stored_font_items(); void select_stored_font_item(); + // Text to emboss std::string m_text; + // True when m_text contain character unknown by selected font + bool m_text_contain_unknown_glyph = false; // cancel for previous update of volume to cancel finalize part std::shared_ptr> m_update_job_cancel; diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 64081db57..d78dbcd95 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -1460,6 +1460,44 @@ void ImGuiWrapper::draw( } } +bool ImGuiWrapper::contain_all_glyphs(const ImFont *font, + const std::string &text) +{ + if (font == nullptr) return false; + if (!font->IsLoaded()) return false; + const ImFontConfig *fc = font->ConfigData; + if (fc == nullptr) return false; + if (text.empty()) return true; + return is_chars_in_ranges(fc->GlyphRanges, text.c_str()); +} + +bool ImGuiWrapper::is_char_in_ranges(const ImWchar *ranges, + unsigned int letter) +{ + 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 ImGuiWrapper::is_chars_in_ranges(const ImWchar *ranges, + const char *chars_ptr) +{ + while (*chars_ptr) { + unsigned int c = 0; + // UTF-8 to 32-bit character need imgui_internal + int c_len = ImTextCharFromUtf8(&c, chars_ptr, NULL); + chars_ptr += c_len; + if (c_len == 0) break; + if (!is_char_in_ranges(ranges, c)) return false; + } + return true; +} + + #ifdef __APPLE__ static const ImWchar ranges_keyboard_shortcuts[] = { diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index 009b239b6..2b770c2f9 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -194,6 +194,17 @@ public: ImU32 color = ImGui::GetColorU32(COL_ORANGE_LIGHT), float thickness = 3.f); + /// + /// Check that font ranges contain all chars in string + /// (rendered Unicodes are stored in GlyphRanges) + /// + /// Contain glyph ranges + /// Vector of character to check + /// True when all glyphs from text are in font ranges + static bool contain_all_glyphs(const ImFont *font, const std::string &text); + static bool is_chars_in_ranges(const ImWchar *ranges, const char *chars_ptr); + static bool is_char_in_ranges(const ImWchar *ranges, unsigned int letter); + bool requires_extra_frame() const { return m_requires_extra_frame; } void set_requires_extra_frame() { m_requires_extra_frame = true; } void reset_requires_extra_frame() { m_requires_extra_frame = false; } diff --git a/src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.cpp b/src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.cpp index 6f3e7c1aa..4e9ad2492 100644 --- a/src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.cpp +++ b/src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.cpp @@ -46,7 +46,9 @@ void CreateFontStyleImagesJob::process(Ctl &ctl) // dot per inch for monitor int dpi = get_dpi_for_window(mf); double ppm = dpi / 25.4; // pixel per milimeter - double unit_per_em = item.font.font_file->unit_per_em; + const auto &cn = item.prop.collection_number; + unsigned int font_index = (cn.has_value()) ? *cn : 0; + double unit_per_em = item.font.font_file->infos[font_index].unit_per_em; double scale = item.prop.size_in_mm / unit_per_em * Emboss::SHAPE_SCALE * ppm; scales[index] = scale; diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index 7b45eb194..542103d39 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -312,7 +312,9 @@ TriangleMesh priv::create_mesh(const char *text, if (shapes.empty()) return {}; if (was_canceled()) return {}; - int unit_per_em = font.font_file->unit_per_em; + const auto &cn = font_prop.collection_number; + unsigned int font_index = (cn.has_value()) ? *cn : 0; + int unit_per_em = font.font_file->infos[font_index].unit_per_em; float scale = font_prop.size_in_mm / unit_per_em; float depth = font_prop.emboss / scale; auto projectZ = std::make_unique(depth); diff --git a/src/slic3r/Utils/FontManager.cpp b/src/slic3r/Utils/FontManager.cpp index fcaaa96b3..f948c3f63 100644 --- a/src/slic3r/Utils/FontManager.cpp +++ b/src/slic3r/Utils/FontManager.cpp @@ -7,6 +7,7 @@ #include "slic3r/GUI/3DScene.hpp" // ::glsafe #include "slic3r/GUI/Jobs/CreateFontStyleImagesJob.hpp" +#include "slic3r/GUI/ImGuiWrapper.hpp" // check of font ranges using namespace Slic3r; using namespace Slic3r::GUI; @@ -223,40 +224,41 @@ void FontManager::clear_imgui_font() { // TODO: improove to clear only actual font if (!is_activ_font()) return; free_imgui_fonts(); - return; + return; + ImFont *imgui_font = get_imgui_font(m_font_selected); m_font_list[m_font_selected].imgui_font_index.reset(); if (imgui_font != nullptr) IM_DELETE(imgui_font); } -ImFont *FontManager::get_imgui_font(const std::string &text) +ImFont *FontManager::get_imgui_font() { - if (!is_activ_font()) return nullptr; - return get_imgui_font(m_font_selected, text); + return get_imgui_font(m_font_selected); } -ImFont *FontManager::get_imgui_font(size_t item_index, const std::string &text) -{ +ImFont *FontManager::create_imgui_font(const std::string &text) +{ + return create_imgui_font(m_font_selected, text); +} + +ImFont *FontManager::get_imgui_font(size_t item_index) +{ + if (!is_activ_font()) return nullptr; Item &item = m_font_list[item_index]; // check is already loaded - if (!item.imgui_font_index.has_value()) - return load_imgui_font(item_index, text); + if (!item.imgui_font_index.has_value()) return nullptr; - size_t index = *item.imgui_font_index; - auto & fonts = m_imgui_font_atlas.Fonts; + size_t index = *item.imgui_font_index; + ImVector &fonts = m_imgui_font_atlas.Fonts; // check correct index int f_size = fonts.size(); - assert(f_size > 0 && index < (size_t)f_size); + assert(f_size > 0 && index < (size_t) f_size); if (f_size <= 0 || index >= (size_t) f_size) return nullptr; ImFont *font = fonts[index]; if (font == nullptr) return nullptr; if (!font->IsLoaded()) return nullptr; if (font->Scale <= 0.f) return nullptr; - // automatic extend range - if (!text.empty() && !is_text_in_ranges(font, text)) - return extend_imgui_font_range(item_index, text); - return font; } @@ -327,51 +329,17 @@ bool FontManager::set_up_font_file(size_t item_index) return set_wx_font(item_index, *item.wx_font); } -bool FontManager::is_text_in_ranges(const ImFont *font, const std::string &text) -{ - if (font == nullptr) return false; - if (!font->IsLoaded()) return false; - const ImFontConfig *fc = font->ConfigData; - if (fc == nullptr) return false; - return is_text_in_ranges(fc->GlyphRanges, text); -} - -bool FontManager::is_char_in_ranges(const ImWchar *ranges, unsigned int letter) -{ - 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 FontManager::is_text_in_ranges(const ImWchar *ranges, const std::string &text) -{ - 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_char_in_ranges(ranges, c)) return false; - } - return true; -} - ImFont* FontManager::extend_imgui_font_range(size_t index, const std::string& text) { auto &font_index_opt = m_font_list[m_font_selected].imgui_font_index; if (!font_index_opt.has_value()) - return load_imgui_font(index, text); + return create_imgui_font(index, text); // TODO: start using merge mode // ImFontConfig::MergeMode = true; free_imgui_fonts(); - return load_imgui_font(index, text); + return create_imgui_font(index, text); } #include "slic3r/GUI/Jobs/CreateFontStyleImagesJob.hpp" @@ -460,15 +428,19 @@ float FontManager::max_imgui_font_size = 60.f; float FontManager::get_imgui_font_size(const FontProp &prop, const Emboss::FontFile &file) { + const auto &cn = prop.collection_number; + unsigned int font_index = (cn.has_value()) ? *cn : 0; + const auto &font_info = file.infos[font_index]; // coeficient for convert line height to font size - float c1 = (file.ascent - file.descent + file.linegap) / (float) file.unit_per_em; + float c1 = (font_info.ascent - font_info.descent + font_info.linegap) / + (float) font_info.unit_per_em; // The point size is defined as 1/72 of the Anglo-Saxon inch (25.4 mm): // It is approximately 0.0139 inch or 352.8 um. return c1 * std::abs(prop.size_in_mm) / 0.3528f; } -ImFont * FontManager::load_imgui_font(size_t index, const std::string &text) +ImFont *FontManager::create_imgui_font(size_t index, const std::string &text) { free_imgui_fonts(); // TODO: remove it after correct initialization @@ -499,19 +471,23 @@ ImFont * FontManager::load_imgui_font(size_t index, const std::string &text) ImFontConfig font_config; // TODO: start using merge mode //font_config.MergeMode = true; + + const auto &cn = font_prop.collection_number; + unsigned int font_index = (cn.has_value()) ? *cn : 0; + const auto &font_info = font_file.infos[font_index]; if (font_prop.char_gap.has_value()) { - float coef = font_size / (double) font_file.unit_per_em; + float coef = font_size / (double) font_info.unit_per_em; font_config.GlyphExtraSpacing.x = coef * (*font_prop.char_gap); } if (font_prop.line_gap.has_value()) { - float coef = font_size / (double) font_file.unit_per_em; + float coef = font_size / (double) font_info.unit_per_em; font_config.GlyphExtraSpacing.y = coef * (*font_prop.line_gap); } font_config.FontDataOwnedByAtlas = false; const std::vector &buffer = *font_file.data; - m_imgui_font_atlas.AddFontFromMemoryTTF( + ImFont * font = m_imgui_font_atlas.AddFontFromMemoryTTF( (void *) buffer.data(), buffer.size(), font_size, &font_config, item.font_ranges.Data); unsigned char *pixels; @@ -538,8 +514,10 @@ ImFont * FontManager::load_imgui_font(size_t index, const std::string &text) m_imgui_font_atlas.TexID = (ImTextureID) (intptr_t) font_texture; assert(!m_imgui_font_atlas.Fonts.empty()); if (m_imgui_font_atlas.Fonts.empty()) return nullptr; + assert(font == m_imgui_font_atlas.Fonts.back()); item.imgui_font_index = m_imgui_font_atlas.Fonts.size() - 1; - return m_imgui_font_atlas.Fonts.back(); + assert(font->IsLoaded()); + return font; } bool FontManager::set_wx_font(size_t item_index, const wxFont &wx_font) { diff --git a/src/slic3r/Utils/FontManager.hpp b/src/slic3r/Utils/FontManager.hpp index 4fda626f5..44dd6d3e0 100644 --- a/src/slic3r/Utils/FontManager.hpp +++ b/src/slic3r/Utils/FontManager.hpp @@ -99,7 +99,9 @@ public: // Getter on acitve font pointer for imgui // Initialize imgui font(generate texture) when doesn't exist yet. // Extend font atlas when not in glyph range - ImFont *get_imgui_font(const std::string &text); + ImFont *get_imgui_font(); + // initialize font range by unique symbols in text + ImFont *create_imgui_font(const std::string& text); // free used memory and font file data void free_except_active_font(); @@ -166,7 +168,7 @@ private: void duplicate(size_t index); // load actual selected font - ImFont *load_imgui_font(size_t index, const std::string &text); + ImFont *create_imgui_font(size_t index, const std::string &text); bool load_active_font(); @@ -174,18 +176,13 @@ private: // getter on index selected font pointer for imgui // text could extend font atlas when not in glyph range - ImFont *get_imgui_font(size_t item_index, const std::string &text = ""); + ImFont *get_imgui_font(size_t item_index); // extend actual imgui font when exist unknown char in text // NOTE: imgui_font has to be unused // return true when extend range otherwise FALSE ImFont *extend_imgui_font_range(size_t font_index, const std::string &text); - // Move to imgui utils - static bool is_text_in_ranges(const ImFont *font, const std::string &text); - static bool is_text_in_ranges(const ImWchar *ranges, const std::string &text); - static bool is_char_in_ranges(const ImWchar *ranges, unsigned int letter); - void free_imgui_fonts(); bool set_up_font_file(size_t item_index); diff --git a/tests/libslic3r/test_emboss.cpp b/tests/libslic3r/test_emboss.cpp index 49a5825e0..38f91c23e 100644 --- a/tests/libslic3r/test_emboss.cpp +++ b/tests/libslic3r/test_emboss.cpp @@ -136,13 +136,15 @@ TEST_CASE("Read glyph C shape from font, stb library calls ONLY", "[Emboss]") { TEST_CASE("Convert glyph % to model", "[Emboss]") { std::string font_path = get_font_filepath(); + unsigned int font_index = 0; // collection char letter = '%'; float flatness = 2.; auto font = Emboss::create_font_file(font_path.c_str()); REQUIRE(font != nullptr); - std::optional glyph = Emboss::letter2glyph(*font, letter, flatness); + std::optional glyph = + Emboss::letter2glyph(*font, font_index, letter, flatness); REQUIRE(glyph.has_value()); ExPolygons shape = glyph->shape; @@ -301,12 +303,13 @@ TEST_CASE("Cut surface", "[]") std::string font_path = get_font_filepath(); char letter = '%'; float flatness = 2.; + unsigned int font_index = 0; // collection auto font = Emboss::create_font_file(font_path.c_str()); REQUIRE(font != nullptr); - std::optional glyph = Emboss::letter2glyph(*font, letter, - flatness); + std::optional glyph = + Emboss::letter2glyph(*font, font_index, letter, flatness); REQUIRE(glyph.has_value()); ExPolygons shape = glyph->shape; @@ -586,14 +589,15 @@ using MyMesh = Slic3r::MeshBoolean::cgal2::CGALMesh; TEST_CASE("Emboss extrude cut", "[Emboss-Cut]") { std::string font_path = get_font_filepath(); + unsigned int font_index = 0; // collection char letter = '%'; float flatness = 2.; auto font = Emboss::create_font_file(font_path.c_str()); REQUIRE(font != nullptr); - std::optional glyph = Emboss::letter2glyph(*font, letter, - flatness); + std::optional glyph = + Emboss::letter2glyph(*font, font_index, letter, flatness); REQUIRE(glyph.has_value()); ExPolygons shape = glyph->shape;