diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index c7837671b7..bc86d45152 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -6,6 +6,87 @@ using namespace Slic3r; +// do not expose out of this file stbtt_ data types +class Privat +{ +public: + Privat() = delete; + + static std::optional load_font_info(const Emboss::Font &font); + + struct Glyph + { + Polygons polygons; + int advance_width, left_side_bearing; + }; + static std::optional get_glyph(stbtt_fontinfo &font_info, int unicode_letter, float flatness = 2.f); +}; + +std::optional Privat::load_font_info(const Emboss::Font &font) +{ + int font_offset = stbtt_GetFontOffsetForIndex(font.buffer.data(), font.index); + if (font_offset < 0) { + std::cerr << "Font index("< Privat::get_glyph(stbtt_fontinfo &font_info, int unicode_letter, float flatness) +{ + int glyph_index = stbtt_FindGlyphIndex(&font_info, unicode_letter); + if (glyph_index == 0) { + std::cerr << "Character codepoint(" << unicode_letter + << " = '" << (char) unicode_letter << "') is not defined in the font."; + return {}; + } + + Privat::Glyph glyph; + stbtt_GetGlyphHMetrics(&font_info, glyph_index, &glyph.advance_width, &glyph.left_side_bearing); + + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(&font_info, glyph_index, &vertices); + if (num_verts <= 0) return glyph; // no shape + + int * contour_lengths = NULL; + int num_countour = 0; + stbtt__point *points = stbtt_FlattenCurves(vertices, num_verts, + flatness, + &contour_lengths, + &num_countour, + font_info.userdata); + + glyph.polygons.reserve(num_countour); + size_t pi = 0; // point index + for (size_t ci = 0; ci < num_countour; ++ci) { + int length = contour_lengths[ci]; + if (length <= 0) continue; + Points pts; + pts.reserve(length); + for (size_t i = 0; i < length; i++) { + const stbtt__point &point = points[pi]; + pi++; + pts.emplace_back(point.x, point.y); + } + if (pts.front() == pts.back()) { + pts.pop_back(); + } else { + int j = 42; + } + + glyph.polygons.emplace_back(pts); + } + + // inner ccw + // outer cw + return glyph; +} + std::optional Emboss::load_font(const char *file_path) { FILE *file = fopen(file_path, "rb"); @@ -43,69 +124,361 @@ std::optional Emboss::load_font(const char *file_path) // select default font on index 0 res.index = 0; res.count = index; - // first fonst has offset zero - font_offset = 0; - stbtt_fontinfo font_info; - if (stbtt_InitFont(&font_info, res.buffer.data(), font_offset) == 0) { - std::cerr << "Can't initialize font."; - return {}; - } + auto font_info = Privat::load_font_info(res); + if (!font_info.has_value()) return {}; // load information about line gap - stbtt_GetFontVMetrics(&(font_info), &res.ascent, &res.descent, &res.linegap); + stbtt_GetFontVMetrics(&(*font_info), &res.ascent, &res.descent, &res.linegap); return res; } Polygons Emboss::letter2polygons(const Font &font, char letter) { - int font_offset = stbtt_GetFontOffsetForIndex(font.buffer.data(), font.index); - stbtt_fontinfo font_info; - if (stbtt_InitFont(&font_info, font.buffer.data(), font_offset) == 0) - return Polygons(); + auto font_info_opt = Privat::load_font_info(font); + if (!font_info_opt.has_value()) return Polygons(); + stbtt_fontinfo *font_info = &(*font_info_opt); - int glyph_index = stbtt_FindGlyphIndex(&(font_info), letter); - int advanceWidth, leftSideBearing; - stbtt_GetGlyphHMetrics(&(font_info), glyph_index, &advanceWidth, - &leftSideBearing); + auto glyph_opt = Privat::get_glyph(*font_info_opt, (int) letter, font.flatness); + if (!glyph_opt.has_value()) return Polygons(); - stbtt_vertex *vertices; - int num_verts = stbtt_GetGlyphShape(&(font_info), glyph_index, &vertices); - if (num_verts < 0) return {}; // no shape + return glyph_opt->polygons; +} - int * contour_lengths = NULL; - int num_countour = 0; - stbtt__point *points = stbtt_FlattenCurves(vertices, num_verts, font.flatness, - &contour_lengths, - &num_countour, - (font_info).userdata); +Polygons Emboss::text2polygons(const Font &font, const std::string &text) +{ + auto font_info_opt = Privat::load_font_info(font); + if (!font_info_opt.has_value()) return Polygons(); + stbtt_fontinfo *font_info = &(*font_info_opt); + Point cursor(0, 0); Polygons result; - result.reserve(num_countour); - size_t pi = 0; // point index - for (size_t ci = 0; ci < num_countour; ++ci) { - int length = contour_lengths[ci]; - Points pts; - pts.reserve(length); - for (size_t i = 0; i < length; i++) { - const stbtt__point &point = points[pi]; - pi++; - pts.emplace_back(point.x, point.y); - } - result.emplace_back(pts); + for (const char &letter : text) { + if (letter == '\0') break; + + auto glyph_opt = Privat::get_glyph(*font_info_opt, (int) letter, font.flatness); + if (!glyph_opt.has_value()) continue; + + // move glyph to cursor position + Polygons polygons = glyph_opt->polygons; // copy + for (Polygon &polygon : polygons) + for (Point &p : polygon.points) p += cursor; + + cursor.x() += glyph_opt->advance_width; + + polygons_append(result, polygons); } - - BoundingBox bb; - for (auto &r : result) bb.merge(r.points); - - // inner ccw - // outer cw return result; } -indexed_triangle_set Emboss::create_model(const std::string &text, - const Font & font, - float z_size) +std::vector its_create_neighbors_index_2(const indexed_triangle_set &its) { - return indexed_triangle_set(); + std::vector out(its.indices.size(), Vec3i(-1, -1, -1)); + + // Create a mapping from triangle edge into face. + struct EdgeToFace + { + // Index of the 1st vertex of the triangle edge. vertex_low <= vertex_high. + int vertex_low; + // Index of the 2nd vertex of the triangle edge. + int vertex_high; + // Index of a triangular face. + int face; + // Index of edge in the face, starting with 1. Negative indices if the + // edge was stored reverse in (vertex_low, vertex_high). + int face_edge; + bool operator==(const EdgeToFace &other) const + { + return vertex_low == other.vertex_low && + vertex_high == other.vertex_high; + } + bool operator<(const EdgeToFace &other) const + { + return vertex_low < other.vertex_low || + (vertex_low == other.vertex_low && + vertex_high < other.vertex_high); + } + }; + std::vector edges_map; + edges_map.assign(its.indices.size() * 3, EdgeToFace()); + for (uint32_t facet_idx = 0; facet_idx < its.indices.size(); ++facet_idx) + for (int i = 0; i < 3; ++i) { + EdgeToFace &e2f = edges_map[facet_idx * 3 + i]; + e2f.vertex_low = its.indices[facet_idx][i]; + e2f.vertex_high = its.indices[facet_idx][(i + 1) % 3]; + e2f.face = facet_idx; + // 1 based indexing, to be always strictly positive. + e2f.face_edge = i + 1; + if (e2f.vertex_low > e2f.vertex_high) { + // Sort the vertices + std::swap(e2f.vertex_low, e2f.vertex_high); + // and make the face_edge negative to indicate a flipped edge. + e2f.face_edge = -e2f.face_edge; + } + } + std::sort(edges_map.begin(), edges_map.end()); + + // Assign a unique common edge id to touching triangle edges. + int num_edges = 0; + for (size_t i = 0; i < edges_map.size(); ++i) { + EdgeToFace &edge_i = edges_map[i]; + if (edge_i.face == -1) + // This edge has been connected to some neighbor already. + continue; + // Unconnected edge. Find its neighbor with the correct orientation. + size_t j; + bool found = false; + for (j = i + 1; j < edges_map.size() && edge_i == edges_map[j]; ++j) + if (edge_i.face_edge * edges_map[j].face_edge < 0 && + edges_map[j].face != -1) { + // Faces touching with opposite oriented edges and none of the + // edges is connected yet. + found = true; + break; + } + if (!found) { + // FIXME Vojtech: Trying to find an edge with equal orientation. + // This smells. + // admesh can assign the same edge ID to more than two facets (which is + // still topologically correct), so we have to search for a + // duplicate of this edge too in case it was already seen in this + // orientation + for (j = i + 1; j < edges_map.size() && edge_i == edges_map[j]; + ++j) + if (edges_map[j].face != -1) { + // Faces touching with equally oriented edges and none of + // the edges is connected yet. + found = true; + break; + } + } + // Assign an edge index to the 1st face. + // out[edge_i.face](std::abs(edge_i.face_edge) - 1) = num_edges; + if (found) { + EdgeToFace &edge_j = edges_map[j]; + out[edge_i.face](std::abs(edge_i.face_edge) - 1) = edge_j.face; + out[edge_j.face](std::abs(edge_j.face_edge) - 1) = edge_i.face; + // Mark the edge as connected. + edge_j.face = -1; + } + ++num_edges; + } + return out; +} + +void its_remove_edge_triangles(indexed_triangle_set &its) +{ + //// start, count + //std::vector> neighbors_vertices(its.vertices.size(), {0,0}); + //// calc counts + //for (const auto &i : its.indices) { + // for (size_t j = 0; j < 3; j++) + // ++neighbors_vertices[i[j]].second; + //} + //uint32_t triangle_start = 0; + //for (auto &neighbor : neighbors_vertices) { + // neighbor.first = triangle_start; + // triangle_start += neighbor.second; + // neighbor.second = 0; + //} + //std::vector neighbors_data(its.indices.size()*3); + //for (const auto &i : its.indices) { + // uint32_t index = &i - &its.indices.front(); + // for (size_t j = 0; j < 3; j++) { + // auto & neighbor = neighbors_vertices[i[j]]; + // size_t index_data = neighbor.second + neighbor.first; + // neighbors_data[index_data] = index; + // ++neighbor.second; + // } + //} + + auto neighbors = its_create_neighbors_index_2(its); + std::set remove; + std::queue insert; + int no_value = -1; + for (const auto &neighbor : neighbors) { + uint32_t index = &neighbor - &neighbors.front(); + auto it = remove.find(index); + if (it != remove.end()) continue; // already removed + + if (neighbor[0] != no_value && + neighbor[1] != no_value && + neighbor[2] != no_value) + continue; + + insert.push(index); + while (!insert.empty()) { + uint32_t i = insert.front(); + insert.pop(); + if (remove.find(i) != remove.end()) continue; + remove.insert(i); + for (size_t j = 0; j < 3; j++) { + if (neighbor[j] == no_value) continue; + uint32_t i2 = static_cast(neighbor[j]); + insert.push(i2); + } + } + } + std::vector rem(remove.begin(), remove.end()); + std::sort(rem.begin(), rem.end()); + uint32_t offset = 0; + for (uint32_t i : rem) { + its.indices.erase(its.indices.begin() + i - offset); + ++offset; + } +} + +indexed_triangle_set Emboss::polygons2model(const Polygons &shape2d, + const IProject &projection) +{ + indexed_triangle_set result; + size_t count_point = count_points(shape2d); + result.vertices.reserve(2 * count_point); + + std::vector &front_points = result.vertices; + std::vector back_points; + back_points.reserve(count_point); + + for (const Polygon &polygon : shape2d) { + for (const Point &p : polygon.points) { + auto p2 = projection.project(p); + front_points.emplace_back(p2.first); + back_points.emplace_back(p2.second); + } + } + // insert back points, front are already in + result.vertices.insert(result.vertices.end(), + std::make_move_iterator(back_points.begin()), + std::make_move_iterator(back_points.end())); + + // CW order of triangle indices + std::vector shape_triangles = triangulate(shape2d); + result.indices.reserve(shape_triangles.size() * 2 + count_point * 2); + // top triangles - change to CCW + for (const Vec3i &t : shape_triangles) + result.indices.emplace_back(t.x(), t.z(), t.y()); + // bottom triangles - use CW + for (const Vec3i &t : shape_triangles) + result.indices.emplace_back(t.x() + count_point, t.y() + count_point, + t.z() + count_point); + + // quads around - zig zag by triangles + size_t polygon_offset = 0; + for (const Polygon &polygon : shape2d) { + uint32_t polygon_points = polygon.points.size(); + for (uint32_t p = 0; p < polygon_points; p++) { + uint32_t i = polygon_offset + p; + // previous index + uint32_t ip = (p == 0) ? (polygon_offset + polygon_points - 1) : (i - 1); + // bottom indices + uint32_t i2 = i + count_point; + uint32_t ip2 = ip + count_point; + + result.indices.emplace_back(i, i2, ip); + result.indices.emplace_back(ip2, ip, i2); + } + polygon_offset += polygon_points; + } + + // remove bad triangulated faces + its_remove_edge_triangles(result); + return result; +} + +#include +#include +#include +std::vector Emboss::triangulate( + const Points& points, + const std::set> &edges) +{ + // IMPROVE use int point insted of float !!! + + // use cgal triangulation + using K = CGAL::Exact_predicates_inexact_constructions_kernel; + using Itag = CGAL::Exact_predicates_tag; + using CDT = CGAL::Constrained_Delaunay_triangulation_2; + using Point = CDT::Point; + + // construct a constrained triangulation + CDT cdt; + std::map map; // for indices + std::vector vertices_handle; // for constriants + vertices_handle.reserve(points.size()); + for (const auto& p: points) { + Point cdt_p(p.x(), p.y()); + auto handl = cdt.insert(cdt_p); + vertices_handle.push_back(handl); + size_t i = &p - &points.front(); + map[handl] = i; + } + + // triangle can not contain forbiden edge + for (const std::pair &edge : edges) { + const CDT::Vertex_handle& vh1 = vertices_handle[edge.first]; + const CDT::Vertex_handle& vh2 = vertices_handle[edge.second]; + cdt.insert_constraint(vh1, vh2); + } + + auto faces = cdt.finite_face_handles(); + std::vector indices; + indices.reserve(faces.size()); + for (CDT::Face_handle face : faces) { + auto v0 = face->vertex(0); + auto v1 = face->vertex(1); + auto v2 = face->vertex(2); + uint32_t i0 = map[v0]; + uint32_t i1 = map[v1]; + uint32_t i2 = map[v2]; + + // check forbiden triangle edge - opposit order + if (edges.find(std::make_pair(i0, i1)) != edges.end()) continue; + if (edges.find(std::make_pair(i1, i2)) != edges.end()) continue; + if (edges.find(std::make_pair(i2, i0)) != edges.end()) continue; + + indices.emplace_back(map[v0], map[v1], map[v2]); + } + return indices; +} + +std::vector Emboss::triangulate(const Polygon &polygon) +{ + const Points & pts = polygon.points; + std::set> edges; + for (uint32_t i = 1; i < pts.size(); ++i) edges.insert({i - 1, i}); + edges.insert({(uint32_t)pts.size() - 1, uint32_t(0)}); + return triangulate(pts, edges); +} + +std::vector Emboss::triangulate(const Polygons &polygons) +{ + size_t count = count_points(polygons); + Points points; + points.reserve(count); + for (const Polygon &polygon : polygons) + points.insert(points.end(), polygon.points.begin(), + polygon.points.end()); + + std::set> edges; + uint32_t offset = 0; + for (const Polygon& polygon : polygons) { + const Points &pts = polygon.points; + for (uint32_t i = 1; i < pts.size(); ++i) { + uint32_t i2 = i + offset; + edges.insert({i2 - 1, i2}); + } + uint32_t size = static_cast(pts.size()); + // add connection from first to last point + edges.insert({offset + size - 1, offset}); + offset += size; + } + return triangulate(points, edges); +} + +std::pair Emboss::ProjectZ::project(const Point &p) const +{ + Vec3f front(p.x(),p.y(),0.f); + Vec3f back = front; // copy + back.z() = m_depth; + return std::make_pair(front, back); } diff --git a/src/libslic3r/Emboss.hpp b/src/libslic3r/Emboss.hpp index 4086f775dd..069f2ae233 100644 --- a/src/libslic3r/Emboss.hpp +++ b/src/libslic3r/Emboss.hpp @@ -2,7 +2,9 @@ #define slic3r_Emboss_hpp_ #include +#include #include +#include #include // indexed_triangle_set #include "Polygon.hpp" @@ -38,6 +40,8 @@ public: // change size of font float scale = 1.; + + // enum class Align: center/left/right }; /// @@ -53,20 +57,80 @@ public: /// Define fonts /// Character to convert /// inner polygon ccw(outer cw) - static Polygons letter2polygons(const Font &font, char letter); + static Polygons letter2polygons(const Font &font, char letter); + /// + /// Convert text into polygons + /// + /// Define fonts + /// Character to convert + /// inner polygon ccw(outer cw) static Polygons text2polygons(const Font &font, const std::string &text); + /// + /// Project 2d point into space + /// Could be plane, sphere, cylindric, ... + /// + class IProject + { + public: + virtual ~IProject() = default; + /// + /// convert 2d point to 3d point + /// + /// 2d coordinate + /// + /// first - front spatial point + /// second - back spatial point + /// + virtual std::pair project(const Point &p) const = 0; + }; + /// /// Create triangle model for text /// - /// - /// - /// - /// - static indexed_triangle_set create_model(const std::string &text, - const Font & font, - float z_size); + /// text or image + /// Define transformation from 2d to 3d(orientation, position, scale, ...) + /// Projected shape into space + static indexed_triangle_set polygons2model(const Polygons &shape2d, const IProject& projection); + + /// + /// Connect points by triangulation to create filled surface by triangle indices + /// + /// Points to connect + /// Constraint for edges, pair is from point(first) to point(second) + /// Triangles + static std::vector triangulate(const Points &points, const std::set> &edges); + static std::vector triangulate(const Polygon &polygon); + static std::vector triangulate(const Polygons &polygons); + + class ProjectZ : public IProject + { + public: + ProjectZ(float depth) : m_depth(depth) {} + // Inherited via IProject + virtual std::pair project(const Point &p) const override; + float m_depth; + }; + + class ProjectScale : public IProject + { + std::unique_ptr core; + public: + ProjectScale(std::unique_ptr core, float scale) + : m_scale(scale) + , core(std::move(core)) + {} + + // Inherited via IProject + virtual std::pair project(const Point &p) const override + { + auto res = core->project(p); + return std::make_pair(res.first * m_scale, res.second * m_scale); + } + + float m_scale; + }; }; } // namespace Slic3r diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index 333f1e6b1a..7abf7fb5b9 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -154,13 +154,17 @@ inline Points to_points(const Polygon &poly) return poly.points; } +inline size_t count_points(const Polygons &polys) { + size_t n_points = 0; + for (size_t i = 0; i < polys.size(); ++i) + n_points += polys[i].points.size(); + return n_points; +} + inline Points to_points(const Polygons &polys) { - size_t n_points = 0; - for (size_t i = 0; i < polys.size(); ++ i) - n_points += polys[i].points.size(); Points points; - points.reserve(n_points); + points.reserve(count_points(polys)); for (const Polygon &poly : polys) append(points, poly.points); return points; @@ -180,11 +184,8 @@ inline Lines to_lines(const Polygon &poly) inline Lines to_lines(const Polygons &polys) { - size_t n_lines = 0; - for (size_t i = 0; i < polys.size(); ++ i) - n_lines += polys[i].points.size(); Lines lines; - lines.reserve(n_lines); + lines.reserve(count_points(polys)); for (size_t i = 0; i < polys.size(); ++ i) { const Polygon &poly = polys[i]; for (Points::const_iterator it = poly.points.begin(); it != poly.points.end()-1; ++it) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index dee4cd3b4d..b6eb3be335 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -7,7 +7,7 @@ #include "slic3r/GUI/Plater.hpp" #include "libslic3r/Model.hpp" -#include "libslic3r/QuadricEdgeCollapse.hpp" +#include "libslic3r/Emboss.hpp" namespace Slic3r::GUI { @@ -19,15 +19,10 @@ GLGizmoEmboss::GLGizmoEmboss(GLCanvas3D & parent, Slic3r::resources_dir() + "/fonts/NotoSans-Regular.ttf"}, {"Arial", "C:/windows/fonts/arialbd.ttf"}}) , m_selected(1) - , m_text_size(255) - , m_text(new char[255]) + , m_text(_u8L("Embossed text")) { // TODO: suggest to use https://fontawesome.com/ - int index = 0; - for (char &c : _u8L("Embossed text")) { - m_text[index++] = c; - } - m_text[index] = '\0'; + m_text.reserve(255); } GLGizmoEmboss::~GLGizmoEmboss() {} @@ -66,16 +61,47 @@ void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit) } ImGui::SameLine(); ImGui::Button("Add"); + static float scale = 0.01f; + ImGui::InputFloat("Scale", &scale); + static float emboss = 100.f; + ImGui::InputFloat("Emboss", &emboss); + + if(ImGui::Button("Preview")){ + + auto project = std::make_unique( + std::make_unique(emboss) + ,scale); + + auto font_path = m_fonts[m_selected].file_path.c_str(); + auto font_opt = Emboss::load_font(font_path); + if (font_opt.has_value()) { + Polygons polygons = Emboss::text2polygons(*font_opt, m_text); + indexed_triangle_set its = Emboss::polygons2model(polygons, *project); + // add object + TriangleMesh tm(its); + tm.repair(); + + tm.WriteOBJFile("text_preview.obj"); + + const Selection &selection = m_parent.get_selection(); + int object_idx = selection.get_object_idx(); + ModelObject *obj = wxGetApp().plater()->model().objects[object_idx]; + ModelVolume *v = obj->volumes.front(); + v->set_mesh(tm); + v->set_new_unique_id(); + obj->invalidate_bounding_box(); + m_parent.reload_scene(true); + } + } - size_t text_size = 255; - //sizeof(*m_text.get()); ImVec2 input_size(-FLT_MIN, ImGui::GetTextLineHeight() * 6); ImGuiInputTextFlags flags = ImGuiInputTextFlags_::ImGuiInputTextFlags_AllowTabInput | ImGuiInputTextFlags_::ImGuiInputTextFlags_AutoSelectAll //|ImGuiInputTextFlags_::ImGuiInputTextFlags_CtrlEnterForNewLine ; - if (ImGui::InputTextMultiline("##Text", m_text.get(), text_size, input_size, flags)) { + if (ImGui::InputTextMultiline("##Text", (char *) m_text.c_str(), + m_text.capacity() + 1, input_size, flags)) { } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index 13ec92098f..3f8b67ec62 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -30,26 +30,6 @@ protected: private: void close(); - struct MyFont; - MyFont createFont(const char * fileName); - - struct Configuration - { - bool use_count = false; - // minimal triangle count - float decimate_ratio = 50.f; // in percent - uint32_t wanted_count = 0; // initialize by percents - - // maximal quadric error - float max_error = 1.; - - void fix_count_by_ratio(size_t triangle_count) - { - wanted_count = static_cast( - std::round(triangle_count * (100.f-decimate_ratio) / 100.f)); - } - } m_configuration; - // This configs holds GUI layout size given by translated texts. // etc. When language changes, GUI is recreated and this class constructed again, // so the change takes effect. (info by GLGizmoFdmSupports.hpp) @@ -72,9 +52,7 @@ private: std::vector m_fonts; size_t m_selected;// index to m_fonts - - size_t m_text_size; // allocated size by m_text - std::unique_ptr m_text; + std::string m_text; }; } // namespace GUI diff --git a/tests/libslic3r/test_meshboolean.cpp b/tests/libslic3r/test_meshboolean.cpp index 1db26381c7..9f799373ef 100644 --- a/tests/libslic3r/test_meshboolean.cpp +++ b/tests/libslic3r/test_meshboolean.cpp @@ -62,9 +62,13 @@ Polygons ttf2polygons(const char * font_name, char letter, float flatness = 1.f) void store_to_svg(Polygons polygons,std::string file_name = "letter.svg") { double scale = 1e6; - for (auto &p : polygons) p.scale(scale); - SVG svg("letter.svg", BoundingBox(polygons.front().points)); - svg.draw(polygons); + BoundingBox bb; + for (auto& p : polygons) { + p.scale(scale); + bb.merge(p.points); + } + SVG svg(file_name, bb); + svg.draw(polygons); } struct Plane