diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 177d8d708b..80024da1d6 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -39,6 +39,7 @@ static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-conte const std::string AppConfig::SECTION_FILAMENTS = "filaments"; const std::string AppConfig::SECTION_MATERIALS = "sla_materials"; +const std::string AppConfig::SECTION_EMBOSS = "emboss"; void AppConfig::reset() { diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index 03c35f35a3..28014c4c17 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -168,6 +168,7 @@ public: static const std::string SECTION_FILAMENTS; static const std::string SECTION_MATERIALS; + static const std::string SECTION_EMBOSS; private: template diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index 0ee7db68b1..270e4ab931 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -57,16 +57,17 @@ std::optional Privat::get_glyph(stbtt_fontinfo &font_info, int un if (num_verts <= 0) return glyph; // no shape int *contour_lengths = NULL; - int num_countour = 0; + int num_countour_int = 0; stbtt__point *points = stbtt_FlattenCurves(vertices, num_verts, - flatness, &contour_lengths, &num_countour, font_info.userdata); + flatness, &contour_lengths, &num_countour_int, font_info.userdata); + size_t num_contour = static_cast(num_countour_int); Polygons glyph_polygons; - glyph_polygons.reserve(num_countour); + glyph_polygons.reserve(num_contour); size_t pi = 0; // point index - for (size_t ci = 0; ci < num_countour; ++ci) { - int length = contour_lengths[ci]; + for (size_t ci = 0; ci < num_contour; ++ci) { + int length = contour_lengths[ci]; // check minimal length for triangle if (length < 4) { // weird font @@ -77,9 +78,8 @@ std::optional Privat::get_glyph(stbtt_fontinfo &font_info, int un --length; Points pts; pts.reserve(length); - for (size_t i = 0; i < length; ++i) { - const stbtt__point &point = points[pi]; - ++pi; + for (int i = 0; i < length; ++i) { + const stbtt__point &point = points[pi++]; pts.emplace_back(point.x, point.y); } // last point is first point diff --git a/src/libslic3r/TextConfigurationSerialization.cpp b/src/libslic3r/TextConfigurationSerialization.cpp index b6d5804ed1..2271f697e8 100644 --- a/src/libslic3r/TextConfigurationSerialization.cpp +++ b/src/libslic3r/TextConfigurationSerialization.cpp @@ -15,94 +15,138 @@ const char TextConfigurationSerialization::separator = '|'; std::string TextConfigurationSerialization::serialize(const TextConfiguration &text_configuration) { - // IMPROVE: make general and move to string utils - auto twice_separator = [](const std::string& data) { - // no value is one space - if (data.empty()) return std::string(" "); - std::string::size_type pos = data.find(separator); - if (pos == data.npos) return data; - // twice all separator inside data - std::string copy = data; - do { - copy.insert(pos, 1, separator); - pos += 2; - } while (copy.npos != (pos = copy.find(separator, pos))); - return copy; - }; - - const FontItem &font_item = text_configuration.font_item; - const FontProp &font_prop = text_configuration.font_prop; - return twice_separator(text_configuration.text) + separator + - twice_separator(font_item.name) + separator + - twice_separator(font_item.path) + separator + - to_string.at(font_item.type) + separator + - std::to_string(font_prop.emboss) + separator + - std::to_string(font_prop.flatness) + separator + - std::to_string(font_prop.size_in_mm) + separator + - std::to_string(font_prop.char_gap) + separator + - std::to_string(font_prop.line_gap); + size_t size = 1 + 3 + 5; + std::vector columns; + columns.reserve(size); + columns.emplace_back(text_configuration.text); + to_columns(text_configuration.font_item, columns); + to_columns(text_configuration.font_prop, columns); + assert(columns.size() == size); + return serialize(columns, separator); } std::optional TextConfigurationSerialization::deserialize(const std::string &data) { - // IMPROVE: make general and move to string utils - auto reduce_separator = [](const std::string& item) { - std::string::size_type pos = item.find(separator); - if (pos == item.npos) return item; - std::string copy = item; - do { - assert(copy[pos] == separator); - assert(copy[pos+1] == separator); - copy.erase(pos, size_t(1)); - pos = copy.find(separator, pos + 1); - } while (pos != copy.npos); - return copy; - }; + size_t size = 1 + 3 + 5; + std::vector columns; + columns.reserve(size); + deserialize(data, separator, columns); + assert(columns.size() == size); - std::string::size_type start = 0; - auto get_column_and_move = [&data, &start](){ - // IMPROVE: make function general and move to string utils - auto find_separator = [&data](std::string::size_type pos) { - pos = data.find(separator, pos); - while (pos != data.npos && data[pos + 1] == separator) - pos = data.find(separator, pos + 2); - return pos; - }; + const std::string& text = columns[0]; + std::optional font_item = get_font_item(columns, 1); + if (!font_item.has_value()) return {}; + std::optional font_prop = get_font_prop(columns, 4); + if (!font_prop.has_value()) return {}; - if (start == data.npos) return std::string(); - std::string::size_type size = find_separator(start) - start; - if (size == 0) return std::string(); - std::string result = data.substr(start, size); - // move start column to next position - start += size + 1; - return result; - }; + return TextConfiguration(*font_item, *font_prop, text); +} - auto get_float_and_move = [&get_column_and_move]() { - std::string column = get_column_and_move(); - if (column.empty()) return 0.f; - return static_cast(std::atof(column.c_str())); - }; - auto get_int_and_move = [&get_column_and_move]() { - std::string column = get_column_and_move(); - if (column.empty()) return 0; - return std::atoi(column.c_str()); - }; +std::string TextConfigurationSerialization::serialize(const FontList &font_list) +{ + std::vector columns; + columns.reserve(3 * font_list.size()); + for (const FontItem &fi : font_list) to_columns(fi, columns); + return serialize(columns, separator); +} - std::string text = reduce_separator(get_column_and_move()); - std::string name = reduce_separator(get_column_and_move()); - std::string path = reduce_separator(get_column_and_move()); - std::string type = reduce_separator(get_column_and_move()); - auto it = to_type.find(type); - if (it == to_type.end()) return {}; // no valid type - FontItem font_item(name,path,it->second); +FontList TextConfigurationSerialization::deserialize_font_list(const std::string &data) +{ + std::vector columns; + deserialize(data, separator, columns); + if ((columns.size() % 3) != 0) return {}; + size_t count = columns.size() / 3; + FontList fl; + fl.reserve(count); + for (size_t i = 0; i < count; i++) + { + std::optional fi = get_font_item(columns, i * 3); + if (!fi.has_value()) return {}; + fl.emplace_back(*fi); + } + return fl; +} +std::string TextConfigurationSerialization::serialize(const std::vector &columns, char separator) +{ + std::string result; + const std::string separator_str = std::string(" ") + separator + ' '; + bool is_first = true; + for (const std::string& column : columns) { + if (is_first) is_first = false; + else result += separator_str; + result += twice(column, separator); + } + return result; +} + +void TextConfigurationSerialization::deserialize(const std::string &data, char separator, std::vector &columns) +{ + if (data.empty()) return; + + size_t position = 0; + while (position < data.size()) { + size_t start = position; + position = find_odd(data, position+1, separator); + size_t size = position - start; + + // is not last column + if (position != data.size()) { + assert(size != 0); + --size; + } + // is not first column + if (start != 0) { + // previous separator + space = 2 + start+=2; + assert(size >=2); + size-=2; + } + + std::string column = data.substr(start, size); + + // heal column + columns.emplace_back(reduce(column, separator)); + } +} + +void TextConfigurationSerialization::to_columns( + const FontItem &font_item, std::vector &columns) +{ + columns.emplace_back(font_item.name); + columns.emplace_back(font_item.path); + columns.emplace_back(to_string.at(font_item.type)); +} + +std::optional TextConfigurationSerialization::get_font_item( + const std::vector &columns, size_t offset) +{ + if (columns.size() <= (offset + 2)) return {}; // no enough columns + auto it = to_type.find(columns[offset+2]); + FontItem::Type type = (it != to_type.end()) ? + it->second : FontItem::Type::undefined; + return FontItem(columns[offset], columns[offset + 1], type); +} + +void TextConfigurationSerialization::to_columns( + const FontProp& font_prop, std::vector &columns) +{ + columns.emplace_back(std::to_string(font_prop.emboss)); + columns.emplace_back(std::to_string(font_prop.flatness)); + columns.emplace_back(std::to_string(font_prop.size_in_mm)); + columns.emplace_back(std::to_string(font_prop.char_gap)); + columns.emplace_back(std::to_string(font_prop.line_gap)); +} + +std::optional TextConfigurationSerialization::get_font_prop( + const std::vector &columns, size_t offset) +{ + if (columns.size() <= (offset + 4)) return {}; // no enough columns FontProp font_prop; - font_prop.emboss = get_float_and_move(); - font_prop.flatness = get_float_and_move(); - font_prop.size_in_mm = get_float_and_move(); - font_prop.char_gap = get_int_and_move(); - if (start == data.npos) return {}; // no valid data - font_prop.line_gap = get_int_and_move(); - return TextConfiguration(font_item, font_prop, text); + font_prop.emboss = static_cast(std::atof(columns[offset].c_str())); + font_prop.flatness = static_cast(std::atof(columns[offset+1].c_str())); + font_prop.size_in_mm = static_cast(std::atof(columns[offset+2].c_str())); + font_prop.char_gap = static_cast(std::atof(columns[offset+3].c_str())); + font_prop.line_gap = std::atoi(columns[offset+4].c_str()); + return font_prop; } \ No newline at end of file diff --git a/src/libslic3r/TextConfigurationSerialization.hpp b/src/libslic3r/TextConfigurationSerialization.hpp index 2fc0379824..d4b0fec591 100644 --- a/src/libslic3r/TextConfigurationSerialization.hpp +++ b/src/libslic3r/TextConfigurationSerialization.hpp @@ -14,16 +14,100 @@ class TextConfigurationSerialization { public: TextConfigurationSerialization() = delete; // only static functions + + // store / load TextConfiguration - .3mf files static std::string serialize(const TextConfiguration &text_configuration); static std::optional deserialize(const std::string &data); + // store / load FontList - AppConfig + static std::string serialize(const FontList &font_list); + static FontList deserialize_font_list(const std::string &data); + +private: // convert type to string and vice versa static const std::map to_type; static const std::map to_string; - static const char separator; + + // store / load general connection of string into one string + static std::string serialize(const std::vector &columns, char separator); + static void deserialize(const std::string& data, char separator, std::vector &columns);// columns vector should be reserved on valid count - // Move to map utils + static void to_columns(const FontItem& font_item, std::vector &columns); + static std::optional get_font_item(const std::vector &columns, size_t offset); + + static void to_columns(const FontProp& font_prop, std::vector &columns); + static std::optional get_font_prop(const std::vector &columns, size_t offset); + + /// + /// Twice all appearance of character in data. + /// Twiced character could be used as separator for this data. + /// IMPROVE: move to string utils + /// + /// input data to twice character + /// Specify character to be twiced in data + /// String conatin only pair continous count of specified character + static std::string twice(const std::string &data, char letter) + { + // no value is one space + if (data.empty()) return std::string(" "); + std::string::size_type pos = data.find(letter); + if (pos == data.npos) return data; + // twice all separator inside data + std::string copy = data; // copy + do { + copy.insert(pos, 1, letter); + pos += 2; + } while (copy.npos != (pos = copy.find(letter, pos))); + return copy; + }; + + /// + /// Reduce all twice appearance of character in data. + /// Main purpose heal after twice function. + /// IMPROVE: move to string utils + /// + /// input data to reduce character + /// Specify character to be reduced + /// String conatining only half letter in row + static std::string reduce(const std::string &data, char letter) + { + std::string::size_type pos = data.find(letter); + if (pos == data.npos) return data; + std::string copy = data; // copy + do { + assert(copy[pos] == letter); + assert(copy[pos + 1] == letter); + copy.erase(pos, size_t(1)); + pos = copy.find(letter, pos + 1); + } while (pos != copy.npos); + return copy; + }; + + /// + /// Find odd position of letter in text data + /// Used with combination of twice and reduce + /// IMPROVE: move to string utils + /// + /// Input text + /// Start index into data for searching odd letter + /// Character to find + /// Index to data with next odd appearance of letter + /// OR size of data when NO next one exists + static size_t find_odd(const std::string &data, size_t pos, char letter) + { + pos = data.find(letter, pos); + while ((pos+1) <= data.size() && data[pos + 1] == letter) + pos = data.find(letter, pos + 2); + return pos; + } + + /// + /// Create map with swaped key-value + /// IMPROVE: Move to map utils + /// + /// Input map + /// Map with swapped key-value template static std::map create_oposit_map( const std::map &map) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index a2c3e4ab0d..8192f5410b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -13,6 +13,8 @@ #include "libslic3r/Model.hpp" #include "libslic3r/ClipperUtils.hpp" // union_ex +#include "libslic3r/AppConfig.hpp" // store/load font list +#include "libslic3r/TextConfigurationSerialization.hpp" // store/load font list #include "imgui/imgui_stdlib.h" // using std::string for inputs #include "nanosvg/nanosvg.h" // load SVG file @@ -77,7 +79,7 @@ GLGizmoEmboss::GLGizmoEmboss(GLCanvas3D &parent) , m_volume_type(ModelVolumeType::MODEL_PART) , m_is_initialized(false) // initialize on first opening gizmo { - // TODO: suggest to use https://fontawesome.com/ + // TODO: add suggestion to use https://fontawesome.com/ // (copy & paste) unicode symbols from web } @@ -152,16 +154,10 @@ void GLGizmoEmboss::initialize() if (m_is_initialized) return; m_is_initialized = true; + load_font_list(); + m_gui_cfg.emplace(GuiCfg()); - m_font_list = {{"NotoSans Regular", Slic3r::resources_dir() + "/fonts/NotoSans-Regular.ttf"} - , {"NotoSans CJK", Slic3r::resources_dir() + "/fonts/NotoSansCJK-Regular.ttc"} -#ifdef USE_FONT_DIALOG - , 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() -#endif // USE_FONT_DIALOG - }; m_font_selected = 0; bool is_font_loaded = load_font(); @@ -177,6 +173,37 @@ void GLGizmoEmboss::initialize() set_default_configuration(); } + +void GLGizmoEmboss::load_font_list() +{ + AppConfig *cfg = wxGetApp().app_config; + std::string font_list_str = cfg->get(AppConfig::SECTION_EMBOSS, M_APP_CFG_FONT_LIST); + if (!font_list_str.empty()) { + std::optional fl = TextConfigurationSerialization::deserialize_font_list(font_list_str); + if (fl.has_value()) m_font_list = *fl; + } + if (m_font_list.empty()) m_font_list = create_default_font_list(); +} + +void GLGizmoEmboss::store_font_list() +{ + AppConfig *cfg = wxGetApp().app_config; + std::string font_list_str = TextConfigurationSerialization::serialize(m_font_list); + cfg->set(AppConfig::SECTION_EMBOSS, M_APP_CFG_FONT_LIST, font_list_str); +} + +FontList GLGizmoEmboss::create_default_font_list() { + return { + {"NotoSans Regular", Slic3r::resources_dir() + "/fonts/NotoSans-Regular.ttf"} + , {"NotoSans CJK", Slic3r::resources_dir() + "/fonts/NotoSansCJK-Regular.ttc"} +#ifdef USE_FONT_DIALOG + , 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() +#endif // USE_FONT_DIALOG + }; +} + void GLGizmoEmboss::set_default_configuration() { m_text = _u8L("Embossed text"); m_font_prop = FontProp(); @@ -226,10 +253,12 @@ ModelVolume *GLGizmoEmboss::get_selected_volume(const Selection &selection, const GLVolume * vol_gl = selection.get_volume(vol_id_gl); const GLVolume::CompositeID &id = vol_gl->composite_id; - if (id.object_id >= objects.size()) return nullptr; + if (id.object_id < 0 || static_cast(id.object_id) >= objects.size()) + return nullptr; ModelObject *object = objects[id.object_id]; - if (id.volume_id >= object->volumes.size()) return nullptr; + if (id.volume_id < 0 || static_cast(id.volume_id) >= object->volumes.size()) + return nullptr; return object->volumes[id.volume_id]; } @@ -384,12 +413,14 @@ void GLGizmoEmboss::draw_font_list() #ifdef USE_FONT_DIALOG if (ImGui::Button(_L("Choose font").c_str())) { choose_font_by_wxdialog(); + store_font_list(); ImGui::CloseCurrentPopup(); } else if (ImGui::IsItemHovered()) ImGui::SetTooltip(_L("Choose from installed font in dialog.").c_str()); ImGui::SameLine(); #endif // USE_FONT_DIALOG if (ImGui::Button(_L("Add File").c_str())) { choose_true_type_file(); + store_font_list(); ImGui::CloseCurrentPopup(); } else if (ImGui::IsItemHovered()) ImGui::SetTooltip(_L("add file with font(.ttf, .ttc)").c_str()); @@ -411,7 +442,7 @@ void GLGizmoEmboss::draw_font_list() auto pos_x = ImGui::GetCursorPosX(); // add rename button ImGui::SetCursorPosX(140); - if (ImGui::Button("rename")) rename_index = index; + if (ImGui::Button("rename")) rename_index = index; ImGui::SameLine(); ImGui::SetCursorPosX(200); @@ -420,6 +451,7 @@ void GLGizmoEmboss::draw_font_list() m_font_list.erase(m_font_list.begin() + index); // fix selected index if (index < m_font_selected) --m_font_selected; + store_font_list(); } m_imgui->disabled_end(); // exist_rename || is_selected ImGui::SameLine(); @@ -469,8 +501,11 @@ void GLGizmoEmboss::draw_font_list() if (ImGui::BeginPopupModal(rename_popup_id)) { ImGui::Text("Rename font name:"); FontItem &fi = m_font_list[rename_id]; - if (ImGui::InputText("##font name", &fi.name, ImGuiInputTextFlags_EnterReturnsTrue) || ImGui::Button("ok")) - ImGui::CloseCurrentPopup(); + if (ImGui::InputText("##font name", &fi.name, ImGuiInputTextFlags_EnterReturnsTrue) || + ImGui::Button("ok")){ + ImGui::CloseCurrentPopup(); + store_font_list(); + } ImGui::EndPopup(); } } @@ -845,5 +880,7 @@ ExPolygons NSVGUtils::to_ExPolygons(NSVGimage *image, return Slic3r::union_ex(polygons); } +const std::string GLGizmoEmboss::M_APP_CFG_FONT_LIST = "font_list"; + // any existing icon filename to not influence GUI const std::string GLGizmoEmboss::M_ICON_FILENAME = "cut.svg"; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index 796d3b2264..ef12b90fc4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -34,6 +34,9 @@ protected: private: void initialize(); + void load_font_list(); + void store_font_list(); + static FontList create_default_font_list(); void set_default_configuration(); void check_selection(); // more general function --> move to select @@ -101,7 +104,8 @@ private: // initialize when GL is accessible bool m_is_initialized; - + + static const std::string M_APP_CFG_FONT_LIST; // only temporary solution static const std::string M_ICON_FILENAME; };