From 8f09c3ac8210904f966ee88a6813cf44c1f40f96 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Fri, 20 Jan 2023 17:33:53 +0100 Subject: [PATCH] WIP: Create another approach to heal shape BUT it is not working --- src/libslic3r/Emboss.cpp | 437 +++++++++++++----- src/libslic3r/Emboss.hpp | 7 +- src/slic3r/GUI/Jobs/EmbossJob.cpp | 46 +- tests/data/contour_ALIENATO.TTF_glyph_i.svg | 2 +- .../contour_Allura_Script.ttf_glyph_m.svg | 3 + tests/libslic3r/test_emboss.cpp | 56 ++- 6 files changed, 377 insertions(+), 174 deletions(-) create mode 100644 tests/data/contour_Allura_Script.ttf_glyph_m.svg diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index 440903757..50114fee0 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -20,43 +20,79 @@ #include "libslic3r/BoundingBox.hpp" using namespace Slic3r; +using namespace Emboss; +using fontinfo_opt = std::optional; + // do not expose out of this file stbtt_ data types namespace priv{ - -bool is_valid(const Emboss::FontFile &font, unsigned int index); -std::optional load_font_info(const unsigned char *data, unsigned int index = 0); -std::optional get_glyph(const stbtt_fontinfo &font_info, int unicode_letter, float flatness); +using Polygon = Slic3r::Polygon; +bool is_valid(const FontFile &font, unsigned int index); +fontinfo_opt load_font_info(const unsigned char *data, unsigned int index = 0); +std::optional get_glyph(const stbtt_fontinfo &font_info, int unicode_letter, float flatness); // take glyph from cache -const Emboss::Glyph* get_glyph(int unicode, const Emboss::FontFile &font, const FontProp &font_prop, - Emboss::Glyphs &cache, std::optional &font_info_opt); +const Glyph* get_glyph(int unicode, const FontFile &font, const FontProp &font_prop, + Glyphs &cache, fontinfo_opt &font_info_opt); EmbossStyle create_style(std::wstring name, std::wstring path); // scale and convert float to int coordinate Point to_point(const stbtt__point &point); +// bad is contour smaller than 3 points +void remove_bad(Polygons &polygons); +void remove_bad(ExPolygons &expolygons); + // helpr for heal shape -bool remove_same_neighbor(Slic3r::Points &points); -bool remove_same_neighbor(Slic3r::Polygons &polygons); +// Return true when erase otherwise false +bool remove_same_neighbor(Points &points); +bool remove_same_neighbor(Polygons &polygons); bool remove_same_neighbor(ExPolygons &expolygons); +// Try to remove self intersection by subtracting rect 2x2 px +bool remove_self_intersections(ExPolygons &shape, unsigned max_iteration = 10); +ExPolygon create_bounding_rect(const ExPolygons &shape); + +void remove_small_islands(ExPolygons &shape, double minimal_area); + // NOTE: expolygons can't contain same_neighbor Points collect_close_points(const ExPolygons &expolygons, double distance = .6); +// Heal duplicates points and self intersections +bool heal_dupl_inter(ExPolygons &shape, unsigned max_iteration); +bool heal_dupl_inter2(ExPolygons &shape, unsigned max_iteration); // by close_ex + +void visualize_heal(const std::string& svg_filepath, const ExPolygons &expolygons); + const Points pts_2x2({Point(0, 0), Point(1, 0), Point(1, 1), Point(0, 1)}); const Points pts_3x3({Point(-1, -1), Point(1, -1), Point(1, 1), Point(-1, 1)}); +// Reduce number of Points in polygons with respect to flatness +// Flatness is area that could be modified +void simplify(Polygon &polygon, float flatness); +void simplify(Polygons &polygons, float flatness); + }; -bool priv::is_valid(const Emboss::FontFile &font, unsigned int index) { +void priv::simplify(Polygon &polygon, float flatness) { + polygon.douglas_peucker(flatness); +} + +void priv::simplify(Polygons &polygons, float flatness) { + for (Polygon &polygon : polygons) + simplify(polygon, flatness); + // simplification could ceate empty polygon + remove_bad(polygons); +} + +bool priv::is_valid(const FontFile &font, unsigned int index) { if (font.data == nullptr) return false; if (font.data->empty()) return false; if (index >= font.infos.size()) return false; return true; } -std::optional priv::load_font_info( +fontinfo_opt priv::load_font_info( const unsigned char *data, unsigned int index) { int font_offset = stbtt_GetFontOffsetForIndex(data, index); @@ -74,6 +110,23 @@ std::optional priv::load_font_info( return font_info; } +void priv::remove_bad(Polygons &polygons) { + polygons.erase( + std::remove_if(polygons.begin(), polygons.end(), + [](const Polygon &p) { return p.size() < 3; }), + polygons.end()); +} + +void priv::remove_bad(ExPolygons &expolygons) { + expolygons.erase( + std::remove_if(expolygons.begin(), expolygons.end(), + [](const ExPolygon &p) { return p.contour.size() < 3; }), + expolygons.end()); + + for (ExPolygon &expolygon : expolygons) + remove_bad(expolygon.holes); +} + bool priv::remove_same_neighbor(Slic3r::Points &points) { if (points.empty()) return false; @@ -85,15 +138,15 @@ bool priv::remove_same_neighbor(Slic3r::Points &points) return true; } -bool priv::remove_same_neighbor(Slic3r::Polygons &polygons) { +bool priv::remove_same_neighbor(Polygons &polygons) { if (polygons.empty()) return false; bool exist = false; - for (Slic3r::Polygon& polygon : polygons) + for (Polygon& polygon : polygons) exist |= remove_same_neighbor(polygon.points); // remove empty polygons polygons.erase( std::remove_if(polygons.begin(), polygons.end(), - [](const Slic3r::Polygon &p) { return p.empty(); }), + [](const Polygon &p) { return p.empty(); }), polygons.end()); return exist; } @@ -104,19 +157,18 @@ bool priv::remove_same_neighbor(ExPolygons &expolygons) { for (ExPolygon &expoly : expolygons) { exist |= remove_same_neighbor(expoly.contour.points); Polygons &holes = expoly.holes; - for (Slic3r::Polygon &hole : holes) + for (Polygon &hole : holes) exist |= remove_same_neighbor(hole.points); - // remove empy holes holes.erase( std::remove_if(holes.begin(), holes.end(), - [](const Slic3r::Polygon &p) { return p.empty(); }), + [](const Polygon &p) { return p.size() < 3; }), holes.end()); } - // remove empty contours - expolygons.erase( - std::remove_if(expolygons.begin(), expolygons.end(), - [](const ExPolygon &p) { return p.contour.empty(); }), - expolygons.end()); + + // Removing of point could create polygon with less than 3 points + if (exist) + remove_bad(expolygons); + return exist; } @@ -145,7 +197,7 @@ Points priv::collect_close_points(const ExPolygons &expolygons, double distance) // do not doubled side point of segment const ExPolygonsIndex id = ids.cvt(index); const ExPolygon &expoly = expolygons[id.expolygons_index]; - const Slic3r::Polygon &poly = id.is_contour() ? expoly.contour : expoly.holes[id.hole_index()]; + const Polygon &poly = id.is_contour() ? expoly.contour : expoly.holes[id.hole_index()]; const Points &poly_pts = poly.points; const Point &line_a = poly_pts[id.point_index]; const Point &line_b = (!ids.is_last_point(id)) ? poly_pts[id.point_index + 1] : poly_pts.front(); @@ -159,7 +211,7 @@ Points priv::collect_close_points(const ExPolygons &expolygons, double distance) }; for (const ExPolygon &expoly : expolygons) { collect_close(expoly.contour.points); - for (const Slic3r::Polygon &hole : expoly.holes) + for (const Polygon &hole : expoly.holes) collect_close(hole.points); } if (res.empty()) return {}; @@ -198,7 +250,7 @@ bool Emboss::divide_segments_for_close_point(ExPolygons &expolygons, double dist // do not doubled side point of segment const ExPolygonsIndex id = ids.cvt(index); const ExPolygon &expoly = expolygons[id.expolygons_index]; - const Slic3r::Polygon &poly = id.is_contour() ? expoly.contour : expoly.holes[id.hole_index()]; + const Polygon &poly = id.is_contour() ? expoly.contour : expoly.holes[id.hole_index()]; const Points &poly_pts = poly.points; const Point &line_a = poly_pts[id.point_index]; const Point &line_b = (!ids.is_last_point(id)) ? poly_pts[id.point_index + 1] : poly_pts.front(); @@ -213,7 +265,7 @@ bool Emboss::divide_segments_for_close_point(ExPolygons &expolygons, double dist }; for (const ExPolygon &expoly : expolygons) { check_points(expoly.contour.points); - for (const Slic3r::Polygon &hole : expoly.holes) + for (const Polygon &hole : expoly.holes) check_points(hole.points); } @@ -235,7 +287,7 @@ bool Emboss::divide_segments_for_close_point(ExPolygons &expolygons, double dist ExPolygonsIndex id = ids.cvt(index); ExPolygon &expoly = expolygons[id.expolygons_index]; - Slic3r::Polygon &poly = id.is_contour() ? expoly.contour : expoly.holes[id.hole_index()]; + Polygon &poly = id.is_contour() ? expoly.contour : expoly.holes[id.hole_index()]; Points &pts = poly.points; size_t count = it2 - it; @@ -272,6 +324,62 @@ bool Emboss::divide_segments_for_close_point(ExPolygons &expolygons, double dist return true; } +bool priv::remove_self_intersections(ExPolygons &shape, unsigned max_iteration) { + if (shape.empty()) + return true; + + Pointfs intersections_f = intersection_points(shape); + if (intersections_f.empty()) + return true; + + // create loop permanent memory + Polygons holes; + Points intersections; + + while (--max_iteration) { + // convert intersections into Points + assert(intersections.empty()); + intersections.reserve(intersections_f.size()); + std::transform(intersections_f.begin(), intersections_f.end(), std::back_inserter(intersections), + [](const Vec2d &p) { return Point(std::floor(p.x()), std::floor(p.y())); }); + + // intersections should be unique poits + std::sort(intersections.begin(), intersections.end()); + auto it = std::unique(intersections.begin(), intersections.end()); + intersections.erase(it, intersections.end()); + + assert(holes.empty()); + holes.reserve(intersections.size()); + + // Fix self intersection in result by subtracting hole 2x2 + for (const Point &p : intersections) { + Polygon hole(priv::pts_2x2); + hole.translate(p); + holes.push_back(hole); + } + // union overlapped holes + if (holes.size() > 1) + holes = Slic3r::union_(holes); + shape = Slic3r::diff_ex(shape, holes, ApplySafetyOffset::Yes); + + // TODO: find where diff ex could create same neighbor + priv::remove_same_neighbor(shape); + + // find new intersections made by diff_ex + intersections_f = intersection_points(shape); + if (intersections_f.empty()) + return true; + else { + // clear permanent vectors + holes.clear(); + intersections.clear(); + } + } + assert(max_iteration == 0); + assert(!intersections_f.empty()); + return false; +} + ExPolygons Emboss::heal_shape(const Polygons &shape) { // When edit this code check that font 'ALIENATE.TTF' and glyph 'i' still work // fix of self intersections @@ -287,7 +395,7 @@ ExPolygons Emboss::heal_shape(const Polygons &shape) { if (!duplicits.empty()) { polygons.reserve(polygons.size() + duplicits.size()); for (const Point &p : duplicits) { - Slic3r::Polygon rect_3x3(priv::pts_3x3); + Polygon rect_3x3(priv::pts_3x3); rect_3x3.translate(p); polygons.push_back(rect_3x3); } @@ -301,97 +409,164 @@ ExPolygons Emboss::heal_shape(const Polygons &shape) { return res; } +void priv::visualize_heal(const std::string &svg_filepath, const ExPolygons &expolygons) { + double svg_scale = SHAPE_SCALE / unscale(1.); + Points pts = to_points(expolygons); + BoundingBox bb(pts); + // bb.scale(svg_scale); + SVG svg(svg_filepath, bb); + svg.draw(expolygons); + + Points duplicits = collect_duplicates(pts); + svg.draw(duplicits, "black", 7 / SHAPE_SCALE); + + Pointfs intersections_f = intersection_points(expolygons); + Points intersections; + intersections.reserve(intersections_f.size()); + std::transform(intersections_f.begin(), intersections_f.end(), std::back_inserter(intersections), + [](const Vec2d &p) { return p.cast(); }); + svg.draw(intersections, "red", 8 / SHAPE_SCALE); +} + bool Emboss::heal_shape(ExPolygons &shape, unsigned max_iteration) { + return priv::heal_dupl_inter(shape, max_iteration); + //return priv::heal_dupl_inter2(shape, max_iteration); +} + +bool priv::heal_dupl_inter(ExPolygons &shape, unsigned max_iteration) +{ if (shape.empty()) return true; - Slic3r::Polygons holes; + // create loop permanent memory + Polygons holes; + Points intersections; while (--max_iteration) { priv::remove_same_neighbor(shape); + Pointfs intersections_f = intersection_points(shape); - Pointfs intersections = intersection_points(shape); - Points duplicits = collect_duplicates(to_points(shape)); - //Points close = priv::collect_close_points(shape, 1.); - if (intersections.empty() && duplicits.empty() /* && close.empty() */) break; + // convert intersections into Points + assert(intersections.empty()); + intersections.reserve(intersections_f.size()); + std::transform(intersections_f.begin(), intersections_f.end(), std::back_inserter(intersections), + [](const Vec2d &p) { return Point(std::floor(p.x()), std::floor(p.y())); }); - holes.clear(); - holes.reserve(intersections.size() + duplicits.size() /* + close.size()*/); + // intersections should be unique poits + std::sort(intersections.begin(), intersections.end()); + auto it = std::unique(intersections.begin(), intersections.end()); + intersections.erase(it, intersections.end()); + + Points duplicits = collect_duplicates(to_points(shape)); + // duplicits are already uniqua and sorted + + // Check whether shape is already healed + if (intersections.empty() && duplicits.empty()) + return true; + + assert(holes.empty()); + holes.reserve(intersections.size() + duplicits.size()); // Fix self intersection in result by subtracting hole 2x2 - for (const Vec2d &p : intersections) { - Slic3r::Polygon hole(priv::pts_2x2); - Point tr(std::floor(p.x()), std::floor(p.y())); - hole.translate(tr); - holes.push_back(hole); - } - - // fix duplicit points by hole 3x3 around duplicit point - for (const Point &p : duplicits) { - Slic3r::Polygon hole(priv::pts_3x3); + for (const Point &p : intersections) { + Polygon hole(priv::pts_2x2); hole.translate(p); holes.push_back(hole); } - // fix close points in simmilar way as duplicits - //for (const Point &p : close) { - // Slic3r::Polygon hole(priv::pts_3x3); - // hole.translate(p); - // holes.push_back(hole); - //} + // Fix duplicit points by hole 3x3 around duplicit point + for (const Point &p : duplicits) { + Polygon hole(priv::pts_3x3); + hole.translate(p); + holes.push_back(hole); + } holes = Slic3r::union_(holes); shape = Slic3r::diff_ex(shape, holes, ApplySafetyOffset::Yes); - } - /* VISUALIZATION of BAD symbols for debug - { - double svg_scale = Emboss::SHAPE_SCALE / unscale(1.); - BoundingBox bb(to_points(shape)); - //bb.scale(svg_scale); - SVG svg("C:/data/temp/fix_self_intersection.svg", bb); - svg.draw(shape); - // svg.draw(polygons, "orange"); - svg.draw(shape, "green"); - svg.draw(duplicits, "lightgray", 13 / Emboss::SHAPE_SCALE); - Points duplicits3 = collect_duplicates(to_points(shape)); - svg.draw(duplicits3, "black", 7 / Emboss::SHAPE_SCALE); - - Pointfs pts2 = intersection_points(shape); - Points pts_i; pts_i.reserve(pts2.size()); - for (auto p : pts2) pts_i.push_back(p.cast()); - svg.draw(pts_i, "red", 8 / Emboss::SHAPE_SCALE); - } //*/ - - if (max_iteration == 0) { - assert(false); - BoundingBox bb = get_extents(shape); - Point size = bb.size(); - if (size.x() < 10) bb.max.x() += 10; - if (size.y() < 10) bb.max.y() += 10; - - Polygon rect({// CCW - bb.min, - {bb.max.x(), bb.min.y()}, - bb.max, - {bb.min.x(), bb.max.y()}}); - - Point offset = bb.size() * 0.1; - Polygon hole({// CW - bb.min + offset, - {bb.min.x() + offset.x(), bb.max.y() - offset.y()}, - bb.max - offset, - {bb.max.x() - offset.x(), bb.min.y() + offset.y()}}); - // BAD symbol - shape = {ExPolygon(rect, hole)}; - return false; + // prepare for next loop + holes.clear(); + intersections.clear(); } - assert(intersection_points(shape).empty()); - assert(collect_duplicates(to_points(shape)).empty()); - return true; + // priv::visualize_heal("C:/data/temp/heal.svg", shape); + assert(false); + shape = {priv::create_bounding_rect(shape)}; + return false; } -std::optional priv::get_glyph(const stbtt_fontinfo &font_info, int unicode_letter, float flatness) +bool priv::heal_dupl_inter2(ExPolygons &shape, unsigned max_iteration) { + priv::remove_same_neighbor(shape); + + const float delta = 2.f; + const ClipperLib::JoinType joinType = ClipperLib::JoinType::jtRound; + + // remove double points + while (max_iteration) { + --max_iteration; + + // if(!priv::remove_self_intersections(shape, max_iteration)) break; + shape = Slic3r::union_ex(shape); + shape = Slic3r::closing_ex(shape, delta, joinType); + + // double minimal_area = 1000; + // priv::remove_small_islands(shape, minimal_area); + + // check that duplicits and intersections do NOT exists + Points duplicits = collect_duplicates(to_points(shape)); + Pointfs intersections_f = intersection_points(shape); + if (duplicits.empty() && intersections_f.empty()) + return true; + } + + // priv::visualize_heal("C:/data/temp/heal.svg", shape); + assert(false); + shape = {priv::create_bounding_rect(shape)}; + return false; +} + +ExPolygon priv::create_bounding_rect(const ExPolygons &shape) { + BoundingBox bb = get_extents(shape); + Point size = bb.size(); + if (size.x() < 10) + bb.max.x() += 10; + if (size.y() < 10) + bb.max.y() += 10; + + Polygon rect({// CCW + bb.min, + {bb.max.x(), bb.min.y()}, + bb.max, + {bb.min.x(), bb.max.y()}}); + + Point offset = bb.size() * 0.1; + Polygon hole({// CW + bb.min + offset, + {bb.min.x() + offset.x(), bb.max.y() - offset.y()}, + bb.max - offset, + {bb.max.x() - offset.x(), bb.min.y() + offset.y()}}); + + return ExPolygon(rect, hole); +} + +void priv::remove_small_islands(ExPolygons &expolygons, double minimal_area) { + if (expolygons.empty()) + return; + + // remove small expolygons contours + auto expoly_it = std::remove_if(expolygons.begin(), expolygons.end(), + [&minimal_area](const ExPolygon &p) { return p.contour.area() < minimal_area; }); + expolygons.erase(expoly_it, expolygons.end()); + + // remove small holes in expolygons + for (ExPolygon &expoly : expolygons) { + Polygons& holes = expoly.holes; + auto it = std::remove_if(holes.begin(), holes.end(), + [&minimal_area](const Polygon &p) { return -p.area() < minimal_area; }); + holes.erase(it, holes.end()); + } +} + +std::optional priv::get_glyph(const stbtt_fontinfo &font_info, int unicode_letter, float flatness) { int glyph_index = stbtt_FindGlyphIndex(&font_info, unicode_letter); if (glyph_index == 0) { @@ -404,7 +579,7 @@ std::optional priv::get_glyph(const stbtt_fontinfo &font_info, in return {}; } - Emboss::Glyph glyph; + Glyph glyph; stbtt_GetGlyphHMetrics(&font_info, glyph_index, &glyph.advance_width, &glyph.left_side_bearing); stbtt_vertex *vertices; @@ -447,6 +622,14 @@ std::optional priv::get_glyph(const stbtt_fontinfo &font_info, in // change outer cw to ccw and inner ccw to cw order std::reverse(pts.begin(), pts.end()); + + { // use douglas peucker to reduce amount of used points + pts.push_back(pts.front()); + pts = MultiPoint::_douglas_peucker(pts, flatness); + pts.pop_back(); + if (pts.size() < 3) + continue; + } glyph_polygons.emplace_back(pts); } if (!glyph_polygons.empty()) @@ -454,12 +637,12 @@ std::optional priv::get_glyph(const stbtt_fontinfo &font_info, in return glyph; } -const Emboss::Glyph* priv::get_glyph( - int unicode, - const Emboss::FontFile & font, - const FontProp & font_prop, - Emboss::Glyphs & cache, - std::optional &font_info_opt) +const Glyph* priv::get_glyph( + int unicode, + const FontFile & font, + const FontProp & font_prop, + Glyphs & cache, + fontinfo_opt &font_info_opt) { // TODO: Use resolution by printer configuration, or add it into FontProp const float RESOLUTION = 0.0125f; // [in mm] @@ -482,7 +665,7 @@ const Emboss::Glyph* priv::get_glyph( // Fix for very small flatness because it create huge amount of points from curve if (flatness < RESOLUTION) flatness = RESOLUTION; - std::optional glyph_opt = + std::optional glyph_opt = priv::get_glyph(*font_info_opt, unicode, flatness); // IMPROVE: multiple loadig glyph without data @@ -494,26 +677,26 @@ const Emboss::Glyph* priv::get_glyph( // scale glyph size glyph_opt->advance_width = - static_cast(glyph_opt->advance_width / Emboss::SHAPE_SCALE); + static_cast(glyph_opt->advance_width / SHAPE_SCALE); glyph_opt->left_side_bearing = - static_cast(glyph_opt->left_side_bearing / Emboss::SHAPE_SCALE); + static_cast(glyph_opt->left_side_bearing / SHAPE_SCALE); if (!glyph_opt->shape.empty()) { if (font_prop.boldness.has_value()) { - float delta = *font_prop.boldness / Emboss::SHAPE_SCALE / + float delta = *font_prop.boldness / SHAPE_SCALE / font_prop.size_in_mm; glyph_opt->shape = Slic3r::union_ex(offset_ex(glyph_opt->shape, delta)); } if (font_prop.skew.has_value()) { const float &ratio = *font_prop.skew; - auto skew = [&ratio](Slic3r::Polygon &polygon) { + auto skew = [&ratio](Polygon &polygon) { for (Slic3r::Point &p : polygon.points) { p.x() += p.y() * ratio; } }; for (ExPolygon &expolygon : glyph_opt->shape) { skew(expolygon.contour); - for (Slic3r::Polygon &hole : expolygon.holes) skew(hole); + for (Polygon &hole : expolygon.holes) skew(hole); } } } @@ -529,8 +712,8 @@ EmbossStyle priv::create_style(std::wstring name, std::wstring path) { } Point priv::to_point(const stbtt__point &point) { - return Point(static_cast(std::round(point.x / Emboss::SHAPE_SCALE)), - static_cast(std::round(point.y / Emboss::SHAPE_SCALE))); + return Point(static_cast(std::round(point.x / SHAPE_SCALE)), + static_cast(std::round(point.y / SHAPE_SCALE))); } #ifdef _WIN32 @@ -738,7 +921,7 @@ std::optional Emboss::get_font_path(const std::wstring &font_face_ } #endif -std::unique_ptr Emboss::create_font_file( +std::unique_ptr Emboss::create_font_file( std::unique_ptr> data) { int collection_size = stbtt_GetNumberOfFonts(data->data()); @@ -767,10 +950,10 @@ std::unique_ptr Emboss::create_font_file( infos.emplace_back(FontFile::Info{ascent, descent, linegap, units_per_em}); } - return std::make_unique(std::move(data), std::move(infos)); + return std::make_unique(std::move(data), std::move(infos)); } -std::unique_ptr Emboss::create_font_file(const char *file_path) +std::unique_ptr Emboss::create_font_file(const char *file_path) { FILE *file = std::fopen(file_path, "rb"); if (file == nullptr) { @@ -833,7 +1016,7 @@ static bool load_hfont(void* hfont, DWORD &dwTable, DWORD &dwOffset, size_t& siz return true; } -void * Emboss::can_load(HFONT hfont) +void *Emboss::can_load(void *hfont) { DWORD dwTable=0, dwOffset=0; size_t size = 0; @@ -841,7 +1024,7 @@ void * Emboss::can_load(HFONT hfont) return hfont; } -std::unique_ptr Emboss::create_font_file(HFONT hfont) +std::unique_ptr Emboss::create_font_file(void *hfont) { HDC hdc = ::CreateCompatibleDC(NULL); if (hdc == NULL) { @@ -868,7 +1051,7 @@ std::unique_ptr Emboss::create_font_file(HFONT hfont) } #endif // _WIN32 -std::optional Emboss::letter2glyph(const FontFile &font, +std::optional Emboss::letter2glyph(const FontFile &font, unsigned int font_index, int letter, float flatness) @@ -885,7 +1068,7 @@ ExPolygons Emboss::text2shapes(FontFileWithCache &font_with_cache, std::function was_canceled) { assert(font_with_cache.has_value()); - std::optional font_info_opt; + fontinfo_opt font_info_opt; Point cursor(0, 0); ExPolygons result; const FontFile& font = *font_with_cache.font_file; @@ -893,7 +1076,7 @@ ExPolygons Emboss::text2shapes(FontFileWithCache &font_with_cache, *font_prop.collection_number : 0; if (!priv::is_valid(font, font_index)) return {}; const FontFile::Info& info = font.infos[font_index]; - Emboss::Glyphs& cache = *font_with_cache.cache; + Glyphs& cache = *font_with_cache.cache; std::wstring ws = boost::nowide::widen(text); for (wchar_t wc: ws){ if (wc == '\n') { @@ -953,7 +1136,7 @@ void Emboss::apply_transformation(const FontProp &font_prop, bool Emboss::is_italic(const FontFile &font, unsigned int font_index) { if (font_index >= font.infos.size()) return false; - std::optional font_info_opt = priv::load_font_info(font.data->data(), font_index); + fontinfo_opt font_info_opt = priv::load_font_info(font.data->data(), font_index); if (!font_info_opt.has_value()) return false; stbtt_fontinfo *info = &(*font_info_opt); @@ -1036,7 +1219,7 @@ double Emboss::get_shape_scale(const FontProp &fp, const FontFile &ff) int unit_per_em = ff.infos[font_index].unit_per_em; double scale = fp.size_in_mm / unit_per_em; // Shape is scaled for store point coordinate as integer - return scale * Emboss::SHAPE_SCALE; + return scale * SHAPE_SCALE; } namespace priv { @@ -1055,7 +1238,7 @@ void add_quad(uint32_t i1, indexed_triangle_set polygons2model_unique( const ExPolygons &shape2d, - const Emboss::IProjection &projection, + const IProjection &projection, const Points &points) { // CW order of triangle indices @@ -1091,7 +1274,7 @@ indexed_triangle_set polygons2model_unique( // quads around - zig zag by triangles size_t polygon_offset = 0; auto add_quads = [&polygon_offset,&result, &count_point] - (const Slic3r::Polygon& polygon) { + (const Polygon& polygon) { uint32_t polygon_points = polygon.points.size(); // previous index uint32_t prev = polygon_offset + polygon_points - 1; @@ -1105,7 +1288,7 @@ indexed_triangle_set polygons2model_unique( for (const ExPolygon &expolygon : shape2d) { add_quads(expolygon.contour); - for (const Slic3r::Polygon &hole : expolygon.holes) add_quads(hole); + for (const Polygon &hole : expolygon.holes) add_quads(hole); } return result; @@ -1113,7 +1296,7 @@ indexed_triangle_set polygons2model_unique( indexed_triangle_set polygons2model_duplicit( const ExPolygons &shape2d, - const Emboss::IProjection &projection, + const IProjection &projection, const Points &points, const Points &duplicits) { @@ -1161,7 +1344,7 @@ indexed_triangle_set polygons2model_duplicit( // quads around - zig zag by triangles size_t polygon_offset = 0; auto add_quads = [&polygon_offset, &result, count_point, &changes] - (const Slic3r::Polygon &polygon) { + (const Polygon &polygon) { uint32_t polygon_points = polygon.points.size(); // previous index uint32_t prev = changes[polygon_offset + polygon_points - 1]; @@ -1176,7 +1359,7 @@ indexed_triangle_set polygons2model_duplicit( for (const ExPolygon &expolygon : shape2d) { add_quads(expolygon.contour); - for (const Slic3r::Polygon &hole : expolygon.holes) add_quads(hole); + for (const Polygon &hole : expolygon.holes) add_quads(hole); } return result; } diff --git a/src/libslic3r/Emboss.hpp b/src/libslic3r/Emboss.hpp index e188d3fe6..9803534c0 100644 --- a/src/libslic3r/Emboss.hpp +++ b/src/libslic3r/Emboss.hpp @@ -128,10 +128,9 @@ namespace Emboss // data = raw file data std::unique_ptr create_font_file(std::unique_ptr> data); #ifdef _WIN32 - // fix for unknown pointer HFONT - using HFONT = void*; - void * can_load(HFONT hfont); - std::unique_ptr create_font_file(HFONT hfont); + // fix for unknown pointer HFONT is replaced with "void *" + void * can_load(void* hfont); + std::unique_ptr create_font_file(void * hfont); #endif // _WIN32 /// diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index f5f238216..870da5516 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -40,6 +40,8 @@ bool check(const DataUpdate &input, bool is_main_thread = false, bool use_surfac bool check(const CreateSurfaceVolumeData &input, bool is_main_thread = false); bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread = false); +template static ExPolygons create_shape(DataBase &input, Fnc was_canceled); + // /// Try to create mesh from text /// @@ -49,7 +51,7 @@ bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread = false); /// NOTE: Cache glyphs is changed /// To check if process was canceled /// Triangle mesh model -template static TriangleMesh try_create_mesh(const DataBase &input, FontFileWithCache &font, Fnc was_canceled); +template static TriangleMesh try_create_mesh(DataBase &input, Fnc was_canceled); template static TriangleMesh create_mesh(DataBase &input, Fnc was_canceled, Job::Ctl &ctl); /// @@ -246,7 +248,7 @@ void UpdateJob::process(Ctl &ctl) if (cancel->load()) return true; return ctl.was_canceled(); }; - m_result = priv::try_create_mesh(m_input, m_input.font_file, was_canceled); + m_result = priv::try_create_mesh(m_input, was_canceled); if (was_canceled()) return; if (m_result.its.empty()) throw priv::JobException(_u8L("Created text volume is empty. Change text or font.").c_str()); @@ -426,22 +428,31 @@ bool priv::check(const UpdateSurfaceVolumeData &input, bool is_main_thread){ return res; } -template -TriangleMesh priv::try_create_mesh(const DataBase &input, FontFileWithCache &font, Fnc was_canceled) -{ - const TextConfiguration &tc = input.text_configuration; - const char *text = tc.text.c_str(); - const FontProp &prop = tc.style.prop; +template +ExPolygons priv::create_shape(DataBase &input, Fnc was_canceled) { + FontFileWithCache &font = input.font_file; + const TextConfiguration &tc = input.text_configuration; + const char *text = tc.text.c_str(); + const FontProp &prop = tc.style.prop; assert(font.has_value()); - if (!font.has_value()) return {}; + if (!font.has_value()) + return {}; - ExPolygons shapes = text2shapes(font, text, prop, was_canceled); + return text2shapes(font, text, prop, was_canceled); +} + +template +TriangleMesh priv::try_create_mesh(DataBase &input, Fnc was_canceled) +{ + ExPolygons shapes = priv::create_shape(input, was_canceled); if (shapes.empty()) return {}; if (was_canceled()) return {}; - - const auto &cn = prop.collection_number; + + const FontProp &prop = input.text_configuration.style.prop; + const std::optional &cn = prop.collection_number; unsigned int font_index = (cn.has_value()) ? *cn : 0; + const FontFileWithCache &font = input.font_file; assert(font_index < font.font_file->infos.size()); int unit_per_em = font.font_file->infos[font_index].unit_per_em; float scale = prop.size_in_mm / unit_per_em; @@ -459,7 +470,7 @@ TriangleMesh priv::create_mesh(DataBase &input, Fnc was_canceled, Job::Ctl& ctl) // Emboss text window is opened by creation new emboss text object TriangleMesh result; if (input.font_file.has_value()) { - result = try_create_mesh(input, input.font_file, was_canceled); + result = try_create_mesh(input, was_canceled); if (was_canceled()) return {}; } @@ -699,12 +710,8 @@ OrthoProject3d priv::create_emboss_projection( // input can't be const - cache of font TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2, std::function was_canceled) { - const TextConfiguration &tc = input1.text_configuration; - const char *text = tc.text.c_str(); - const FontProp &fp = tc.style.prop; - - ExPolygons shapes = text2shapes(input1.font_file, text, fp, was_canceled); - if (shapes.empty() || shapes.front().contour.empty()) + ExPolygons shapes = create_shape(input1, was_canceled); + if (shapes.empty()) throw JobException(_u8L("Font doesn't have any shape for given text.").c_str()); if (was_canceled()) return {}; @@ -716,6 +723,7 @@ TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2 bb.translate(-projection_center); const FontFile &ff = *input1.font_file.font_file; + const FontProp &fp = input1.text_configuration.style.prop; double shape_scale = get_shape_scale(fp, ff); const SurfaceVolumeData::ModelSources &sources = input2.sources; diff --git a/tests/data/contour_ALIENATO.TTF_glyph_i.svg b/tests/data/contour_ALIENATO.TTF_glyph_i.svg index f1d5a130f..66c1ff3e6 100644 --- a/tests/data/contour_ALIENATO.TTF_glyph_i.svg +++ b/tests/data/contour_ALIENATO.TTF_glyph_i.svg @@ -1,4 +1,4 @@ - + diff --git a/tests/data/contour_Allura_Script.ttf_glyph_m.svg b/tests/data/contour_Allura_Script.ttf_glyph_m.svg new file mode 100644 index 000000000..e86aaed6f --- /dev/null +++ b/tests/data/contour_Allura_Script.ttf_glyph_m.svg @@ -0,0 +1,3 @@ + + + diff --git a/tests/libslic3r/test_emboss.cpp b/tests/libslic3r/test_emboss.cpp index 2aba5ef19..fb51fce5b 100644 --- a/tests/libslic3r/test_emboss.cpp +++ b/tests/libslic3r/test_emboss.cpp @@ -216,20 +216,20 @@ ExPolygons heal_and_check(const Polygons &polygons) Pointfs intersections = intersection_points(shape); Points shape_points = to_points(shape); Points duplicits = collect_duplicates(shape_points); - //{ - // BoundingBox bb(polygons_points); - // // bb.scale(svg_scale); - // SVG svg("C:/data/temp/test_visualization.svg", bb); - // svg.draw(polygons, "gray"); // input - // svg.draw(shape, "green"); // output + { + BoundingBox bb(polygons_points); + // bb.scale(svg_scale); + SVG svg("C:/data/temp/test_visualization.svg", bb); + svg.draw(polygons, "gray"); // input + svg.draw(shape, "green"); // output - // Points pts; - // pts.reserve(intersections.size()); - // for (const Vec2d &intersection : intersections) - // pts.push_back(intersection.cast()); - // svg.draw(pts, "red", 10); - // svg.draw(duplicits, "orenge", 10); - //} + Points pts; + pts.reserve(intersections.size()); + for (const Vec2d &intersection : intersections) + pts.push_back(intersection.cast()); + svg.draw(pts, "red", 10); + svg.draw(duplicits, "orenge", 10); + } CHECK(intersections.empty()); CHECK(duplicits.empty()); @@ -241,39 +241,49 @@ void scale(Polygons &polygons, double multiplicator) { for (Point &p : polygon) p *= multiplicator; } -TEST_CASE("Heal of damaged polygons", "[Emboss]") -{ - // Shape loaded from svg is letter 'i' from font 'ALIENATE.TTF' - std::string file_name = "contour_ALIENATO.TTF_glyph_i.svg"; - std::string file_path = TEST_DATA_DIR PATH_SEPARATOR + file_name; +Polygons load_polygons(const std::string &svg_file) { + std::string file_path = TEST_DATA_DIR PATH_SEPARATOR + svg_file; NSVGimage *image = nsvgParseFromFile(file_path.c_str(), "px", 96.0f); Polygons polygons = NSVGUtils::to_polygons(image); nsvgDelete(image); + return polygons; +} - heal_and_check(polygons); +TEST_CASE("Heal of 'i' in ALIENATO.TTF", "[Emboss]") +{ + // Shape loaded from svg is letter 'i' from font 'ALIENATE.TTF' + std::string file_name = "contour_ALIENATO.TTF_glyph_i.svg"; + Polygons polygons = load_polygons(file_name); + + auto a = heal_and_check(polygons); Polygons scaled_shape = polygons; // copy scale(scaled_shape, 1 / Emboss::SHAPE_SCALE); - heal_and_check(scaled_shape); + auto b = heal_and_check(scaled_shape); // different scale scale(scaled_shape, 10.); - heal_and_check(scaled_shape); + auto c = heal_and_check(scaled_shape); // check reverse order of points Polygons reverse_shape = polygons; for (Polygon &p : reverse_shape) std::reverse(p.points.begin(), p.points.end()); - heal_and_check(scaled_shape); + auto d = heal_and_check(scaled_shape); #ifdef VISUALIZE CHECK(false); #endif // VISUALIZE } +TEST_CASE("Heal of 'm' in Allura_Script.ttf", "[Emboss]") +{ + Polygons polygons = load_polygons("contour_Allura_Script.ttf_glyph_m.svg"); + auto a = heal_and_check(polygons); +} + TEST_CASE("Heal of points close to line", "[Emboss]") { - // Shape loaded from svg is letter 'i' from font 'ALIENATE.TTF' std::string file_name = "points_close_to_line.svg"; std::string file_path = TEST_DATA_DIR PATH_SEPARATOR + file_name; NSVGimage *image = nsvgParseFromFile(file_path.c_str(), "px", 96.0f);