From 578abe4ccee23a26d883957a8ed13528486b6d37 Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Tue, 7 Sep 2021 13:02:04 +0200 Subject: [PATCH] Add Windows font list Add User edited letter and line spacing Fix polygon point orders Fix Letter intersection --- src/libslic3r/Emboss.cpp | 230 ++++++++++++++++++------ src/libslic3r/Emboss.hpp | 41 ++++- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 91 ++++++++-- src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 21 +-- 4 files changed, 287 insertions(+), 96 deletions(-) diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index 62abc125ca..e27ad54717 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -1,12 +1,23 @@ #include "Emboss.hpp" #include #include +#include #define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation #include "imgui/imstb_truetype.h" // stbtt_fontinfo using namespace Slic3r; +Emboss::FontItem::FontItem(const std::string &name, const std::string &path) + : name(name) + , path(path) +{} + +Emboss::FontItem::FontItem(const std::wstring &name, const std::wstring &path) + : name(boost::nowide::narrow(name.c_str())) + , path(boost::nowide::narrow(path.c_str())) +{} + // do not expose out of this file stbtt_ data types class Privat { @@ -64,8 +75,12 @@ std::optional Privat::get_glyph(stbtt_fontinfo &font_info, int un size_t pi = 0; // point index for (size_t ci = 0; ci < num_countour; ++ci) { int length = contour_lengths[ci]; - // minimal length for triangle - assert(length >= 4); + // check minimal length for triangle + if (length < 4) { + // weird font + pi+=length; + continue; + } // last point is first point --length; Points pts; @@ -78,11 +93,15 @@ std::optional Privat::get_glyph(stbtt_fontinfo &font_info, int un // last point is first point assert(pts.front() == Point(points[pi].x, points[pi].y)); ++pi; + + // change outer cw to ccw and inner ccw to cw order + std::reverse(pts.begin(), pts.end()); + glyph.polygons.emplace_back(pts); } - // inner ccw - // outer cw + // inner cw - hole + // outer ccw - contour return glyph; } @@ -155,25 +174,6 @@ std::optional Emboss::get_font_path(const std::wstring &font_face_ return wsFontFile; } -// family-name, file-path; -using FontInfo = std::pair; -using FontList = std::vector; - -bool CALLBACK EnumFamCallBack(LPLOGFONT lplf, - LPNEWTEXTMETRIC lpntm, - DWORD FontType, - LPVOID aFontList) -{ - std::vector *fontList = (std::vector *) (aFontList); - if (FontType & TRUETYPE_FONTTYPE) { - std::wstring name = lplf->lfFaceName; - fontList->push_back(name); - } - return true; - //UNREFERENCED_PARAMETER(lplf); - UNREFERENCED_PARAMETER(lpntm); -} - #include void choose_font_dlg() { HWND hwnd = (HWND)GetFocus(); // owner window @@ -217,26 +217,146 @@ void get_OS_font() << std::endl; } -void Emboss::get_font_list() { - get_OS_font(); - choose_font_dlg(); +//#include - HDC hDC = GetDC(NULL); - std::vector font_names; - EnumFontFamilies(hDC, (LPCTSTR) NULL, (FONTENUMPROC) EnumFamCallBack, (LPARAM) &font_names); +Emboss::FontList Emboss::get_font_list() +{ + //auto a = get_font_path(L"none"); + //get_OS_font(); + //choose_font_dlg(); + //FontList list1 = get_font_list_by_enumeration(); + //FontList list2 = get_font_list_by_register(); + //FontList list3 = get_font_list_by_folder(); + return get_font_list_by_register(); +} - FontList font_list; - for (const std::wstring &font_name : font_names) { - std::cout << "Font name: "; - std::wcout << font_name; - //auto font_path_opt = get_font_path(font_name); - //if (font_path_opt.has_value()) { - // std::wcout << " path: "<< *font_path_opt; - //} - std::cout << std::endl; - //font_list.emplace_back(font_name, ) +bool exists_file(const std::wstring &name) +{ + if (FILE *file = _wfopen(name.c_str(), L"r")) { + fclose(file); + return true; + } else { + return false; } } + +Emboss::FontList Emboss::get_font_list_by_register() { + static const LPWSTR fontRegistryPath = L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"; + HKEY hKey; + LONG result; + + // 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; + return {}; + } + + DWORD maxValueNameSize, maxValueDataSize; + result = RegQueryInfoKey(hKey, 0, 0, 0, 0, 0, 0, 0, &maxValueNameSize, + &maxValueDataSize, 0, 0); + if (result != ERROR_SUCCESS) { + std::cerr << "Can not earn query key, function 'RegQueryInfoKey' return code: " + << result << std::endl; + return {}; + } + + // Build full font file path + WCHAR winDir[MAX_PATH]; + GetWindowsDirectory(winDir, MAX_PATH); + std::wstring font_path = std::wstring(winDir) + L"\\Fonts\\"; + + FontList font_list; + DWORD valueIndex = 0; + // Look for a matching font name + LPWSTR font_name = new WCHAR[maxValueNameSize]; + LPBYTE fileTTF_name = new BYTE[maxValueDataSize]; + DWORD font_name_size, fileTTF_name_size, valueType; + do { + fileTTF_name_size = maxValueDataSize; + font_name_size = maxValueNameSize; + + result = RegEnumValue(hKey, valueIndex, font_name, &font_name_size, 0, + &valueType, fileTTF_name, &fileTTF_name_size); + valueIndex++; + if (result != ERROR_SUCCESS || valueType != REG_SZ) continue; + std::wstring font_name_w(font_name, font_name_size); + std::wstring file_name_w((LPWSTR) fileTTF_name, fileTTF_name_size); + std::wstring path_w = font_path + file_name_w; + + // filtrate .fon from lists + size_t pos = font_name_w.rfind(L" (TrueType)"); + if (pos >= font_name_w.size()) continue; + // remove TrueType text from name + font_name_w = std::wstring(font_name_w, 0, pos); + font_list.emplace_back(font_name_w, path_w); + } while (result != ERROR_NO_MORE_ITEMS); + delete[] font_name; + delete[] fileTTF_name; + + RegCloseKey(hKey); + return font_list; +} + +// TODO: Fix global function +bool CALLBACK EnumFamCallBack(LPLOGFONT lplf, + LPNEWTEXTMETRIC lpntm, + DWORD FontType, + LPVOID aFontList) +{ + std::vector *fontList = + (std::vector *) (aFontList); + if (FontType & TRUETYPE_FONTTYPE) { + std::wstring name = lplf->lfFaceName; + fontList->push_back(name); + } + return true; + // UNREFERENCED_PARAMETER(lplf); + UNREFERENCED_PARAMETER(lpntm); +} + +Emboss::FontList Emboss::get_font_list_by_enumeration() { + + HDC hDC = GetDC(NULL); + std::vector font_names; + EnumFontFamilies(hDC, (LPCTSTR) NULL, (FONTENUMPROC) EnumFamCallBack, + (LPARAM) &font_names); + + FontList font_list; + for (const std::wstring &font_name : font_names) { + font_list.emplace_back(font_name, L""); + } + return font_list; +} + +Emboss::FontList Emboss::get_font_list_by_folder() { + FontList result; + WCHAR winDir[MAX_PATH]; + UINT winDir_size = GetWindowsDirectory(winDir, MAX_PATH); + std::wstring search_dir = std::wstring(winDir, winDir_size) + L"\\Fonts\\"; + WIN32_FIND_DATA fd; + HANDLE hFind; + auto iterate_files = [&hFind, &fd, &search_dir, &result]() { + if (hFind == INVALID_HANDLE_VALUE) return; + // read all (real) files in current folder + do { + // skip folder . and .. + if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue; + std::wstring file_name(fd.cFileName); + // TODO: find font name instead of filename + result.emplace_back(file_name, search_dir + file_name); + } while (::FindNextFile(hFind, &fd)); + ::FindClose(hFind); + }; + + hFind = ::FindFirstFile((search_dir + L"*.ttf").c_str(), &fd); + iterate_files(); + hFind = ::FindFirstFile((search_dir + L"*.ttc").c_str(), &fd); + iterate_files(); + return result; +} + #else void Emboss::get_font_list() {} @@ -301,11 +421,12 @@ Polygons Emboss::letter2polygons(const Font &font, char letter, float flatness) auto glyph_opt = Privat::get_glyph(*font_info_opt, (int) letter, flatness); if (!glyph_opt.has_value()) return Polygons(); - return glyph_opt->polygons; + return union_(glyph_opt->polygons); } -#include -Polygons Emboss::text2polygons(const Font &font, const char *text, float flatness) +Polygons Emboss::text2polygons(const Font & font, + const char * text, + const FontProp &font_prop) { auto font_info_opt = Privat::load_font_info(font); if (!font_info_opt.has_value()) return Polygons(); @@ -318,11 +439,11 @@ Polygons Emboss::text2polygons(const Font &font, const char *text, float flatnes for (wchar_t wc: ws){ if (wc == '\n') { cursor.x() = 0; - cursor.y() -= font.ascent - font.descent + font.linegap; + cursor.y() -= font.ascent - font.descent + font.linegap + font_prop.line_gap; continue; } int unicode = static_cast(wc); - auto glyph_opt = Privat::get_glyph(*font_info_opt, unicode, flatness); + auto glyph_opt = Privat::get_glyph(*font_info_opt, unicode, font_prop.flatness); if (!glyph_opt.has_value()) continue; // move glyph to cursor position @@ -330,11 +451,11 @@ Polygons Emboss::text2polygons(const Font &font, const char *text, float flatnes for (Polygon &polygon : polygons) for (Point &p : polygon.points) p += cursor; - cursor.x() += glyph_opt->advance_width; + cursor.x() += glyph_opt->advance_width + font_prop.char_gap; polygons_append(result, polygons); } - return result; + return union_(result); } indexed_triangle_set Emboss::polygons2model(const Polygons &shape2d, @@ -394,8 +515,7 @@ indexed_triangle_set Emboss::polygons2model(const Polygons &shape2d, #include #include #include -Emboss::Indices Emboss::triangulate(const Points & points, - const HalfEdges &half_edges) +Emboss::Indices Emboss::triangulate(const Points &points, const HalfEdges &half_edges) { // IMPROVE use int point insted of float !!! @@ -436,9 +556,9 @@ Emboss::Indices Emboss::triangulate(const Points & points, pi[i] = map[face->vertex(i)]; // Do not use triangles with opposit edges - if (half_edges.find(std::make_pair(pi[0], pi[1])) != half_edges.end()) continue; - if (half_edges.find(std::make_pair(pi[1], pi[2])) != half_edges.end()) continue; - if (half_edges.find(std::make_pair(pi[2], pi[0])) != half_edges.end()) continue; + if (half_edges.find(std::make_pair(pi[1], pi[0])) != half_edges.end()) continue; + if (half_edges.find(std::make_pair(pi[2], pi[1])) != half_edges.end()) continue; + if (half_edges.find(std::make_pair(pi[0], pi[2])) != half_edges.end()) continue; indices.emplace_back(pi[0], pi[1], pi[2]); } @@ -447,7 +567,7 @@ Emboss::Indices Emboss::triangulate(const Points & points, Emboss::Indices Emboss::triangulate(const Polygon &polygon) { - const Points & pts = polygon.points; + const Points &pts = polygon.points; std::set> edges; for (uint32_t i = 1; i < pts.size(); ++i) edges.insert({i - 1, i}); edges.insert({(uint32_t)pts.size() - 1, uint32_t(0)}); @@ -494,7 +614,7 @@ void Emboss::remove_outer(Indices &indices, const HalfEdges &half_edges) { bool is_border = false; for (size_t j = 0; j < 3; ++j) { size_t j2 = (j == 0) ? 2 : (j - 1); - HalfEdge he(t[j], t[j2]); + HalfEdge he(t[j2], t[j]); if (half_edges.find(he) != half_edges.end()) is_border = true; else @@ -516,7 +636,7 @@ void Emboss::remove_outer(Indices &indices, const HalfEdges &half_edges) { for (size_t j = 0; j < 3; ++j) { size_t j2 = (j == 0) ? 2 : (j - 1); // opposit - HalfEdge he(t[j2], t[j]); + HalfEdge he(t[j], t[j2]); if (edge2triangle.find(he) == edge2triangle.end()) is_edge = true; } @@ -532,7 +652,7 @@ void Emboss::remove_outer(Indices &indices, const HalfEdges &half_edges) { for (size_t j = 0; j < 3; ++j) { size_t j2 = (j == 0) ? 2 : (j - 1); // opposit - HalfEdge he(t[j2], t[j]); + HalfEdge he(t[j], t[j2]); auto it = edge2triangle.find(he); if (it == edge2triangle.end()) continue; // edge insert.push(it->second); diff --git a/src/libslic3r/Emboss.hpp b/src/libslic3r/Emboss.hpp index 6e5936cea2..c334a34dac 100644 --- a/src/libslic3r/Emboss.hpp +++ b/src/libslic3r/Emboss.hpp @@ -19,13 +19,28 @@ class Emboss public: Emboss() = delete; + struct FontItem + { + std::string name; + std::string path; + FontItem(const std::string &name, const std::string &path); + FontItem(const std::wstring &name, const std::wstring &path); + }; + using FontList = std::vector; + /// /// Collect fonts registred inside OS /// - static void get_font_list(); + /// OS resistred TTF font files(full path) with names + static FontList get_font_list(); +#ifdef _WIN32 + static FontList get_font_list_by_register(); + static FontList get_font_list_by_enumeration(); + static FontList get_font_list_by_folder(); +#endif /// - /// OS dependent function to get location of font by its name + /// OS dependent function to get location of font by its name descriptor /// /// Unique identificator for font /// File path to font when found @@ -44,10 +59,20 @@ public: // vertical position is "scale*(ascent - descent + lineGap)" int ascent=0, descent=0, linegap=0; - // user defined unscaled char space - int extra_char_space = 0; + }; + // user defined font property + struct FontProp + { + // define extra space between letters, negative mean closer letter + int char_gap = 0; + // define extra space between lines, negative mean closer lines + int line_gap = 0; + // Precision of lettter outline curve in conversion to lines + float flatness = 2.0; // TODO: add enum class Align: center/left/right + + FontProp() = default; }; /// @@ -63,7 +88,7 @@ public: /// Define fonts /// One character to convert /// Precision of lettter outline curve in conversion to lines - /// inner polygon ccw(outer cw) + /// inner polygon cw(outer ccw) static Polygons letter2polygons(const Font &font, char letter, float flatness); /// @@ -71,9 +96,9 @@ public: /// /// Define fonts /// Characters to convert - /// Precision of lettter outline curve in conversion to lines - /// Inner polygon ccw(outer cw) - static Polygons text2polygons(const Font &font, const char *text, float flatness); + /// User defined property of font + /// Inner polygon cw(outer ccw) + static Polygons text2polygons(const Font &font, const char *text, const FontProp& font_prop); /// /// Project 2d point into space diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index f665b7feac..d3d85b3ab3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -14,21 +14,31 @@ GLGizmoEmboss::GLGizmoEmboss(GLCanvas3D & parent, const std::string &icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) - , m_fonts({ + , m_font_list({ {"NotoSans Regular", Slic3r::resources_dir() + "/fonts/NotoSans-Regular.ttf"}, - {"NotoSans CJK", Slic3r::resources_dir() + "/fonts/NotoSansCJK-Regular.ttc"}, - //{"Font Awsome", Slic3r::resources_dir() + "/fonts/fa-solid-900.ttf"}, - {"Arial", "C:/windows/fonts/arialbd.ttf"}}) - , m_fonts_selected(0) + {"NotoSans CJK", Slic3r::resources_dir() + "/fonts/NotoSansCJK-Regular.ttc"}}) + , m_font_selected(0) , m_text_size(255) , m_text(new char[m_text_size]) , m_scale(0.01f) , m_emboss(5.f) - , m_flatness(2.f) { - load_font(); // TODO: suggest to use https://fontawesome.com/ // (copy & paste) unicode symbols from web + + bool is_font_loaded = load_font(); + add_fonts(Emboss::get_font_list()); + + if (!is_font_loaded) { + // can't load so erase it from list + m_font_list.erase(m_font_list.begin() + m_font_selected); + m_font_selected = 0; // select first + do{ + is_font_loaded = load_font(); + if (!is_font_loaded) m_font_list.erase(m_font_list.begin()); + } while (!is_font_loaded && !m_font_list.empty()); + } + int index = 0; for (char &c : _u8L("Embossed text")) { m_text[index++] = c; } m_text[index] = '\0'; @@ -57,13 +67,23 @@ void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit) ImGuiWindowFlags_NoCollapse; m_imgui->begin(on_get_name(), flag); - auto& current = m_fonts[m_fonts_selected]; + size_t max_font_name = 20; // count characters + + auto& current = m_font_list[m_font_selected]; if (ImGui::BeginCombo("##font_selector", current.name.c_str())) { - for (const MyFont &f : m_fonts) { + for (const Emboss::FontItem &f : m_font_list) { ImGui::PushID((void*)&f.name); - if (ImGui::Selectable(f.name.c_str(), &f == ¤t)) { - m_fonts_selected = &f - &m_fonts.front(); - load_font(); + std::string name = (f.name.size() < max_font_name) ? + f.name : (f.name.substr(0,max_font_name - 3) + " .."); + if (ImGui::Selectable(name.c_str(), &f == ¤t)) { + m_font_selected = &f - &m_font_list.front(); + load_font(); + process(); + } + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::Text((f.name + " " + f.path).c_str()); + ImGui::EndTooltip(); } ImGui::PopID(); } @@ -88,7 +108,10 @@ void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit) ImGui::InputFloat("Scale", &m_scale); ImGui::InputFloat("Emboss", &m_emboss); - ImGui::InputFloat("Flatness", &m_flatness); + ImGui::InputFloat("Flatness", &m_font_prop.flatness); + ImGui::InputInt("CharGap", &m_font_prop.char_gap); + ImGui::InputInt("LineGap", &m_font_prop.line_gap); + m_imgui->disabled_begin(!m_font.has_value()); if (ImGui::Button("Preview")) process(); m_imgui->disabled_end(); @@ -163,7 +186,7 @@ void GLGizmoEmboss::process() { auto project = std::make_unique( std::make_unique(m_emboss/m_scale), m_scale); - Polygons polygons = Emboss::text2polygons(*m_font, m_text.get(), m_flatness); + Polygons polygons = Emboss::text2polygons(*m_font, m_text.get(), m_font_prop); if (polygons.empty()) return; indexed_triangle_set its = Emboss::polygons2model(polygons, *project); @@ -204,15 +227,17 @@ void GLGizmoEmboss::draw_add_button() { if (dialog.ShowModal() == wxID_OK) dialog.GetPaths(input_files); if (input_files.IsEmpty()) return; + Emboss::FontList font_list; + font_list.reserve(input_files.size()); for (auto &input_file : input_files) { std::string name = input_file.AfterLast('\\').c_str(); std::string path = input_file.c_str(); - m_fonts.emplace_back(name, path); + font_list.emplace_back(name, path); } // set last added font as active - m_fonts_selected = m_fonts.size() - 1; + m_font_selected = m_font_list.size() - 1; + add_fonts(font_list); load_font(); - //load_files(input_files); } if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); @@ -221,10 +246,38 @@ void GLGizmoEmboss::draw_add_button() { } } -void GLGizmoEmboss::load_font() +bool GLGizmoEmboss::load_font() { - auto font_path = m_fonts[m_fonts_selected].file_path.c_str(); + auto font_path = m_font_list[m_font_selected].path.c_str(); m_font = Emboss::load_font(font_path); + return m_font.has_value(); } +void GLGizmoEmboss::sort_fonts() { + // initialize original index locations + std::vector idx(m_font_list.size()); + std::iota(idx.begin(), idx.end(), 0); + + std::stable_sort(idx.begin(), idx.end(), + [this](size_t i1, size_t i2) { + return m_font_list[i1].name < m_font_list[i2].name; + }); + + Emboss::FontList font_list; + font_list.reserve(m_font_list.size()); + size_t selected = 0; + for (const size_t &i : idx) { + if (i == m_font_selected) selected = &i - &idx.front(); + font_list.emplace_back(m_font_list[i]); + } + m_font_list = font_list; + m_font_selected = selected; +} + +void GLGizmoEmboss::add_fonts(const Emboss::FontList &font_list) { + m_font_list.insert(m_font_list.end(), font_list.begin(), font_list.end()); + sort_fonts(); +} + + } // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index e8db96bf89..7e433da0cc 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -33,7 +33,9 @@ private: void process(); void close(); void draw_add_button(); - void load_font(); + bool load_font(); + void sort_fonts(); + void add_fonts(const Emboss::FontList &font_list); // This configs holds GUI layout size given by translated texts. // etc. When language changes, GUI is recreated and this class constructed again, @@ -44,27 +46,18 @@ private: }; std::optional m_gui_cfg; - struct MyFont - { - std::string name; - std::string file_path; - - MyFont(const std::string &name, const std::string &file_path) - : name(name), file_path(file_path) - {} - }; - - std::vector m_fonts; - size_t m_fonts_selected;// index to m_fonts + Emboss::FontList m_font_list; + size_t m_font_selected;// index to m_font_list std::optional m_font; size_t m_text_size; std::unique_ptr m_text; + Emboss::FontProp m_font_prop; + float m_scale; float m_emboss; - float m_flatness; }; } // namespace GUI