Store font list into App configuration

This commit is contained in:
Filip Sykala 2021-10-05 16:10:24 +02:00
parent 976407fdb1
commit ffba045734
7 changed files with 276 additions and 105 deletions

View File

@ -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_FILAMENTS = "filaments";
const std::string AppConfig::SECTION_MATERIALS = "sla_materials"; const std::string AppConfig::SECTION_MATERIALS = "sla_materials";
const std::string AppConfig::SECTION_EMBOSS = "emboss";
void AppConfig::reset() void AppConfig::reset()
{ {

View File

@ -168,6 +168,7 @@ public:
static const std::string SECTION_FILAMENTS; static const std::string SECTION_FILAMENTS;
static const std::string SECTION_MATERIALS; static const std::string SECTION_MATERIALS;
static const std::string SECTION_EMBOSS;
private: private:
template<typename T> template<typename T>

View File

@ -57,16 +57,17 @@ std::optional<Emboss::Glyph> Privat::get_glyph(stbtt_fontinfo &font_info, int un
if (num_verts <= 0) return glyph; // no shape if (num_verts <= 0) return glyph; // no shape
int *contour_lengths = NULL; int *contour_lengths = NULL;
int num_countour = 0; int num_countour_int = 0;
stbtt__point *points = stbtt_FlattenCurves(vertices, num_verts, 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<size_t>(num_countour_int);
Polygons glyph_polygons; Polygons glyph_polygons;
glyph_polygons.reserve(num_countour); glyph_polygons.reserve(num_contour);
size_t pi = 0; // point index size_t pi = 0; // point index
for (size_t ci = 0; ci < num_countour; ++ci) { for (size_t ci = 0; ci < num_contour; ++ci) {
int length = contour_lengths[ci]; int length = contour_lengths[ci];
// check minimal length for triangle // check minimal length for triangle
if (length < 4) { if (length < 4) {
// weird font // weird font
@ -77,9 +78,8 @@ std::optional<Emboss::Glyph> Privat::get_glyph(stbtt_fontinfo &font_info, int un
--length; --length;
Points pts; Points pts;
pts.reserve(length); pts.reserve(length);
for (size_t i = 0; i < length; ++i) { for (int i = 0; i < length; ++i) {
const stbtt__point &point = points[pi]; const stbtt__point &point = points[pi++];
++pi;
pts.emplace_back(point.x, point.y); pts.emplace_back(point.x, point.y);
} }
// last point is first point // last point is first point

View File

@ -15,94 +15,138 @@ const char TextConfigurationSerialization::separator = '|';
std::string TextConfigurationSerialization::serialize(const TextConfiguration &text_configuration) std::string TextConfigurationSerialization::serialize(const TextConfiguration &text_configuration)
{ {
// IMPROVE: make general and move to string utils size_t size = 1 + 3 + 5;
auto twice_separator = [](const std::string& data) { std::vector<std::string> columns;
// no value is one space columns.reserve(size);
if (data.empty()) return std::string(" "); columns.emplace_back(text_configuration.text);
std::string::size_type pos = data.find(separator); to_columns(text_configuration.font_item, columns);
if (pos == data.npos) return data; to_columns(text_configuration.font_prop, columns);
// twice all separator inside data assert(columns.size() == size);
std::string copy = data; return serialize(columns, separator);
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);
} }
std::optional<TextConfiguration> TextConfigurationSerialization::deserialize(const std::string &data) std::optional<TextConfiguration> TextConfigurationSerialization::deserialize(const std::string &data)
{ {
// IMPROVE: make general and move to string utils size_t size = 1 + 3 + 5;
auto reduce_separator = [](const std::string& item) { std::vector<std::string> columns;
std::string::size_type pos = item.find(separator); columns.reserve(size);
if (pos == item.npos) return item; deserialize(data, separator, columns);
std::string copy = item; assert(columns.size() == size);
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;
};
std::string::size_type start = 0; const std::string& text = columns[0];
auto get_column_and_move = [&data, &start](){ std::optional<FontItem> font_item = get_font_item(columns, 1);
// IMPROVE: make function general and move to string utils if (!font_item.has_value()) return {};
auto find_separator = [&data](std::string::size_type pos) { std::optional<FontProp> font_prop = get_font_prop(columns, 4);
pos = data.find(separator, pos); if (!font_prop.has_value()) return {};
while (pos != data.npos && data[pos + 1] == separator)
pos = data.find(separator, pos + 2);
return pos;
};
if (start == data.npos) return std::string(); return TextConfiguration(*font_item, *font_prop, text);
std::string::size_type size = find_separator(start) - start; }
if (size == 0) return std::string();
std::string result = data.substr(start, size); std::string TextConfigurationSerialization::serialize(const FontList &font_list)
// move start column to next position {
start += size + 1; std::vector<std::string> columns;
return result; columns.reserve(3 * font_list.size());
}; for (const FontItem &fi : font_list) to_columns(fi, columns);
return serialize(columns, separator);
auto get_float_and_move = [&get_column_and_move]() { }
std::string column = get_column_and_move();
if (column.empty()) return 0.f; FontList TextConfigurationSerialization::deserialize_font_list(const std::string &data)
return static_cast<float>(std::atof(column.c_str())); {
}; std::vector<std::string> columns;
auto get_int_and_move = [&get_column_and_move]() { deserialize(data, separator, columns);
std::string column = get_column_and_move(); if ((columns.size() % 3) != 0) return {};
if (column.empty()) return 0; size_t count = columns.size() / 3;
return std::atoi(column.c_str()); FontList fl;
}; fl.reserve(count);
for (size_t i = 0; i < count; i++)
std::string text = reduce_separator(get_column_and_move()); {
std::string name = reduce_separator(get_column_and_move()); std::optional<FontItem> fi = get_font_item(columns, i * 3);
std::string path = reduce_separator(get_column_and_move()); if (!fi.has_value()) return {};
std::string type = reduce_separator(get_column_and_move()); fl.emplace_back(*fi);
auto it = to_type.find(type); }
if (it == to_type.end()) return {}; // no valid type return fl;
FontItem font_item(name,path,it->second); }
FontProp font_prop; std::string TextConfigurationSerialization::serialize(const std::vector<std::string> &columns, char separator)
font_prop.emboss = get_float_and_move(); {
font_prop.flatness = get_float_and_move(); std::string result;
font_prop.size_in_mm = get_float_and_move(); const std::string separator_str = std::string(" ") + separator + ' ';
font_prop.char_gap = get_int_and_move(); bool is_first = true;
if (start == data.npos) return {}; // no valid data for (const std::string& column : columns) {
font_prop.line_gap = get_int_and_move(); if (is_first) is_first = false;
return TextConfiguration(font_item, font_prop, text); else result += separator_str;
result += twice(column, separator);
}
return result;
}
void TextConfigurationSerialization::deserialize(const std::string &data, char separator, std::vector<std::string> &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<std::string> &columns)
{
columns.emplace_back(font_item.name);
columns.emplace_back(font_item.path);
columns.emplace_back(to_string.at(font_item.type));
}
std::optional<FontItem> TextConfigurationSerialization::get_font_item(
const std::vector<std::string> &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<std::string> &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<FontProp> TextConfigurationSerialization::get_font_prop(
const std::vector<std::string> &columns, size_t offset)
{
if (columns.size() <= (offset + 4)) return {}; // no enough columns
FontProp font_prop;
font_prop.emboss = static_cast<float>(std::atof(columns[offset].c_str()));
font_prop.flatness = static_cast<float>(std::atof(columns[offset+1].c_str()));
font_prop.size_in_mm = static_cast<float>(std::atof(columns[offset+2].c_str()));
font_prop.char_gap = static_cast<float>(std::atof(columns[offset+3].c_str()));
font_prop.line_gap = std::atoi(columns[offset+4].c_str());
return font_prop;
} }

View File

@ -14,16 +14,100 @@ class TextConfigurationSerialization
{ {
public: public:
TextConfigurationSerialization() = delete; // only static functions TextConfigurationSerialization() = delete; // only static functions
// store / load TextConfiguration - .3mf files
static std::string serialize(const TextConfiguration &text_configuration); static std::string serialize(const TextConfiguration &text_configuration);
static std::optional<TextConfiguration> deserialize(const std::string &data); static std::optional<TextConfiguration> 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 // convert type to string and vice versa
static const std::map<std::string, FontItem::Type> to_type; static const std::map<std::string, FontItem::Type> to_type;
static const std::map<FontItem::Type, std::string> to_string; static const std::map<FontItem::Type, std::string> to_string;
static const char separator; static const char separator;
// Move to map utils // store / load general connection of string into one string
static std::string serialize(const std::vector<std::string> &columns, char separator);
static void deserialize(const std::string& data, char separator, std::vector<std::string> &columns);// columns vector should be reserved on valid count
static void to_columns(const FontItem& font_item, std::vector<std::string> &columns);
static std::optional<FontItem> get_font_item(const std::vector<std::string> &columns, size_t offset);
static void to_columns(const FontProp& font_prop, std::vector<std::string> &columns);
static std::optional<FontProp> get_font_prop(const std::vector<std::string> &columns, size_t offset);
/// <summary>
/// Twice all appearance of character in data.
/// Twiced character could be used as separator for this data.
/// IMPROVE: move to string utils
/// </summary>
/// <param name="data">input data to twice character</param>
/// <param name="letter">Specify character to be twiced in data</param>
/// <returns>String conatin only pair continous count of specified character</returns>
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;
};
/// <summary>
/// Reduce all twice appearance of character in data.
/// Main purpose heal after twice function.
/// IMPROVE: move to string utils
/// </summary>
/// <param name="data">input data to reduce character</param>
/// <param name="letter">Specify character to be reduced</param>
/// <returns>String conatining only half letter in row</returns>
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;
};
/// <summary>
/// Find odd position of letter in text data
/// Used with combination of twice and reduce
/// IMPROVE: move to string utils
/// </summary>
/// <param name="data">Input text</param>
/// <param name="pos">Start index into data for searching odd letter</param>
/// <param name="letter">Character to find</param>
/// <returns>Index to data with next odd appearance of letter
/// OR size of data when NO next one exists</returns>
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;
}
/// <summary>
/// Create map with swaped key-value
/// IMPROVE: Move to map utils
/// </summary>
/// <param name="map">Input map</param>
/// <returns>Map with swapped key-value</returns>
template<typename Key, typename Value> template<typename Key, typename Value>
static std::map<Value, Key> create_oposit_map( static std::map<Value, Key> create_oposit_map(
const std::map<Key, Value> &map) const std::map<Key, Value> &map)

View File

@ -13,6 +13,8 @@
#include "libslic3r/Model.hpp" #include "libslic3r/Model.hpp"
#include "libslic3r/ClipperUtils.hpp" // union_ex #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 "imgui/imgui_stdlib.h" // using std::string for inputs
#include "nanosvg/nanosvg.h" // load SVG file #include "nanosvg/nanosvg.h" // load SVG file
@ -77,7 +79,7 @@ GLGizmoEmboss::GLGizmoEmboss(GLCanvas3D &parent)
, m_volume_type(ModelVolumeType::MODEL_PART) , m_volume_type(ModelVolumeType::MODEL_PART)
, m_is_initialized(false) // initialize on first opening gizmo , 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 // (copy & paste) unicode symbols from web
} }
@ -152,16 +154,10 @@ void GLGizmoEmboss::initialize()
if (m_is_initialized) return; if (m_is_initialized) return;
m_is_initialized = true; m_is_initialized = true;
load_font_list();
m_gui_cfg.emplace(GuiCfg()); 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; m_font_selected = 0;
bool is_font_loaded = load_font(); bool is_font_loaded = load_font();
@ -177,6 +173,37 @@ void GLGizmoEmboss::initialize()
set_default_configuration(); 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<FontList> 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() { void GLGizmoEmboss::set_default_configuration() {
m_text = _u8L("Embossed text"); m_text = _u8L("Embossed text");
m_font_prop = FontProp(); 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 * vol_gl = selection.get_volume(vol_id_gl);
const GLVolume::CompositeID &id = vol_gl->composite_id; const GLVolume::CompositeID &id = vol_gl->composite_id;
if (id.object_id >= objects.size()) return nullptr; if (id.object_id < 0 || static_cast<size_t>(id.object_id) >= objects.size())
return nullptr;
ModelObject *object = objects[id.object_id]; ModelObject *object = objects[id.object_id];
if (id.volume_id >= object->volumes.size()) return nullptr; if (id.volume_id < 0 || static_cast<size_t>(id.volume_id) >= object->volumes.size())
return nullptr;
return object->volumes[id.volume_id]; return object->volumes[id.volume_id];
} }
@ -384,12 +413,14 @@ void GLGizmoEmboss::draw_font_list()
#ifdef USE_FONT_DIALOG #ifdef USE_FONT_DIALOG
if (ImGui::Button(_L("Choose font").c_str())) { if (ImGui::Button(_L("Choose font").c_str())) {
choose_font_by_wxdialog(); choose_font_by_wxdialog();
store_font_list();
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
} else if (ImGui::IsItemHovered()) ImGui::SetTooltip(_L("Choose from installed font in dialog.").c_str()); } else if (ImGui::IsItemHovered()) ImGui::SetTooltip(_L("Choose from installed font in dialog.").c_str());
ImGui::SameLine(); ImGui::SameLine();
#endif // USE_FONT_DIALOG #endif // USE_FONT_DIALOG
if (ImGui::Button(_L("Add File").c_str())) { if (ImGui::Button(_L("Add File").c_str())) {
choose_true_type_file(); choose_true_type_file();
store_font_list();
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
} else if (ImGui::IsItemHovered()) ImGui::SetTooltip(_L("add file with font(.ttf, .ttc)").c_str()); } else if (ImGui::IsItemHovered()) ImGui::SetTooltip(_L("add file with font(.ttf, .ttc)").c_str());
@ -420,6 +451,7 @@ void GLGizmoEmboss::draw_font_list()
m_font_list.erase(m_font_list.begin() + index); m_font_list.erase(m_font_list.begin() + index);
// fix selected index // fix selected index
if (index < m_font_selected) --m_font_selected; if (index < m_font_selected) --m_font_selected;
store_font_list();
} }
m_imgui->disabled_end(); // exist_rename || is_selected m_imgui->disabled_end(); // exist_rename || is_selected
ImGui::SameLine(); ImGui::SameLine();
@ -469,8 +501,11 @@ void GLGizmoEmboss::draw_font_list()
if (ImGui::BeginPopupModal(rename_popup_id)) { if (ImGui::BeginPopupModal(rename_popup_id)) {
ImGui::Text("Rename font name:"); ImGui::Text("Rename font name:");
FontItem &fi = m_font_list[rename_id]; FontItem &fi = m_font_list[rename_id];
if (ImGui::InputText("##font name", &fi.name, ImGuiInputTextFlags_EnterReturnsTrue) || ImGui::Button("ok")) if (ImGui::InputText("##font name", &fi.name, ImGuiInputTextFlags_EnterReturnsTrue) ||
ImGui::Button("ok")){
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
store_font_list();
}
ImGui::EndPopup(); ImGui::EndPopup();
} }
} }
@ -845,5 +880,7 @@ ExPolygons NSVGUtils::to_ExPolygons(NSVGimage *image,
return Slic3r::union_ex(polygons); return Slic3r::union_ex(polygons);
} }
const std::string GLGizmoEmboss::M_APP_CFG_FONT_LIST = "font_list";
// any existing icon filename to not influence GUI // any existing icon filename to not influence GUI
const std::string GLGizmoEmboss::M_ICON_FILENAME = "cut.svg"; const std::string GLGizmoEmboss::M_ICON_FILENAME = "cut.svg";

View File

@ -34,6 +34,9 @@ protected:
private: private:
void initialize(); void initialize();
void load_font_list();
void store_font_list();
static FontList create_default_font_list();
void set_default_configuration(); void set_default_configuration();
void check_selection(); void check_selection();
// more general function --> move to select // more general function --> move to select
@ -102,6 +105,7 @@ private:
// initialize when GL is accessible // initialize when GL is accessible
bool m_is_initialized; bool m_is_initialized;
static const std::string M_APP_CFG_FONT_LIST;
// only temporary solution // only temporary solution
static const std::string M_ICON_FILENAME; static const std::string M_ICON_FILENAME;
}; };