WIP: Create another approach to heal shape BUT it is not working

This commit is contained in:
Filip Sykala - NTB T15p 2023-01-20 17:33:53 +01:00
parent 8c2ac9d83b
commit 8f09c3ac82
6 changed files with 377 additions and 174 deletions

View File

@ -20,43 +20,79 @@
#include "libslic3r/BoundingBox.hpp"
using namespace Slic3r;
using namespace Emboss;
using fontinfo_opt = std::optional<stbtt_fontinfo>;
// do not expose out of this file stbtt_ data types
namespace priv{
bool is_valid(const Emboss::FontFile &font, unsigned int index);
std::optional<stbtt_fontinfo> load_font_info(const unsigned char *data, unsigned int index = 0);
std::optional<Emboss::Glyph> 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<Glyph> 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<stbtt_fontinfo> &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<stbtt_fontinfo> 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<stbtt_fontinfo> 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<double>(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<int>(); });
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<double>(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<int>());
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<Emboss::Glyph> 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<Glyph> 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<Emboss::Glyph> 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<Emboss::Glyph> 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<Emboss::Glyph> 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<stbtt_fontinfo> &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<Emboss::Glyph> glyph_opt =
std::optional<Glyph> 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<int>(glyph_opt->advance_width / Emboss::SHAPE_SCALE);
static_cast<int>(glyph_opt->advance_width / SHAPE_SCALE);
glyph_opt->left_side_bearing =
static_cast<int>(glyph_opt->left_side_bearing / Emboss::SHAPE_SCALE);
static_cast<int>(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<int>(std::round(point.x / Emboss::SHAPE_SCALE)),
static_cast<int>(std::round(point.y / Emboss::SHAPE_SCALE)));
return Point(static_cast<int>(std::round(point.x / SHAPE_SCALE)),
static_cast<int>(std::round(point.y / SHAPE_SCALE)));
}
#ifdef _WIN32
@ -738,7 +921,7 @@ std::optional<std::wstring> Emboss::get_font_path(const std::wstring &font_face_
}
#endif
std::unique_ptr<Emboss::FontFile> Emboss::create_font_file(
std::unique_ptr<FontFile> Emboss::create_font_file(
std::unique_ptr<std::vector<unsigned char>> data)
{
int collection_size = stbtt_GetNumberOfFonts(data->data());
@ -767,10 +950,10 @@ std::unique_ptr<Emboss::FontFile> Emboss::create_font_file(
infos.emplace_back(FontFile::Info{ascent, descent, linegap, units_per_em});
}
return std::make_unique<Emboss::FontFile>(std::move(data), std::move(infos));
return std::make_unique<FontFile>(std::move(data), std::move(infos));
}
std::unique_ptr<Emboss::FontFile> Emboss::create_font_file(const char *file_path)
std::unique_ptr<FontFile> 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::FontFile> Emboss::create_font_file(HFONT hfont)
std::unique_ptr<FontFile> Emboss::create_font_file(void *hfont)
{
HDC hdc = ::CreateCompatibleDC(NULL);
if (hdc == NULL) {
@ -868,7 +1051,7 @@ std::unique_ptr<Emboss::FontFile> Emboss::create_font_file(HFONT hfont)
}
#endif // _WIN32
std::optional<Emboss::Glyph> Emboss::letter2glyph(const FontFile &font,
std::optional<Glyph> 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<bool()> was_canceled)
{
assert(font_with_cache.has_value());
std::optional<stbtt_fontinfo> 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<stbtt_fontinfo> 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;
}

View File

@ -128,10 +128,9 @@ namespace Emboss
// data = raw file data
std::unique_ptr<FontFile> create_font_file(std::unique_ptr<std::vector<unsigned char>> data);
#ifdef _WIN32
// fix for unknown pointer HFONT
using HFONT = void*;
void * can_load(HFONT hfont);
std::unique_ptr<FontFile> create_font_file(HFONT hfont);
// fix for unknown pointer HFONT is replaced with "void *"
void * can_load(void* hfont);
std::unique_ptr<FontFile> create_font_file(void * hfont);
#endif // _WIN32
/// <summary>

View File

@ -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<typename Fnc> static ExPolygons create_shape(DataBase &input, Fnc was_canceled);
// <summary>
/// Try to create mesh from text
/// </summary>
@ -49,7 +51,7 @@ bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread = false);
/// NOTE: Cache glyphs is changed</param>
/// <param name="was_canceled">To check if process was canceled</param>
/// <returns>Triangle mesh model</returns>
template<typename Fnc> static TriangleMesh try_create_mesh(const DataBase &input, FontFileWithCache &font, Fnc was_canceled);
template<typename Fnc> static TriangleMesh try_create_mesh(DataBase &input, Fnc was_canceled);
template<typename Fnc> static TriangleMesh create_mesh(DataBase &input, Fnc was_canceled, Job::Ctl &ctl);
/// <summary>
@ -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<typename Fnc>
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<typename Fnc>
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<typename Fnc>
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<unsigned int> &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<bool()> 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;

View File

@ -1,4 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000" width="1000px" height="1000px">
<svg xmlns="http://www.w3.org/2000/svg">
<path fill="#808080" d="M 437 868 425 886 417 866 405 884 400 874 388 880 402 896 381 899 395 915 374 918 388 934 367 936 381 952 360 955 374 971 360 982 371 973 353 961 373 953 355 941 375 933 357 921 377 913 359 901 379 894 361 882 372 869 365 857 350 873 346 852 331 867 326 856 313 859 323 878 301 875 311 894 290 892 300 911 279 909 289 927 267 925 278 944 264 947 276 941 262 925 283 922 269 906 290 903 275 887 297 884 282 869 303 866 289 850 300 847 296 834 278 846 278 824 260 836 254 822 240 822 246 843 226 836 231 856 211 849 217 870 197 863 202 884 182 877 188 898 180 892 193 889 183 870 204 872 193 853 215 855 204 836 225 838 214 819 232 809 230 795 210 804 214 783 195 792 188 774 175 771 175 792 157 780 158 802 140 790 140 812 122 800 123 822 110 818 124 818 118 797 138 804 132 783 153 790 147 769 167 776 161 756 172 758 173 744 152 749 152 734 154 720 133 723 134 708 122 702 118 723 103 707 98 729 83 713 79 734 60 730 73 733 72 712 91 723 90 701 108 713 107 691 124 692 129 679 107 678 120 661 99 659 112 643 91 641 96 630 86 621 76 640 66 621 56 641 46 622 36 641 30 632 42 638 47 617 61 633 66 612 81 627 85 606 92 614 100 603 79 596 96 583 76 576 93 563 72 556 81 550 81 542 73 531 60 548 53 527 40 544 25 532 36 541 44 521 56 539 64 520 81 528 92 520 75 507 95 500 77 487 98 480 80 467 92 454 87 441 70 455 68 433 52 447 42 432 50 443 63 426 70 446 83 428 96 440 108 434 94 418 115 415 101 399 122 396 108 380 122 369 120 355 100 364 104 343 85 352 82 338 87 351 104 338 105 359 122 346 126 358 139 354 128 336 149 337 138 319 160 320 148 302 170 303 159 284 168 287 168 273 148 278 142 258 145 272 164 262 172 279 185 276 175 257 197 259 187 241 208 243 198 224 219 226 209 207 224 202 220 192 226 199 231 196 245 193 235 174 256 177 246 158 260 155 274 155 267 134 288 141 282 120 302 126 296 106 313 102 327 101 319 81 338 73 352 73 346 52 366 60 361 39 380 34 393 38 394 17 411 29 412 8 429 21 430 -1 440 5 448 17 462 0 467 21 481 5 496 18 499 32 518 22 515 43 534 34 543 52 544 66 564 58 570 76 569 90 590 86 582 105 603 101 594 121 615 117 614 130 614 144 635 137 628 158 649 152 655 171 653 185 674 182 665 201 686 198 684 211 698 209 690 190 700 192 687 197 700 214 688 218 686 232 707 229 697 248 718 246 708 265 730 262 720 281 741 279 736 288 750 290 747 269 766 278 763 257 778 258 764 260 773 279 752 275 760 295 744 299 741 312 762 311 751 330 772 329 761 347 783 346 777 355 780 364 793 369 794 347 811 361 813 339 830 353 831 331 839 338 825 336 829 358 809 348 813 370 793 361 797 382 786 378 780 391 802 394 787 409 809 412 794 428 815 431 810 444 821 452 828 431 841 448 848 428 861 445 867 424 879 432 866 427 865 449 848 436 846 457 829 444 828 465 816 460 808 471 828 479 811 491 831 498 813 511 833 518 825 527 834 537 846 519 854 539 866 520 874 540 886 522 896 532 884 525 878 545 865 528 859 549 845 532 839 553 824 546 813 555 831 567 811 575 828 587 808 594 826 607 816 610 823 622 838 607 842 628 857 612 861 633 876 618 880 622 870 640 861 621 850 639 841 620 830 638 821 619 810 637 810 628 797 634 811 650 790 652 803 669 782 671 796 687 786 686 790 699 808 688 807 709 825 698 824 719 843 708 842 730 860 718 861 730 854 718 839 734 835 713 820 728 816 707 801 723 796 702 781 717 774 705 760 707 770 727 749 724 758 743 741 750 741 764 762 757 756 778 776 771 770 792 790 785 784 806 805 799 810 818 806 805 788 816 789 795 771 806 771 785 753 797 754 775 736 787 736 765 718 777 722 768 708 768 713 788 693 781 699 802 684 802 681 816 703 814 692 832 713 831 703 849 724 848 713 866 735 865 724 883 746 881 741 892 740 878 720 885 726 865 705 872 711 851 691 858 696 837 676 844 682 824 662 831 662 818 649 814 649 836 631 823 631 845 619 840 613 853 635 856 620 871 641 875 626 890 648 894 633 909 654 913 639 928 661 931 656 947 658 933 637 936 647 917 626 919 636 900 615 903 625 884 603 886 614 867 592 869 592 852 580 846 576 867 561 851 556 872 548 865 540 876 560 884 542 896 562 904 545 916 565 923 547 936 567 943 549 955 569 963 562 982 567 969 546 967 560 951 539 948 553 932 532 929 546 913 525 911 539 895 517 892 520 872 509 864 501 884 489 867 482 887 476 878 465 887 483 899 462 906 480 919 460 926 477 939 457 946 474 959 454 966 472 978 460 994 468 983 448 975 466 963 446 955 463 943 443 936 461 923 441 916 459 904 439 896 446 878 z" />
<path fill="#404040" d="M 391 214 383 234 371 216 363 236 354 228 342 222 338 243 323 228 319 249 304 235 300 256 296 247 283 242 282 264 265 251 263 272 246 259 245 280 240 272 226 272 232 293 212 286 218 307 197 300 203 320 188 322 175 326 186 344 165 343 176 361 155 361 166 379 156 378 144 384 158 400 136 403 151 419 129 421 143 437 130 446 120 455 138 467 118 475 136 487 116 495 135 507 124 520 135 529 143 509 155 527 163 508 175 526 183 506 195 524 203 505 204 514 217 519 219 498 235 511 237 490 254 504 256 482 270 487 283 491 283 469 301 481 301 460 316 462 331 450 345 451 340 430 360 438 355 417 375 425 370 404 380 408 393 404 382 386 403 387 392 369 404 366 416 359 401 344 422 340 407 325 421 311 432 303 416 289 437 284 420 270 432 259 433 245 412 249 420 230 399 234 402 222 z" />
<path fill="#404040" d="M 467 232 486 242 476 254 469 266 489 272 473 285 494 291 477 305 498 311 481 324 502 330 496 347 491 360 512 362 508 377 507 391 528 386 521 406 542 401 534 421 555 416 551 426 553 440 572 431 568 452 588 444 596 463 598 477 618 467 622 482 628 495 644 481 646 502 663 488 669 501 676 513 691 497 696 517 710 501 721 514 730 525 742 507 750 527 762 510 769 530 782 512 784 522 788 520 796 509 776 501 794 489 774 481 782 464 788 452 767 448 782 433 761 429 776 413 755 410 770 394 749 391 756 382 758 368 737 371 747 352 725 355 735 336 714 338 718 311 697 314 706 295 685 298 692 292 689 279 670 289 672 268 653 279 655 257 636 268 638 247 619 257 622 248 615 236 600 252 596 231 581 246 576 225 562 241 558 230 549 220 537 237 529 218 517 236 509 216 497 234 489 214 477 232 477 222 z" />

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg">
<path fill="#808080" d="M 815000 -18000 831750 -8750 846000 7000 871000 46000 877000 60000 884000 74000 886000 79000 892000 89000 896000 100000 897250 104000 897000 106000 895500 105500 893000 102000 887000 93000 881000 83000 879000 78000 878500 77000 881999 81999 881000 80000 880000 79000 878000 76000 877000 74000 876000 73000 874000 69000 872000 67000 871000 65000 870000 64000 859250 51750 847000 45000 849000 45000 835500 40750 823000 42000 811250 48250 800000 57000 782000 82000 768000 110000 749500 152750 734000 197000 708000 288000 686000 368000 679000 391000 671000 414000 667250 418250 662000 421000 651000 425000 631250 426250 612000 422000 590500 410250 572000 393000 556750 373000 543000 351000 510750 290500 484000 225000 467898 169648 465000 192000 448000 257000 438000 288250 426000 318000 413000 344500 398000 370000 381750 393000 363000 412000 344000 423000 334500 427000 324000 429000 313000 429000 303000 428000 291000 426000 279000 422000 265625 415563 253500 407250 242625 397063 233000 385000 215750 358250 200000 330000 185250 297250 173000 263000 162750 228250 154000 192000 145000 140000 139624 105054 138000 115000 133000 165000 125000 225000 115000 284000 101000 337000 91500 362250 79000 386000 63750 405750 46000 421000 26250 431750 5000 438000 -9000 439000 -20000 439000 -24000 435000 -27000 430000 -33000 421000 -38000 409000 -43000 398000 -48000 388000 -48000 386500 -50000 382000 -51000 378250 -50000 377000 -27000 376500 -4000 371000 17500 360000 36000 343000 51250 322750 63000 300000 80000 250000 92250 194500 99000 138000 113000 25000 117000 5000 121000 8000 124000 13000 127000 19000 136750 37000 144000 55000 152500 80250 158000 106000 168000 158000 178000 218000 190000 278000 191000 282000 193000 288000 194000 292000 196000 297000 202000 318000 203000 320999 203001 320999 203001 321001 204000 322750 203801 322601 203800 322601 204000 323000 206000 325000 209000 329000 210000 331000 216000 337000 228250 348500 243000 359000 242000 357000 264250 365250 287000 368000 306250 365500 326000 356000 346500 339500 364000 318000 378750 294250 391000 269000 410000 223750 425000 176000 436000 127500 443000 78000 443000 79000 444000 52000 444000 27000 444250 24500 445000 25000 447000 28000 457000 46000 468000 69000 471000 77000 473000 85000 476000 130000 485000 176000 487000 189000 491000 204000 492000 206000 492500 208250 492000 207000 494000 215000 496000 222000 498000 230000 502000 245000 507000 259000 508500 263750 509000 266000 510000 270000 513000 277000 515000 284000 515353 284999 515354 284999 515354 285000 515500 285000 517000 288999 517001 288999 517001 289001 517000 289001 518000 292000 520000 295000 520000 295500 520250 295938 520000 295250 521000 297000 522000 298000 524000 303000 525750 306000 526999 307922 527000 307922 527000 307750 528999 310999 529001 310999 529001 311001 532000 316000 534000 319000 537000 322000 541000 328000 547000 333000 552000 340000 563000 350000 577000 359000 575000 357000 594000 364000 603500 365000 614000 364000 624000 362750 634000 359000 637500 358000 640000 355000 643750 348750 647000 340000 651000 327000 656000 314000 678000 228000 703000 144000 717000 99000 735000 56000 751000 23000 772000 -5000 792000 -19000 803250 -20750 z M 202999 320999 202430 320287 202429 320287 203000 321999 203001 321999 203001 322001 203443 322332 203445 322332 203445 322334 203799 322599 203000 321001 202999 321001 z M 203000 321999 202200 320001 202199 320001 202199 319999 202000 319750 z M 202000 319500 202200 319999 202201 319999 202201 320001 202428 320285 202429 320285 202000 319000 z M 527000 307924 527000 308000 528999 310999 527001 307924 z M 516500 288250 516999 288999 516200 287401 z M 515000 285000 516199 287399 516200 287399 515353 285001 515352 285001 515352 285000 z M 881000 81500 881000 81878 881063 82000 882250 84000 882563 84000 882000 82001 881999 82001 881999 81999 880333 80333 z " />
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -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<int>());
// 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<int>());
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);