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

View File

@ -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<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
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<size_t>(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<Emboss::Glyph> 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

View File

@ -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<std::string> 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<TextConfiguration> 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<std::string> 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<FontItem> font_item = get_font_item(columns, 1);
if (!font_item.has_value()) return {};
std::optional<FontProp> 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<float>(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<std::string> 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<std::string> 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<FontItem> 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<std::string> &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<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 = 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<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:
TextConfigurationSerialization() = delete; // only static functions
// store / load TextConfiguration - .3mf files
static std::string serialize(const TextConfiguration &text_configuration);
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
static const std::map<std::string, FontItem::Type> to_type;
static const std::map<FontItem::Type, std::string> to_string;
static const char separator;
// 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
// Move to map utils
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>
static std::map<Value, Key> create_oposit_map(
const std::map<Key, Value> &map)

View File

@ -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<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() {
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<size_t>(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<size_t>(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";

View File

@ -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;
};