#ifndef slic3r_Emboss_hpp_ #define slic3r_Emboss_hpp_ #include #include #include #include #include // indexed_triangle_set #include "Polygon.hpp" #include "ExPolygon.hpp" #include "EmbossShape.hpp" // ExPolygonsWithIds #include "BoundingBox.hpp" #include "TextConfiguration.hpp" namespace Slic3r { /// /// class with only static function add ability to engraved OR raised /// text OR polygons onto model surface /// namespace Emboss { static const float UNION_DELTA = 50.0f; // [approx in nano meters depends on volume scale] static const unsigned UNION_MAX_ITERATIN = 10; // [count] /// /// Collect fonts registred inside OS /// /// OS registred TTF font files(full path) with names EmbossStyles get_font_list(); #ifdef _WIN32 EmbossStyles get_font_list_by_register(); EmbossStyles get_font_list_by_enumeration(); EmbossStyles get_font_list_by_folder(); #endif /// /// OS dependent function to get location of font by its name descriptor /// /// Unique identificator for font /// File path to font when found std::optional get_font_path(const std::wstring &font_face_name); // description of one letter struct Glyph { // NOTE: shape is scaled by SHAPE_SCALE // to be able store points without floating points ExPolygons shape; // values are in font points int advance_width=0, left_side_bearing=0; }; // cache for glyph by unicode using Glyphs = std::map; /// /// keep information from file about font /// (store file data itself) /// + cache data readed from buffer /// struct FontFile { // loaded data from font file // must store data size for imgui rasterization // To not store data on heap and To prevent unneccesary copy // data are stored inside unique_ptr std::unique_ptr> data; struct Info { // vertical position is "scale*(ascent - descent + lineGap)" int ascent, descent, linegap; // for convert font units to pixel int unit_per_em; }; // info for each font in data std::vector infos; FontFile(std::unique_ptr> data, std::vector &&infos) : data(std::move(data)), infos(std::move(infos)) { assert(this->data != nullptr); assert(!this->data->empty()); } bool operator==(const FontFile &other) const { if (data->size() != other.data->size()) return false; //if(*data != *other.data) return false; for (size_t i = 0; i < infos.size(); i++) if (infos[i].ascent != other.infos[i].ascent || infos[i].descent == other.infos[i].descent || infos[i].linegap == other.infos[i].linegap) return false; return true; } }; /// /// Add caching for shape of glyphs /// struct FontFileWithCache { // Pointer on data of the font file std::shared_ptr font_file; // Cache for glyph shape // IMPORTANT: accessible only in plater job thread !!! // main thread only clear cache by set to another shared_ptr std::shared_ptr cache; FontFileWithCache() : font_file(nullptr), cache(nullptr) {} explicit FontFileWithCache(std::unique_ptr font_file) : font_file(std::move(font_file)) , cache(std::make_shared()) {} bool has_value() const { return font_file != nullptr && cache != nullptr; } }; /// /// Load font file into buffer /// /// Location of .ttf or .ttc font file /// Font object when loaded. std::unique_ptr create_font_file(const char *file_path); // data = raw file data std::unique_ptr create_font_file(std::unique_ptr> data); #ifdef _WIN32 // fix for unknown pointer HFONT is replaced with "void *" void * can_load(void* hfont); std::unique_ptr create_font_file(void * hfont); #endif // _WIN32 /// /// convert letter into polygons /// /// Define fonts /// Index of font in collection /// One character defined by unicode codepoint /// Precision of lettter outline curve in conversion to lines /// inner polygon cw(outer ccw) std::optional letter2glyph(const FontFile &font, unsigned int font_index, int letter, float flatness); /// /// Convert text into polygons /// /// Define fonts + cache, which could extend /// Characters to convert /// User defined property of the font /// Way to interupt processing /// Inner polygon cw(outer ccw) HealedExPolygons text2shapes (FontFileWithCache &font, const char *text, const FontProp &font_prop, const std::function &was_canceled = []() {return false;}); ExPolygonsWithIds text2vshapes(FontFileWithCache &font, const std::wstring& text, const FontProp &font_prop, const std::function& was_canceled = []() {return false;}); HealedExPolygons union_with_delta(ExPolygons expoly, float delta, unsigned max_heal_iteration); const unsigned ENTER_UNICODE = static_cast('\n'); /// Sum of character '\n' unsigned get_count_lines(const std::wstring &ws); unsigned get_count_lines(const std::string &text); unsigned get_count_lines(const ExPolygonsWithIds &shape); /// /// Fix duplicit points and self intersections in polygons. /// Also try to reduce amount of points and remove useless polygon parts /// /// Fill type ClipperLib::pftNonZero for overlapping otherwise /// Look at heal_expolygon()::max_iteration /// Healed shapes with flag is fully healed HealedExPolygons heal_polygons(const Polygons &shape, bool is_non_zero = true, unsigned max_iteration = 10); /// /// NOTE: call Slic3r::union_ex before this call /// /// Heal (read: Fix) issues in expolygons: /// - self intersections /// - duplicit points /// - points close to line segments /// /// In/Out shape to heal /// Heal could create another issue, /// After healing it is checked again until shape is good or maximal count of iteration /// True when shapes is good otherwise False bool heal_expolygons(ExPolygons &shape, unsigned max_iteration = 10); /// /// Divide line segments in place near to point /// (which could lead to self intersection due to preccision) /// Remove same neighbors /// Note: Possible part of heal shape /// /// Expolygon to edit /// (epsilon)Euclidean distance from point to line which divide line /// True when some division was made otherwise false bool divide_segments_for_close_point(ExPolygons &expolygons, double distance); /// /// Use data from font property to modify transformation /// /// Z-rotation as angle to Y axis /// Z-move as surface distance /// In / Out transformation to modify by property void apply_transformation(const std::optional &angle, const std::optional &distance, Transform3d &transformation); /// /// Read information from naming table of font file /// search for italic (or oblique), bold italic (or bold oblique) /// /// Selector of font /// Index of font in collection /// True when the font description contains italic/obligue otherwise False bool is_italic(const FontFile &font, unsigned int font_index); /// /// Create unique character set from string with filtered from text with only character from font /// /// Source vector of glyphs /// Font descriptor /// Define font in collection /// True when text contain glyph unknown in font /// Unique set of character from text contained in font std::string create_range_text(const std::string &text, const FontFile &font, unsigned int font_index, bool* exist_unknown = nullptr); /// /// Calculate scale for glyph shape convert from shape points to mm /// /// Property of font /// Font data /// Conversion to mm double get_text_shape_scale(const FontProp &fp, const FontFile &ff); /// /// getter of font info by collection defined in prop /// /// Contain infos about all fonts(collections) in file /// Index of collection /// Ascent, descent, line gap const FontFile::Info &get_font_info(const FontFile &font, const FontProp &prop); /// /// Read from font file and properties height of line with spacing /// /// Infos for collections /// Collection index + Additional line gap /// Line height with spacing in scaled font points (same as ExPolygons) int get_line_height(const FontFile &font, const FontProp &prop); /// /// Calculate Vertical align /// /// Top | Center | Bottom /// /// Return align Y offset in mm double get_align_y_offset_in_mm(FontProp::VerticalAlign align, unsigned count_lines, const FontFile &ff, const FontProp &fp); /// /// Project spatial point /// class IProject3d { public: virtual ~IProject3d() = default; /// /// Move point with respect to projection direction /// e.g. Orthogonal projection will move with point by direction /// e.g. Spherical projection need to use center of projection /// /// Spatial point coordinate /// Projected spatial point virtual Vec3d project(const Vec3d &point) const = 0; }; /// /// Project 2d point into space /// Could be plane, sphere, cylindric, ... /// class IProjection : public IProject3d { public: virtual ~IProjection() = default; /// /// convert 2d point to 3d points /// /// 2d coordinate /// /// first - front spatial point /// second - back spatial point /// virtual std::pair create_front_back(const Point &p) const = 0; /// /// Back projection /// /// Point to project /// [optional] Depth of 2d projected point. Be careful number is in 2d scale /// Uprojected point when it is possible virtual std::optional unproject(const Vec3d &p, double * depth = nullptr) const = 0; }; /// /// Create triangle model for text /// /// text or image /// Define transformation from 2d to 3d(orientation, position, scale, ...) /// Projected shape into space indexed_triangle_set polygons2model(const ExPolygons &shape2d, const IProjection& projection); /// /// Suggest wanted up vector of embossed text by emboss direction /// /// Normalized vector of emboss direction in world /// Is compared with normal.z to suggest up direction /// Wanted up vector Vec3d suggest_up(const Vec3d normal, double up_limit = 0.9); /// /// By transformation calculate angle between suggested and actual up vector /// /// Transformation of embossed volume in world /// Is compared with normal.z to suggest up direction /// Rotation of suggested up-vector[in rad] in the range [-Pi, Pi], When rotation is not zero std::optional calc_up(const Transform3d &tr, double up_limit = 0.9); /// /// Create transformation for emboss text object to lay on surface point /// /// Position of surface point /// Normal of surface point /// Is compared with normal.z to suggest up direction /// Transformation onto surface point Transform3d create_transformation_onto_surface( const Vec3d &position, const Vec3d &normal, double up_limit = 0.9); class ProjectZ : public IProjection { public: explicit ProjectZ(double depth) : m_depth(depth) {} // Inherited via IProject std::pair create_front_back(const Point &p) const override; Vec3d project(const Vec3d &point) const override; std::optional unproject(const Vec3d &p, double * depth = nullptr) const override; double m_depth; }; class ProjectScale : public IProjection { std::unique_ptr core; double m_scale; public: ProjectScale(std::unique_ptr core, double scale) : core(std::move(core)), m_scale(scale) {} // Inherited via IProject std::pair create_front_back(const Point &p) const override { auto res = core->create_front_back(p); return std::make_pair(res.first * m_scale, res.second * m_scale); } Vec3d project(const Vec3d &point) const override{ return core->project(point); } std::optional unproject(const Vec3d &p, double *depth = nullptr) const override { auto res = core->unproject(p / m_scale, depth); if (depth != nullptr) *depth *= m_scale; return res; } }; class ProjectTransform : public IProjection { std::unique_ptr m_core; Transform3d m_tr; Transform3d m_tr_inv; double z_scale; public: ProjectTransform(std::unique_ptr core, const Transform3d &tr) : m_core(std::move(core)), m_tr(tr) { m_tr_inv = m_tr.inverse(); z_scale = (m_tr.linear() * Vec3d::UnitZ()).norm(); } // Inherited via IProject std::pair create_front_back(const Point &p) const override { auto [front, back] = m_core->create_front_back(p); return std::make_pair(m_tr * front, m_tr * back); } Vec3d project(const Vec3d &point) const override{ return m_core->project(point); } std::optional unproject(const Vec3d &p, double *depth = nullptr) const override { auto res = m_core->unproject(m_tr_inv * p, depth); if (depth != nullptr) *depth *= z_scale; return res; } }; class OrthoProject3d : public Emboss::IProject3d { // size and direction of emboss for ortho projection Vec3d m_direction; public: OrthoProject3d(Vec3d direction) : m_direction(direction) {} Vec3d project(const Vec3d &point) const override{ return point + m_direction;} }; class OrthoProject: public Emboss::IProjection { Transform3d m_matrix; // size and direction of emboss for ortho projection Vec3d m_direction; Transform3d m_matrix_inv; public: OrthoProject(Transform3d matrix, Vec3d direction) : m_matrix(matrix), m_direction(direction), m_matrix_inv(matrix.inverse()) {} // Inherited via IProject std::pair create_front_back(const Point &p) const override; Vec3d project(const Vec3d &point) const override; std::optional unproject(const Vec3d &p, double * depth = nullptr) const override; }; /// /// Define polygon for draw letters /// struct TextLine { // slice of object Polygon polygon; // point laying on polygon closest to zero PolygonPoint start; // offset of text line in volume mm float y; }; using TextLines = std::vector; /// /// Sample slice polygon by bounding boxes centers /// slice start point has shape_center_x coor /// /// Polygon and start point[Slic3r scaled milimeters] /// Bounding boxes of letter on one line[in font scales] /// Scale for bbs (after multiply bb is in milimeters) /// Sampled polygon by bounding boxes PolygonPoints sample_slice(const TextLine &slice, const BoundingBoxes &bbs, double scale); /// /// Calculate angle for polygon point /// /// Distance for found normal in point /// Select point on polygon /// Polygon know neighbor of point /// angle(atan2) of normal in polygon point double calculate_angle(int32_t distance, PolygonPoint polygon_point, const Polygon &polygon); std::vector calculate_angles(int32_t distance, const PolygonPoints& polygon_points, const Polygon &polygon); } // namespace Emboss /////////////////////// // Move to ExPolygonsWithIds Utils void translate(ExPolygonsWithIds &e, const Point &p); BoundingBox get_extents(const ExPolygonsWithIds &e); void center(ExPolygonsWithIds &e); // delta .. safe offset before union (use as boolean close) // NOTE: remove unprintable spaces between neighbor curves (made by linearization of curve) ExPolygons union_with_delta(EmbossShape &shape, float delta, unsigned max_heal_iteration); } // namespace Slic3r #endif // slic3r_Emboss_hpp_