Add Windows font list

Add User edited letter and line spacing
Fix polygon point orders
Fix Letter intersection
This commit is contained in:
Filip Sykala 2021-09-07 13:02:04 +02:00
parent 84488ba6df
commit 578abe4cce
4 changed files with 287 additions and 96 deletions

View file

@ -1,12 +1,23 @@
#include "Emboss.hpp"
#include <stdio.h>
#include <cstdlib>
#include <boost/nowide/convert.hpp>
#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::Glyph> 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::Glyph> 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<std::wstring> Emboss::get_font_path(const std::wstring &font_face_
return wsFontFile;
}
// family-name, file-path;
using FontInfo = std::pair<std::wstring, std::wstring>;
using FontList = std::vector<FontInfo>;
bool CALLBACK EnumFamCallBack(LPLOGFONT lplf,
LPNEWTEXTMETRIC lpntm,
DWORD FontType,
LPVOID aFontList)
{
std::vector<std::wstring> *fontList = (std::vector<std::wstring> *) (aFontList);
if (FontType & TRUETYPE_FONTTYPE) {
std::wstring name = lplf->lfFaceName;
fontList->push_back(name);
}
return true;
//UNREFERENCED_PARAMETER(lplf);
UNREFERENCED_PARAMETER(lpntm);
}
#include <commdlg.h>
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 <wx/fontdlg.h>
HDC hDC = GetDC(NULL);
std::vector<std::wstring> 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<std::wstring> *fontList =
(std::vector<std::wstring> *) (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<std::wstring> 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 <boost\nowide\convert.hpp>
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<int>(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 <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Constrained_Delaunay_triangulation_2.h>
#include <CGAL/Triangulation_vertex_base_with_info_2.h>
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<std::pair<uint32_t, uint32_t>> 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);

View file

@ -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<FontItem>;
/// <summary>
/// Collect fonts registred inside OS
/// </summary>
static void get_font_list();
/// <returns>OS resistred TTF font files(full path) with names</returns>
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
/// <summary>
/// OS dependent function to get location of font by its name
/// OS dependent function to get location of font by its name descriptor
/// </summary>
/// <param name="font_face_name">Unique identificator for font</param>
/// <returns>File path to font when found</returns>
@ -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;
};
/// <summary>
@ -63,7 +88,7 @@ public:
/// <param name="font">Define fonts</param>
/// <param name="letter">One character to convert</param>
/// <param name="flatness">Precision of lettter outline curve in conversion to lines</param>
/// <returns>inner polygon ccw(outer cw)</returns>
/// <returns>inner polygon cw(outer ccw)</returns>
static Polygons letter2polygons(const Font &font, char letter, float flatness);
/// <summary>
@ -71,9 +96,9 @@ public:
/// </summary>
/// <param name="font">Define fonts</param>
/// <param name="text">Characters to convert</param>
/// <param name="flatness">Precision of lettter outline curve in conversion to lines</param>
/// <returns>Inner polygon ccw(outer cw)</returns>
static Polygons text2polygons(const Font &font, const char *text, float flatness);
/// <param name="font_prop">User defined property of font</param>
/// <returns>Inner polygon cw(outer ccw)</returns>
static Polygons text2polygons(const Font &font, const char *text, const FontProp& font_prop);
/// <summary>
/// Project 2d point into space

View file

@ -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 == &current)) {
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 == &current)) {
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<Emboss::ProjectScale>(
std::make_unique<Emboss::ProjectZ>(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<size_t> 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

View file

@ -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<GuiCfg> 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<MyFont> 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<Emboss::Font> m_font;
size_t m_text_size;
std::unique_ptr<char[]> m_text;
Emboss::FontProp m_font_prop;
float m_scale;
float m_emboss;
float m_flatness;
};
} // namespace GUI