diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 835980c60..b52d3d30d 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -184,6 +184,8 @@ set(SLIC3R_GUI_SOURCES GUI/Jobs/PlaterWorker.hpp GUI/Jobs/ArrangeJob.hpp GUI/Jobs/ArrangeJob.cpp + GUI/Jobs/CreateFontNameImageJob.cpp + GUI/Jobs/CreateFontNameImageJob.hpp GUI/Jobs/CreateFontStyleImagesJob.cpp GUI/Jobs/CreateFontStyleImagesJob.hpp GUI/Jobs/EmbossJob.cpp diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 027794eab..861a41c7c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -723,6 +723,8 @@ void GLGizmoEmboss::initialize() 2 * style.FramePadding.x; int max_style_image_height = 2 * input_height; cfg.max_style_image_size = Vec2i(max_style_image_width, max_style_image_height); + cfg.face_name_size.y() = line_height_with_spacing; + m_gui_cfg.emplace(std::move(cfg)); init_icons(); @@ -1313,8 +1315,43 @@ void GLGizmoEmboss::init_face_names() { std::sort(m_face_names.names.begin(), m_face_names.names.end()); } +#include "slic3r/GUI/Jobs/CreateFontNameImageJob.hpp" void GLGizmoEmboss::draw_font_list() -{ +{ + // Create of texture for font name + auto init_texture = [&face_names = m_face_names, size = m_gui_cfg->face_name_size]() { + // check if already exists + GLuint &id = face_names.texture_id; + if (id != 0) return; + // create texture for font + GLenum target = GL_TEXTURE_2D, format = GL_ALPHA, + type = GL_UNSIGNED_BYTE; + GLint level = 0, border = 0; + glsafe(::glGenTextures(1, &id)); + glsafe(::glBindTexture(target, id)); + glsafe(::glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST)); + glsafe(::glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST)); + GLint w = size.x(), h = face_names.names.size() * size.y(); + std::vector data(w * h, {0}); + glsafe(::glTexImage2D(target, level, GL_ALPHA, w, h, border, format, type, data.data())); + // bind default texture + GLuint no_texture_id = 0; + glsafe(::glBindTexture(target, no_texture_id)); + + // no one is initialized yet + face_names.exist_textures = std::vector(face_names.names.size(), {false}); + }; + auto init_trancated_names = [&face_names = m_face_names, + width = m_gui_cfg->face_name_max_width]() { + if (!face_names.names_truncated.empty()) return; + face_names.names_truncated.reserve(face_names.names.size()); + for (const wxString &face_name : face_names.names) { + std::string name(face_name.ToUTF8().data()); + face_names.names_truncated.emplace_back(ImGuiWrapper::trunc(name, width)); + } + }; + + // Set partial wxString actual_face_name; if (m_font_manager.is_activ_font()) { std::optional &wx_font_opt = m_font_manager.get_wx_font(); @@ -1326,18 +1363,44 @@ void GLGizmoEmboss::draw_font_list() wxString del_facename; if (ImGui::BeginCombo("##font_selector", selected)) { if (!m_face_names.is_init) init_face_names(); + init_texture(); + if (m_face_names.names_truncated.empty()) init_trancated_names(); + ImTextureID tex_id = (void *) (intptr_t) m_face_names.texture_id; for (const wxString &face_name : m_face_names.names) { size_t index = &face_name - &m_face_names.names.front(); ImGui::PushID(index); bool is_selected = (actual_face_name == face_name); - if (ImGui::Selectable(face_name.ToUTF8().data(), is_selected)) { + if (ImGui::Selectable(m_face_names.names_truncated[index].c_str(), is_selected)) { if (!select_facename(face_name)) { del_facename = face_name; wxMessageBox(GUI::format( _L("Font face \"%1%\" can't be selected."), face_name)); } } + if (ImGui::IsItemHovered()) + ImGui::SetTooltip(face_name.ToUTF8().data()); if (is_selected) ImGui::SetItemDefaultFocus(); + if (!m_face_names.exist_textures[index] && + ImGui::IsItemVisible()) { + m_face_names.exist_textures[index] = true; + std::string text = m_text.empty() ? "AaBbCc" : m_text; + // render text to texture + FontImageData data{text, + face_name, + m_face_names.encoding, + m_face_names.texture_id, + index, + m_gui_cfg->face_name_size}; + auto job = std::make_unique(std::move(data)); + auto& worker = wxGetApp().plater()->get_ui_job_worker(); + queue_job(worker, std::move(job)); + } + ImGui::SameLine(m_gui_cfg->face_name_offset); + ImVec2 size(m_gui_cfg->face_name_size.x(), + m_gui_cfg->face_name_size.y()), + uv0(0.f, index / (float) m_face_names.names.size()), + uv1(1.f, (index + 1) / (float) m_face_names.names.size()); + ImGui::Image(tex_id, size, uv0, uv1); ImGui::PopID(); } #ifdef SHOW_FONT_COUNT @@ -1345,6 +1408,10 @@ void GLGizmoEmboss::draw_font_list() static_cast(m_face_names.names.size())); #endif // SHOW_FONT_COUNT ImGui::EndCombo(); + } else { + // free texture and set id to zero + glsafe(::glDeleteTextures(1, &m_face_names.texture_id)); + m_face_names.texture_id = 0; } // delete unloadable face name when appear @@ -1352,6 +1419,7 @@ void GLGizmoEmboss::draw_font_list() // IMPROVE: store list of deleted facename into app.ini std::vector &f = m_face_names.names; f.erase(std::remove(f.begin(), f.end(), del_facename), f.end()); + m_face_names.names_truncated.clear(); } #ifdef ALLOW_ADD_FONT_BY_FILE diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index 5a48d8ada..649e62bd5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -22,6 +22,7 @@ #include "libslic3r/TextConfiguration.hpp" #include +#include class wxFont; namespace Slic3r{ @@ -184,6 +185,11 @@ private: ImVec2 text_size; + // maximal size of face image + Vec2i face_name_size = Vec2i(128, 0); + float face_name_max_width = 100.f; + float face_name_offset = 100.f; + // Only translations needed for calc GUI size struct Translations { @@ -220,6 +226,11 @@ private: bool is_init = false; std::vector names; wxFontEncoding encoding; + + std::vector names_truncated; + GLuint texture_id = 0; + // is texture started create? + std::vector exist_textures = {}; } m_face_names; // Track stored values in AppConfig diff --git a/src/slic3r/GUI/Jobs/CreateFontNameImageJob.cpp b/src/slic3r/GUI/Jobs/CreateFontNameImageJob.cpp new file mode 100644 index 000000000..0defecffc --- /dev/null +++ b/src/slic3r/GUI/Jobs/CreateFontNameImageJob.cpp @@ -0,0 +1,114 @@ +#include "CreateFontNameImageJob.hpp" + +#include "libslic3r/Emboss.hpp" +// rasterization of ExPoly +#include "libslic3r/SLA/AGGRaster.hpp" + +#include "slic3r/Utils/WxFontUtils.hpp" +#include "slic3r/GUI/3DScene.hpp" // ::glsafe + +#include "wx/fontenum.h" + +using namespace Slic3r; +using namespace Slic3r::GUI; + +CreateFontImageJob::CreateFontImageJob(FontImageData &&input) + : m_input(std::move(input)) +{ + assert(!m_input.text.empty()); + assert(wxFontEnumerator::IsValidFacename(m_input.font_name)); + assert(m_input.gray_level > 0 && m_input.gray_level < 255); + assert(m_input.texture_id != 0); +} + +void CreateFontImageJob::process(Ctl &ctl) +{ + if (!wxFontEnumerator::IsValidFacename(m_input.font_name)) return; + // Select font + wxFont wx_font( + wxFontInfo().FaceName(m_input.font_name).Encoding(m_input.encoding)); + if (!wx_font.IsOk()) return; + + std::unique_ptr font_file = + WxFontUtils::create_font_file(wx_font); + if (font_file == nullptr) return; + + Emboss::FontFileWithCache font_file_with_cache(std::move(font_file)); + FontProp fp; + // use only first line of text + std::string text = m_input.text; + size_t enter_pos = text.find('\n'); + if (enter_pos < text.size()) { + // text start with enter + if (enter_pos == 0) return; + // exist enter, soo delete all after enter + text = text.substr(0, enter_pos); + } + + ExPolygons shapes = Emboss::text2shapes(font_file_with_cache, + text.c_str(), fp); + // normalize height of font + BoundingBox bounding_box; + for (ExPolygon &shape : shapes) + bounding_box.merge(BoundingBox(shape.contour.points)); + if (bounding_box.size().x() < 1 || bounding_box.size().y() < 1) return; + double scale = m_input.size.y() / (double) bounding_box.size().y(); + BoundingBoxf bb2(bounding_box.min.cast(), + bounding_box.max.cast()); + bb2.scale(scale); + Vec2d size_f = bb2.size(); + m_tex_size = Point(std::ceil(size_f.x()), std::ceil(size_f.y())); + // crop image width + if (m_tex_size.x() > m_input.size.x()) m_tex_size.x() = m_input.size.x(); + + // Set up result + m_result = std::vector(m_tex_size.x() * m_tex_size.y(), {0}); + + sla::Resolution resolution(m_tex_size.x(), m_tex_size.y()); + double pixel_dim = SCALING_FACTOR / scale; + sla::PixelDim dim(pixel_dim, pixel_dim); + double gamma = 1.; + std::unique_ptr r = + sla::create_raster_grayscale_aa(resolution, dim, gamma); + for (ExPolygon &shape : shapes) shape.translate(-bounding_box.min); + for (const ExPolygon &shape : shapes) r->draw(shape); + + // copy rastered data to pixels + sla::RasterEncoder encoder = + [&pix = m_result, w = m_tex_size.x(), h = m_tex_size.y(), + gray_level = m_input.gray_level] + (const void *ptr, size_t width, size_t height, size_t num_components) { + size_t size {static_cast(w*h)}; + const unsigned char *ptr2 = (const unsigned char *) ptr; + for (size_t x = 0; x < width; ++x) + for (size_t y = 0; y < height; ++y) { + size_t index = y*w + x; + assert(index < size); + if (index >= size) continue; + pix[index] = ptr2[y * width + x] / gray_level; + } + return sla::EncodedRaster(); + }; + r->encode(encoder); +} + +void CreateFontImageJob::finalize(bool canceled, std::exception_ptr &) +{ + if (canceled) return; + + // upload texture on GPU + GLuint tex_id; + GLenum target = GL_TEXTURE_2D, format = GL_ALPHA, type = GL_UNSIGNED_BYTE; + GLint level = 0, border = 0; + glsafe(::glBindTexture(target, m_input.texture_id)); + + GLint + w = m_tex_size.x(), h = m_tex_size.y(), + xoffset = m_input.size.x() - m_tex_size.x(), // arrange right + yoffset = m_input.size.y() * m_input.index; + glsafe(::glTexSubImage2D(target, level, xoffset, yoffset, w, h, format, type, m_result.data())); + + // bind default texture + GLuint no_texture_id = 0; + glsafe(::glBindTexture(target, no_texture_id)); +} \ No newline at end of file diff --git a/src/slic3r/GUI/Jobs/CreateFontNameImageJob.hpp b/src/slic3r/GUI/Jobs/CreateFontNameImageJob.hpp new file mode 100644 index 000000000..5c001fbc9 --- /dev/null +++ b/src/slic3r/GUI/Jobs/CreateFontNameImageJob.hpp @@ -0,0 +1,62 @@ +#ifndef slic3r_CreateFontNameImageJob_hpp_ +#define slic3r_CreateFontNameImageJob_hpp_ + +#include +#include +#include +#include +#include +#include "Job.hpp" + +namespace Slic3r::GUI { + +/// +/// Keep data for rasterization of text by font face +/// +struct FontImageData +{ + // Text to rasterize + std::string text; + // Define font face + wxString font_name; + wxFontEncoding encoding; + // texture for copy result to + GLuint texture_id; + // Index of face name, define place in texture + size_t index; + // Height of each text + // And Limit for width + Vec2i size; // in px + + // bigger value create darker image + // divide value 255 + unsigned char gray_level = 5; +}; + +/// +/// Create image for face name +/// +class CreateFontImageJob : public Job +{ + FontImageData m_input; + std::vector m_result; + Point m_tex_size; +public: + CreateFontImageJob(FontImageData &&input); + /// + /// Rasterize text into image (result) + /// + /// Check for cancelation + void process(Ctl &ctl) override; + + /// + /// Copy image data into OpenGL texture + /// + /// + /// + void finalize(bool canceled, std::exception_ptr &); +}; + +} // namespace Slic3r::GUI + +#endif // slic3r_CreateFontNameImageJob_hpp_ diff --git a/src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.hpp b/src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.hpp index e66e66ce2..eba6364b4 100644 --- a/src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.hpp +++ b/src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.hpp @@ -9,7 +9,6 @@ namespace Slic3r::GUI { - /// /// Create texture with name of styles written by its style /// NOTE: Access to glyph cache is possible only from job @@ -34,4 +33,4 @@ public: } // namespace Slic3r::GUI -#endif // slic3r_EmbossJob_hpp_ +#endif // slic3r_CreateFontStyleImagesJob_hpp_