diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index f553aef09d..b3dcdf4b36 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -9,6 +9,9 @@ #include // CGAL project #include "libslic3r.h" + +#include "ClipperUtils.hpp" // for boldness - polygon extend(offset) + using namespace Slic3r; double Emboss::SHAPE_SCALE = 0.001;//SCALING_FACTOR; @@ -541,8 +544,11 @@ ExPolygons Emboss::text2shapes(Font & font, std::wstring ws = boost::nowide::widen(text); for (wchar_t wc: ws){ if (wc == '\n') { + int line_height = font.ascent - font.descent + font.linegap; + if (font_prop.line_gap.has_value()) + line_height += *font_prop.line_gap; cursor.x() = 0; - cursor.y() -= (font.ascent - font.descent + font.linegap + font_prop.line_gap) / SHAPE_SCALE; + cursor.y() -= static_cast(line_height / SHAPE_SCALE); continue; } if (wc == '\t') { @@ -550,7 +556,10 @@ ExPolygons Emboss::text2shapes(Font & font, const int count_spaces = 4; std::optional space_opt = Private::get_glyph(int(' '), font, font_prop, font.cache, font_info_opt); if (!space_opt.has_value()) continue; - cursor.x() += (count_spaces *(space_opt->advance_width + font_prop.char_gap)) / SHAPE_SCALE; + int width = space_opt->advance_width; + if (font_prop.char_gap.has_value()) + width += *font_prop.char_gap; + cursor.x() += static_cast((count_spaces * width) / SHAPE_SCALE); continue; } @@ -562,7 +571,30 @@ ExPolygons Emboss::text2shapes(Font & font, ExPolygons expolygons = glyph_opt->shape; // copy for (ExPolygon &expolygon : expolygons) expolygon.translate(cursor); - cursor.x() += (glyph_opt->advance_width + font_prop.char_gap) / SHAPE_SCALE; + + if (font_prop.boldness.has_value()) { + float delta = *font_prop.boldness / SHAPE_SCALE; + expolygons = offset_ex(expolygons, delta); + } + + if (font_prop.skew.has_value()) { + const float& ratio = *font_prop.skew; + auto skew = [&ratio](Polygon &polygon) { + for (Point &p : polygon.points) { + p.x() += p.y() * ratio; + } + }; + for (ExPolygon &expolygon : expolygons) { + skew(expolygon.contour); + for (Polygon &hole : expolygon.holes) + skew(hole); + } + } + + int width = glyph_opt->advance_width; + if (font_prop.char_gap.has_value()) + width += *font_prop.char_gap; + cursor.x() += static_cast(width / SHAPE_SCALE); expolygons_append(result, expolygons); } result = Slic3r::union_ex(result); diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index fa766934b6..1ae6823bd3 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -156,6 +156,8 @@ static constexpr const char *CHAR_GAP_ATTR = "char_gap"; static constexpr const char *LINE_GAP_ATTR = "line_gap"; static constexpr const char *LINE_HEIGHT_ATTR = "line_height"; static constexpr const char *DEPTH_ATTR = "depth"; +static constexpr const char *BOLDNESS_ATTR = "boldness"; +static constexpr const char *SKEW_ATTR = "skew"; static constexpr const char *FONT_FAMILY_ATTR = "family"; static constexpr const char *FONT_FACE_NAME_ATTR = "face_name"; @@ -3254,10 +3256,18 @@ void TextConfigurationSerialization::to_xml(std::stringstream &stream, const Tex // font property const FontProp &fp = tc.font_prop; - stream << CHAR_GAP_ATTR << "=\"" << fp.char_gap << "\" "; - stream << LINE_GAP_ATTR << "=\"" << fp.line_gap << "\" "; + if (fp.char_gap.has_value()) + stream << CHAR_GAP_ATTR << "=\"" << *fp.char_gap << "\" "; + if (fp.line_gap.has_value()) + stream << LINE_GAP_ATTR << "=\"" << *fp.line_gap << "\" "; + stream << LINE_HEIGHT_ATTR << "=\"" << fp.size_in_mm << "\" "; stream << DEPTH_ATTR << "=\"" << fp.emboss << "\" "; + if (fp.boldness.has_value()) + stream << BOLDNESS_ATTR << "=\"" << *fp.boldness << "\" "; + if (fp.skew.has_value()) + stream << SKEW_ATTR << "=\"" << *fp.skew << "\" "; + // font descriptor if (fp.family.has_value()) stream << FONT_FAMILY_ATTR << "=\"" << *fp.family << "\" "; @@ -3280,8 +3290,17 @@ std::optional TextConfigurationSerialization::read(const char FontItem fi(font_name, font_descriptor, type); FontProp fp; - fp.char_gap = get_attribute_value_int(attributes, num_attributes, CHAR_GAP_ATTR); - fp.line_gap = get_attribute_value_int(attributes, num_attributes, LINE_GAP_ATTR); + int char_gap = get_attribute_value_int(attributes, num_attributes, CHAR_GAP_ATTR); + if (char_gap != 0) fp.char_gap = char_gap; + int line_gap = get_attribute_value_int(attributes, num_attributes, LINE_GAP_ATTR); + if (line_gap != 0) fp.line_gap = line_gap; + float boldness = get_attribute_value_float(attributes, num_attributes, BOLDNESS_ATTR); + if (std::fabs(boldness) > std::numeric_limits::epsilon()) + fp.boldness = boldness; + float skew = get_attribute_value_float(attributes, num_attributes, SKEW_ATTR); + if (std::fabs(skew) > std::numeric_limits::epsilon()) + fp.skew = skew; + fp.size_in_mm = get_attribute_value_float(attributes, num_attributes, LINE_HEIGHT_ATTR); fp.emboss = get_attribute_value_float(attributes, num_attributes, DEPTH_ATTR); diff --git a/src/libslic3r/TextConfiguration.hpp b/src/libslic3r/TextConfiguration.hpp index 3955f32d43..9c2abd61ca 100644 --- a/src/libslic3r/TextConfiguration.hpp +++ b/src/libslic3r/TextConfiguration.hpp @@ -38,11 +38,20 @@ using FontList = std::vector; struct FontProp { // define extra space between letters, negative mean closer letter - int char_gap = 0; + std::optional char_gap = 0; // define extra space between lines, negative mean closer lines - int line_gap = 0; + std::optional line_gap = 0; // Z depth of text [in mm] float emboss = 5; + + // positive value mean wider character shape + // negative value mean tiner character shape + std::optional boldness = 0.f; // [in mm] + + // positive value mean italic of character (CW) + // negative value mean CCW skew (unItalic) + std::optional skew = 0.f; + // TODO: add enum class Align: center/left/right ////// diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 846f47efdb..723d0ab83b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -423,7 +423,7 @@ void GLGizmoEmboss::initialize() m_gui_cfg->text_size.y + style.WindowPadding.y * 2.f; m_gui_cfg->minimal_window_size = ImVec2(window_width, window_height); - float advance_height = (input_height + style.ItemSpacing.y) * 4.f; + float advance_height = (input_height + style.ItemSpacing.y) * 6.f; m_gui_cfg->minimal_window_size_with_advance = ImVec2(window_width, window_height + advance_height); @@ -765,13 +765,21 @@ void GLGizmoEmboss::draw_advanced() ImGui::SetNextItemWidth(m_gui_cfg->advanced_input_width); if (ImGui::InputFloat(_u8L("Emboss[in mm]").c_str(), &m_font_prop.emboss)) process(); - ImGui::SetNextItemWidth(2*m_gui_cfg->advanced_input_width); - if (ImGui::InputInt(_u8L("CharGap[in font points]").c_str(), - &m_font_prop.char_gap)) + + ImGui::SetNextItemWidth(2 * m_gui_cfg->advanced_input_width); + if(ImGuiWrapper::input_optional_int(_u8L("CharGap[in font points]").c_str(), m_font_prop.char_gap)) process(); + ImGui::SetNextItemWidth(2*m_gui_cfg->advanced_input_width); - if (ImGui::InputInt(_u8L("LineGap[in font points]").c_str(), - &m_font_prop.line_gap)) + if (ImGuiWrapper::input_optional_int(_u8L("LineGap[in font points]").c_str(), m_font_prop.line_gap)) + process(); + + ImGui::SetNextItemWidth(2 * m_gui_cfg->advanced_input_width); + if (m_imgui->slider_optional_float(_u8L("Boldness[in font points]").c_str(), m_font_prop.boldness, -200.f, 200.f, "%.0f", 1.f, false, _L("tiny / wide chars"))) + process(); + + ImGui::SetNextItemWidth(2 * m_gui_cfg->advanced_input_width); + if (m_imgui->slider_optional_float(_u8L("Skew ratio").c_str(), m_font_prop.skew, -1.f, 1.f, "%.2f", 1.f, false, _L("italic strength"))) process(); // when more collection add selector @@ -793,6 +801,7 @@ void GLGizmoEmboss::draw_advanced() } } + #ifdef ALLOW_DEBUG_MODE std::string descriptor = m_font_list[m_font_selected].path; ImGui::Text("family = %s", (m_font_prop.family.has_value() ? diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index dcdcffa48c..e02cfe520c 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -1041,6 +1041,93 @@ bool ImGuiWrapper::want_any_input() const return io.WantCaptureMouse || io.WantCaptureKeyboard || io.WantTextInput; } +template +static bool input_optional(std::optional &v, Func& f, std::function is_default) +{ + if (v.has_value()) { + if (f(*v)) { + if (is_default(*v)) v.reset(); + return true; + } + } else { + T val = 0; + if (f(val)) { + if (!is_default(val)) v = val; + return true; + } + } + return false; +} + +bool ImGuiWrapper::input_optional_int(const char * label, + std::optional& v, + int step, + int step_fast, + ImGuiInputTextFlags flags) +{ + auto func = [&](int &value) { + return ImGui::InputInt(label, &value, step, step_fast, flags); + }; + std::function is_default = + [](const int &value) -> bool { return value == 0; }; + return input_optional(v, func, is_default); +} + +bool ImGuiWrapper::input_optional_float(const char * label, + std::optional &v, + float step, + float step_fast, + const char * format, + ImGuiInputTextFlags flags) +{ + auto func = [&](float &value) { + return ImGui::InputFloat(label, &value, step, step_fast, format, flags); + }; + std::function is_default = + [](const float &value) -> bool { + return std::fabs(value) < std::numeric_limits::epsilon(); + }; + return input_optional(v, func, is_default); +} + +bool ImGuiWrapper::drag_optional_float(const char * label, + std::optional &v, + float v_speed, + float v_min, + float v_max, + const char * format, + float power) +{ + auto func = [&](float &value) { + return ImGui::DragFloat(label, &value, v_speed, v_min, v_max, format, power); + }; + std::function is_default = + [](const float &value) -> bool { + return std::fabs(value) < std::numeric_limits::epsilon(); + }; + return input_optional(v, func, is_default); +} + +bool ImGuiWrapper::slider_optional_float(const char * label, + std::optional &v, + float v_min, + float v_max, + const char * format, + float power, + bool clamp, + const wxString & tooltip, + bool show_edit_btn) +{ + auto func = [&](float &value) { + return slider_float(label, &value, v_min, v_max, format, power, clamp, tooltip, show_edit_btn); + }; + std::function is_default = + [](const float &value) -> bool { + return std::fabs(value) < std::numeric_limits::epsilon(); + }; + return input_optional(v, func, is_default); +} + std::string ImGuiWrapper::trunc(const std::string &text, float width, const char * tail) diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index fcad7819e5..dedff042f7 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -128,6 +128,12 @@ public: bool want_text_input() const; bool want_any_input() const; + // Input [optional] int for nonzero value more info in ImGui::InputInt + static bool input_optional_int(const char *label, std::optional& v, int step=1, int step_fast=100, ImGuiInputTextFlags flags=0); + // Input [optional] float for nonzero value more info in ImGui::InputFloat + static bool input_optional_float(const char* label, std::optional &v, float step = 0.0f, float step_fast = 0.0f, const char* format = "%.3f", ImGuiInputTextFlags flags = 0); + static bool drag_optional_float(const char* label, std::optional &v, float v_speed, float v_min, float v_max, const char* format, float power); + bool slider_optional_float(const char* label, std::optional &v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f, bool clamp = true, const wxString& tooltip = {}, bool show_edit_btn = true); /// /// Truncate text by ImGui draw function to specific width /// NOTE 1: ImGui must be initialized