mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-21 05:37:52 -06:00
Port Emboss & SVG gizmo from PrusaSlicer (#2819)
* Rework UI jobs to make them more understandable and flexible. * Update Orca specific jobs * Fix progress issue * Fix dark mode and window radius * Update cereal version from 1.2.2 to 1.3.0 (cherry picked from commit prusa3d/PrusaSlicer@057232a275) * Initial port of Emboss gizmo * Bump up CGAL version to 5.4 (cherry picked from commit prusa3d/PrusaSlicer@1bf9dee3e7) * Fix text rotation * Fix test dragging * Add text gizmo to right click menu * Initial port of SVG gizmo * Fix text rotation * Fix Linux build * Fix "from surface" * Fix -90 rotation * Fix icon path * Fix loading font with non-ascii name * Fix storing non-utf8 font descriptor in 3mf file * Fix filtering with non-utf8 characters * Emboss: Use Orca style input dialog * Fix build on macOS * Fix tooltip color in light mode * InputText: fixed incorrect padding when FrameBorder > 0. (ocornut/imgui#4794, ocornut/imgui#3781) InputTextMultiline: fixed vertical tracking with large values of FramePadding.y. (ocornut/imgui#3781, ocornut/imgui#4794) (cherry picked from commit ocornut/imgui@072caa4a90) (cherry picked from commit ocornut/imgui@bdd2a94315) * SVG: Use Orca style input dialog * Fix job progress update * Fix crash when select editing text in preview screen * Use Orca checkbox style * Fix issue that toolbar icons are kept regenerated * Emboss: Fix text & icon alignment * SVG: Fix text & icon alignment * Emboss: fix toolbar icon mouse hover state * Add a simple subtle outline effect by drawing back faces using wireframe mode * Disable selection outlines * Show outline in white if the model color is too dark * Make the outline algorithm more reliable * Enable cull face, which fix render on Linux * Fix `disable_cullface` * Post merge fix * Optimize selection rendering * Fix scale gizmo * Emboss: Fix text rotation if base object is scaled * Fix volume synchronize * Fix emboss rotation * Emboss: Fix advance toggle * Fix text position after reopened the project * Make font style preview darker * Make font style preview selector height shorter --------- Co-authored-by: tamasmeszaros <meszaros.q@gmail.com> Co-authored-by: ocornut <omarcornut@gmail.com> Co-authored-by: SoftFever <softfeverever@gmail.com>
This commit is contained in:
parent
7a8e1929ee
commit
933aa3050b
197 changed files with 27190 additions and 2454 deletions
547
src/libslic3r/NSVGUtils.cpp
Normal file
547
src/libslic3r/NSVGUtils.cpp
Normal file
|
@ -0,0 +1,547 @@
|
|||
///|/ Copyright (c) Prusa Research 2021 - 2022 Filip Sykala @Jony01
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#include "NSVGUtils.hpp"
|
||||
#include <array>
|
||||
#include <charconv> // to_chars
|
||||
|
||||
#include <boost/nowide/iostream.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Emboss.hpp" // heal for shape
|
||||
|
||||
namespace {
|
||||
using namespace Slic3r; // Polygon
|
||||
// see function nsvg__lineTo(NSVGparser* p, float x, float y)
|
||||
bool is_line(const float *p, float precision = 1e-4f);
|
||||
// convert curve in path to lines
|
||||
struct LinesPath{
|
||||
Polygons polygons;
|
||||
Polylines polylines; };
|
||||
LinesPath linearize_path(NSVGpath *first_path, const NSVGLineParams ¶m);
|
||||
HealedExPolygons fill_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams ¶m);
|
||||
HealedExPolygons stroke_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams ¶m);
|
||||
} // namespace
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
ExPolygonsWithIds create_shape_with_ids(const NSVGimage &image, const NSVGLineParams ¶m)
|
||||
{
|
||||
ExPolygonsWithIds result;
|
||||
size_t shape_id = 0;
|
||||
for (NSVGshape *shape_ptr = image.shapes; shape_ptr != NULL; shape_ptr = shape_ptr->next, ++shape_id) {
|
||||
const NSVGshape &shape = *shape_ptr;
|
||||
if (!(shape.flags & NSVG_FLAGS_VISIBLE))
|
||||
continue;
|
||||
|
||||
bool is_fill_used = shape.fill.type != NSVG_PAINT_NONE;
|
||||
bool is_stroke_used =
|
||||
shape.stroke.type != NSVG_PAINT_NONE &&
|
||||
shape.strokeWidth > 1e-5f;
|
||||
|
||||
if (!is_fill_used && !is_stroke_used)
|
||||
continue;
|
||||
|
||||
const LinesPath lines_path = linearize_path(shape.paths, param);
|
||||
|
||||
if (is_fill_used) {
|
||||
unsigned unique_id = static_cast<unsigned>(2 * shape_id);
|
||||
HealedExPolygons expoly = fill_to_expolygons(lines_path, shape, param);
|
||||
result.push_back({unique_id, expoly.expolygons, expoly.is_healed});
|
||||
}
|
||||
if (is_stroke_used) {
|
||||
unsigned unique_id = static_cast<unsigned>(2 * shape_id + 1);
|
||||
HealedExPolygons expoly = stroke_to_expolygons(lines_path, shape, param);
|
||||
result.push_back({unique_id, expoly.expolygons, expoly.is_healed});
|
||||
}
|
||||
}
|
||||
|
||||
// SVG is used as centered
|
||||
// Do not disturb user by settings of pivot position
|
||||
center(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
Polygons to_polygons(const NSVGimage &image, const NSVGLineParams ¶m)
|
||||
{
|
||||
Polygons result;
|
||||
for (NSVGshape *shape = image.shapes; shape != NULL; shape = shape->next) {
|
||||
if (!(shape->flags & NSVG_FLAGS_VISIBLE))
|
||||
continue;
|
||||
if (shape->fill.type == NSVG_PAINT_NONE)
|
||||
continue;
|
||||
const LinesPath lines_path = linearize_path(shape->paths, param);
|
||||
polygons_append(result, lines_path.polygons);
|
||||
// close polyline to create polygon
|
||||
polygons_append(result, to_polygons(lines_path.polylines));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void bounds(const NSVGimage &image, Vec2f& min, Vec2f &max)
|
||||
{
|
||||
for (const NSVGshape *shape = image.shapes; shape != NULL; shape = shape->next)
|
||||
for (const NSVGpath *path = shape->paths; path != NULL; path = path->next) {
|
||||
if (min.x() > path->bounds[0])
|
||||
min.x() = path->bounds[0];
|
||||
if (min.y() > path->bounds[1])
|
||||
min.y() = path->bounds[1];
|
||||
if (max.x() < path->bounds[2])
|
||||
max.x() = path->bounds[2];
|
||||
if (max.y() < path->bounds[3])
|
||||
max.y() = path->bounds[3];
|
||||
}
|
||||
}
|
||||
|
||||
NSVGimage_ptr nsvgParseFromFile(const std::string &filename, const char *units, float dpi)
|
||||
{
|
||||
NSVGimage *image = ::nsvgParseFromFile(filename.c_str(), units, dpi);
|
||||
return {image, &nsvgDelete};
|
||||
}
|
||||
|
||||
std::unique_ptr<std::string> read_from_disk(const std::string &path)
|
||||
{
|
||||
boost::nowide::ifstream fs{path};
|
||||
if (!fs.is_open())
|
||||
return nullptr;
|
||||
std::stringstream ss;
|
||||
ss << fs.rdbuf();
|
||||
return std::make_unique<std::string>(ss.str());
|
||||
}
|
||||
|
||||
NSVGimage_ptr nsvgParse(const std::string& file_data, const char *units, float dpi){
|
||||
// NOTE: nsvg parser consume data from input(char *)
|
||||
size_t size = file_data.size();
|
||||
// file data could be big, so it is allocated on heap
|
||||
std::unique_ptr<char[]> data_copy(new char[size+1]);
|
||||
memcpy(data_copy.get(), file_data.c_str(), size);
|
||||
data_copy[size] = '\0'; // data for nsvg must be null terminated
|
||||
NSVGimage *image = ::nsvgParse(data_copy.get(), units, dpi);
|
||||
return {image, &nsvgDelete};
|
||||
}
|
||||
|
||||
NSVGimage *init_image(EmbossShape::SvgFile &svg_file){
|
||||
// is already initialized?
|
||||
if (svg_file.image.get() != nullptr)
|
||||
return svg_file.image.get();
|
||||
|
||||
if (svg_file.file_data == nullptr) {
|
||||
// chech if path is known
|
||||
if (svg_file.path.empty())
|
||||
return nullptr;
|
||||
svg_file.file_data = read_from_disk(svg_file.path);
|
||||
if (svg_file.file_data == nullptr)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// init svg image
|
||||
svg_file.image = nsvgParse(*svg_file.file_data);
|
||||
if (svg_file.image.get() == NULL)
|
||||
return nullptr;
|
||||
|
||||
return svg_file.image.get();
|
||||
}
|
||||
|
||||
size_t get_shapes_count(const NSVGimage &image)
|
||||
{
|
||||
size_t count = 0;
|
||||
for (NSVGshape * s = image.shapes; s != NULL; s = s->next)
|
||||
++count;
|
||||
return count;
|
||||
}
|
||||
|
||||
//void save(const NSVGimage &image, std::ostream &data)
|
||||
//{
|
||||
// data << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>";
|
||||
//
|
||||
// // tl .. top left
|
||||
// Vec2f tl(std::numeric_limits<float>::max(), std::numeric_limits<float>::max());
|
||||
// // br .. bottom right
|
||||
// Vec2f br(std::numeric_limits<float>::min(), std::numeric_limits<float>::min());
|
||||
// bounds(image, tl, br);
|
||||
//
|
||||
// tl.x() = std::floor(tl.x());
|
||||
// tl.y() = std::floor(tl.y());
|
||||
//
|
||||
// br.x() = std::ceil(br.x());
|
||||
// br.y() = std::ceil(br.y());
|
||||
// Vec2f s = br - tl;
|
||||
// Point size = s.cast<Point::coord_type>();
|
||||
//
|
||||
// data << "<svg xmlns=\"http://www.w3.org/2000/svg\" "
|
||||
// << "width=\"" << size.x() << "mm\" "
|
||||
// << "height=\"" << size.y() << "mm\" "
|
||||
// << "viewBox=\"0 0 " << size.x() << " " << size.y() << "\" >\n";
|
||||
// data << "<!-- Created with PrusaSlicer (https://www.prusa3d.com/prusaslicer/) -->\n";
|
||||
//
|
||||
// std::array<char, 128> buffer;
|
||||
// auto write_point = [&tl, &buffer](std::string &d, const float *p) {
|
||||
// float x = p[0] - tl.x();
|
||||
// float y = p[1] - tl.y();
|
||||
// auto to_string = [&buffer](float f) -> std::string {
|
||||
// auto [ptr, ec] = std::to_chars(buffer.data(), buffer.data() + buffer.size(), f);
|
||||
// if (ec != std::errc{})
|
||||
// return "0";
|
||||
// return std::string(buffer.data(), ptr);
|
||||
// };
|
||||
// d += to_string(x) + "," + to_string(y) + " ";
|
||||
// };
|
||||
//
|
||||
// for (const NSVGshape *shape = image.shapes; shape != NULL; shape = shape->next) {
|
||||
// enum struct Type { move, line, curve, close }; // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
|
||||
// Type type = Type::move;
|
||||
// std::string d = "M "; // move on start point
|
||||
// for (const NSVGpath *path = shape->paths; path != NULL; path = path->next) {
|
||||
// if (path->npts <= 1)
|
||||
// continue;
|
||||
//
|
||||
// if (type == Type::close) {
|
||||
// type = Type::move;
|
||||
// // NOTE: After close must be a space
|
||||
// d += " M "; // move on start point
|
||||
// }
|
||||
// write_point(d, path->pts);
|
||||
// size_t path_size = static_cast<size_t>(path->npts - 1);
|
||||
//
|
||||
// if (path->closed) {
|
||||
// // Do not use last point in path it is duplicit
|
||||
// if (path->npts <= 4)
|
||||
// continue;
|
||||
// path_size = static_cast<size_t>(path->npts - 4);
|
||||
// }
|
||||
//
|
||||
// for (size_t i = 0; i < path_size; i += 3) {
|
||||
// const float *p = &path->pts[i * 2];
|
||||
// if (!::is_line(p)) {
|
||||
// if (type != Type::curve) {
|
||||
// type = Type::curve;
|
||||
// d += "C "; // start sequence of triplets defining curves
|
||||
// }
|
||||
// write_point(d, &p[2]);
|
||||
// write_point(d, &p[4]);
|
||||
// } else {
|
||||
//
|
||||
// if (type != Type::line) {
|
||||
// type = Type::line;
|
||||
// d += "L "; // start sequence of line points
|
||||
// }
|
||||
// }
|
||||
// write_point(d, &p[6]);
|
||||
// }
|
||||
// if (path->closed) {
|
||||
// type = Type::close;
|
||||
// d += "Z"; // start sequence of line points
|
||||
// }
|
||||
// }
|
||||
// if (type != Type::close) {
|
||||
// //type = Type::close;
|
||||
// d += "Z"; // closed path
|
||||
// }
|
||||
// data << "<path fill=\"#D2D2D2\" d=\"" << d << "\" />\n";
|
||||
// }
|
||||
// data << "</svg>\n";
|
||||
//}
|
||||
//
|
||||
//bool save(const NSVGimage &image, const std::string &svg_file_path)
|
||||
//{
|
||||
// std::ofstream file{svg_file_path};
|
||||
// if (!file.is_open())
|
||||
// return false;
|
||||
// save(image, file);
|
||||
// return true;
|
||||
//}
|
||||
} // namespace Slic3r
|
||||
|
||||
namespace {
|
||||
using namespace Slic3r; // Polygon + Vec2f
|
||||
|
||||
Point::coord_type to_coor(float val, double scale) { return static_cast<Point::coord_type>(std::round(val * scale)); }
|
||||
|
||||
bool need_flattening(float tessTol, const Vec2f &p1, const Vec2f &p2, const Vec2f &p3, const Vec2f &p4) {
|
||||
// f .. first
|
||||
// s .. second
|
||||
auto det = [](const Vec2f &f, const Vec2f &s) {
|
||||
return std::fabs(f.x() * s.y() - f.y() * s.x());
|
||||
};
|
||||
|
||||
Vec2f pd = (p4 - p1);
|
||||
Vec2f pd2 = (p2 - p4);
|
||||
float d2 = det(pd2, pd);
|
||||
Vec2f pd3 = (p3 - p4);
|
||||
float d3 = det(pd3, pd);
|
||||
float d23 = d2 + d3;
|
||||
|
||||
return (d23 * d23) >= tessTol * pd.squaredNorm();
|
||||
}
|
||||
|
||||
// see function nsvg__lineTo(NSVGparser* p, float x, float y)
|
||||
bool is_line(const float *p, float precision){
|
||||
//Vec2f p1(p[0], p[1]);
|
||||
//Vec2f p2(p[2], p[3]);
|
||||
//Vec2f p3(p[4], p[5]);
|
||||
//Vec2f p4(p[6], p[7]);
|
||||
float dx_3 = (p[6] - p[0]) / 3.f;
|
||||
float dy_3 = (p[7] - p[1]) / 3.f;
|
||||
|
||||
return
|
||||
is_approx(p[2], p[0] + dx_3, precision) &&
|
||||
is_approx(p[4], p[6] - dx_3, precision) &&
|
||||
is_approx(p[3], p[1] + dy_3, precision) &&
|
||||
is_approx(p[5], p[7] - dy_3, precision);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert cubic curve to lines
|
||||
/// Inspired by nanosvgrast.h function nsvgRasterize -> nsvg__flattenShape -> nsvg__flattenCubicBez
|
||||
/// https://github.com/memononen/nanosvg/blob/f0a3e1034dd22e2e87e5db22401e44998383124e/src/nanosvgrast.h#L335
|
||||
/// </summary>
|
||||
/// <param name="polygon">Result points</param>
|
||||
/// <param name="tessTol">Tesselation tolerance</param>
|
||||
/// <param name="p1">Curve point</param>
|
||||
/// <param name="p2">Curve point</param>
|
||||
/// <param name="p3">Curve point</param>
|
||||
/// <param name="p4">Curve point</param>
|
||||
/// <param name="level">Actual depth of recursion</param>
|
||||
void flatten_cubic_bez(Points &points, float tessTol, const Vec2f& p1, const Vec2f& p2, const Vec2f& p3, const Vec2f& p4, int level)
|
||||
{
|
||||
if (!need_flattening(tessTol, p1, p2, p3, p4)) {
|
||||
Point::coord_type x = static_cast<Point::coord_type>(std::round(p4.x()));
|
||||
Point::coord_type y = static_cast<Point::coord_type>(std::round(p4.y()));
|
||||
points.emplace_back(x, y);
|
||||
return;
|
||||
}
|
||||
|
||||
--level;
|
||||
if (level == 0)
|
||||
return;
|
||||
|
||||
Vec2f p12 = (p1 + p2) * 0.5f;
|
||||
Vec2f p23 = (p2 + p3) * 0.5f;
|
||||
Vec2f p34 = (p3 + p4) * 0.5f;
|
||||
Vec2f p123 = (p12 + p23) * 0.5f;
|
||||
Vec2f p234 = (p23 + p34) * 0.5f;
|
||||
Vec2f p1234 = (p123 + p234) * 0.5f;
|
||||
flatten_cubic_bez(points, tessTol, p1, p12, p123, p1234, level);
|
||||
flatten_cubic_bez(points, tessTol, p1234, p234, p34, p4, level);
|
||||
}
|
||||
|
||||
LinesPath linearize_path(NSVGpath *first_path, const NSVGLineParams ¶m)
|
||||
{
|
||||
LinesPath result;
|
||||
Polygons &polygons = result.polygons;
|
||||
Polylines &polylines = result.polylines;
|
||||
|
||||
// multiple use of allocated memmory for points between paths
|
||||
Points points;
|
||||
for (NSVGpath *path = first_path; path != NULL; path = path->next) {
|
||||
// Flatten path
|
||||
Point::coord_type x = to_coor(path->pts[0], param.scale);
|
||||
Point::coord_type y = to_coor(path->pts[1], param.scale);
|
||||
points.emplace_back(x, y);
|
||||
size_t path_size = (path->npts > 1) ? static_cast<size_t>(path->npts - 1) : 0;
|
||||
for (size_t i = 0; i < path_size; i += 3) {
|
||||
const float *p = &path->pts[i * 2];
|
||||
if (is_line(p)) {
|
||||
// point p4
|
||||
Point::coord_type xx = to_coor(p[6], param.scale);
|
||||
Point::coord_type yy = to_coor(p[7], param.scale);
|
||||
points.emplace_back(xx, yy);
|
||||
continue;
|
||||
}
|
||||
Vec2f p1(p[0], p[1]);
|
||||
Vec2f p2(p[2], p[3]);
|
||||
Vec2f p3(p[4], p[5]);
|
||||
Vec2f p4(p[6], p[7]);
|
||||
flatten_cubic_bez(points, param.tesselation_tolerance,
|
||||
p1 * param.scale, p2 * param.scale, p3 * param.scale, p4 * param.scale,
|
||||
param.max_level);
|
||||
}
|
||||
assert(!points.empty());
|
||||
if (points.empty())
|
||||
continue;
|
||||
|
||||
if (param.is_y_negative)
|
||||
for (Point &p : points)
|
||||
p.y() = -p.y();
|
||||
|
||||
if (path->closed) {
|
||||
polygons.emplace_back(points);
|
||||
} else {
|
||||
polylines.emplace_back(points);
|
||||
}
|
||||
// prepare for new path - recycle alocated memory
|
||||
points.clear();
|
||||
}
|
||||
remove_same_neighbor(polygons);
|
||||
remove_same_neighbor(polylines);
|
||||
return result;
|
||||
}
|
||||
|
||||
HealedExPolygons fill_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams ¶m)
|
||||
{
|
||||
Polygons fill = lines_path.polygons; // copy
|
||||
|
||||
// close polyline to create polygon
|
||||
polygons_append(fill, to_polygons(lines_path.polylines));
|
||||
if (fill.empty())
|
||||
return {};
|
||||
|
||||
// if (shape->fillRule == NSVGfillRule::NSVG_FILLRULE_NONZERO)
|
||||
bool is_non_zero = true;
|
||||
if (shape.fillRule == NSVGfillRule::NSVG_FILLRULE_EVENODD)
|
||||
is_non_zero = false;
|
||||
|
||||
return Emboss::heal_polygons(fill, is_non_zero, param.max_heal_iteration);
|
||||
}
|
||||
|
||||
struct DashesParam{
|
||||
// first dash length
|
||||
float dash_length = 1.f; // scaled
|
||||
|
||||
// is current dash .. true
|
||||
// is current space .. false
|
||||
bool is_line = true;
|
||||
|
||||
// current index to array
|
||||
unsigned char dash_index = 0;
|
||||
static constexpr size_t max_dash_array_size = 8; // limitation of nanosvg strokeDashArray
|
||||
std::array<float, max_dash_array_size> dash_array; // scaled
|
||||
unsigned char dash_count = 0; // count of values in array
|
||||
|
||||
explicit DashesParam(const NSVGshape &shape, double scale) :
|
||||
dash_count(shape.strokeDashCount)
|
||||
{
|
||||
assert(dash_count > 0);
|
||||
assert(dash_count <= max_dash_array_size); // limitation of nanosvg strokeDashArray
|
||||
for (size_t i = 0; i < dash_count; ++i)
|
||||
dash_array[i] = static_cast<float>(shape.strokeDashArray[i] * scale);
|
||||
|
||||
// Figure out dash offset.
|
||||
float all_dash_length = 0;
|
||||
for (unsigned char j = 0; j < dash_count; ++j)
|
||||
all_dash_length += dash_array[j];
|
||||
|
||||
if (dash_count%2 == 1) // (shape.strokeDashCount & 1)
|
||||
all_dash_length *= 2.0f;
|
||||
|
||||
// Find location inside pattern
|
||||
float dash_offset = fmodf(static_cast<float>(shape.strokeDashOffset * scale), all_dash_length);
|
||||
if (dash_offset < 0.0f)
|
||||
dash_offset += all_dash_length;
|
||||
|
||||
while (dash_offset > dash_array[dash_index]) {
|
||||
dash_offset -= dash_array[dash_index];
|
||||
dash_index = (dash_index + 1) % shape.strokeDashCount;
|
||||
is_line = !is_line;
|
||||
}
|
||||
|
||||
dash_length = dash_array[dash_index] - dash_offset;
|
||||
}
|
||||
};
|
||||
|
||||
Polylines to_dashes(const Polyline &polyline, const DashesParam& param)
|
||||
{
|
||||
Polylines dashes;
|
||||
Polyline dash; // cache for one dash in dashed line
|
||||
Point prev_point;
|
||||
|
||||
bool is_line = param.is_line;
|
||||
unsigned char dash_index = param.dash_index;
|
||||
float dash_length = param.dash_length; // current rest of dash distance
|
||||
for (const Point &point : polyline.points) {
|
||||
if (&point == &polyline.points.front()) {
|
||||
// is first point
|
||||
prev_point = point; // copy
|
||||
continue;
|
||||
}
|
||||
|
||||
Point diff = point - prev_point;
|
||||
float line_segment_length = diff.cast<float>().norm();
|
||||
while (dash_length < line_segment_length) {
|
||||
// Calculate intermediate point
|
||||
float d = dash_length / line_segment_length;
|
||||
Point move_point = diff * d;
|
||||
Point intermediate = prev_point + move_point;
|
||||
|
||||
// add Dash in stroke
|
||||
if (is_line) {
|
||||
if (dash.empty()) {
|
||||
dashes.emplace_back(Points{prev_point, intermediate});
|
||||
} else {
|
||||
dash.append(prev_point);
|
||||
dash.append(intermediate);
|
||||
dashes.push_back(dash);
|
||||
dash.clear();
|
||||
}
|
||||
}
|
||||
|
||||
diff -= move_point;
|
||||
line_segment_length -= dash_length;
|
||||
prev_point = intermediate;
|
||||
|
||||
// Advance dash pattern
|
||||
is_line = !is_line;
|
||||
dash_index = (dash_index + 1) % param.dash_count;
|
||||
dash_length = param.dash_array[dash_index];
|
||||
}
|
||||
|
||||
if (is_line)
|
||||
dash.append(prev_point);
|
||||
dash_length -= line_segment_length;
|
||||
prev_point = point; // copy
|
||||
}
|
||||
|
||||
// add last dash
|
||||
if (is_line){
|
||||
assert(!dash.empty());
|
||||
dash.append(prev_point); // prev_point == polyline.points.back()
|
||||
dashes.push_back(dash);
|
||||
}
|
||||
return dashes;
|
||||
}
|
||||
|
||||
HealedExPolygons stroke_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams ¶m)
|
||||
{
|
||||
// convert stroke to polygon
|
||||
ClipperLib::JoinType join_type = ClipperLib::JoinType::jtSquare;
|
||||
switch (static_cast<NSVGlineJoin>(shape.strokeLineJoin)) {
|
||||
case NSVGlineJoin::NSVG_JOIN_BEVEL: join_type = ClipperLib::JoinType::jtSquare; break;
|
||||
case NSVGlineJoin::NSVG_JOIN_MITER: join_type = ClipperLib::JoinType::jtMiter; break;
|
||||
case NSVGlineJoin::NSVG_JOIN_ROUND: join_type = ClipperLib::JoinType::jtRound; break;
|
||||
}
|
||||
|
||||
double mitter = shape.miterLimit * param.scale;
|
||||
if (join_type == ClipperLib::JoinType::jtRound) {
|
||||
// mitter is used as ArcTolerance
|
||||
// http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/Properties/ArcTolerance.htm
|
||||
mitter = std::pow(param.tesselation_tolerance, 1/3.);
|
||||
}
|
||||
float stroke_width = static_cast<float>(shape.strokeWidth * param.scale);
|
||||
|
||||
ClipperLib::EndType end_type = ClipperLib::EndType::etOpenButt;
|
||||
switch (static_cast<NSVGlineCap>(shape.strokeLineCap)) {
|
||||
case NSVGlineCap::NSVG_CAP_BUTT: end_type = ClipperLib::EndType::etOpenButt; break;
|
||||
case NSVGlineCap::NSVG_CAP_ROUND: end_type = ClipperLib::EndType::etOpenRound; break;
|
||||
case NSVGlineCap::NSVG_CAP_SQUARE: end_type = ClipperLib::EndType::etOpenSquare; break;
|
||||
}
|
||||
|
||||
Polygons result;
|
||||
if (shape.strokeDashCount > 0) {
|
||||
DashesParam params(shape, param.scale);
|
||||
Polylines dashes;
|
||||
for (const Polyline &polyline : lines_path.polylines)
|
||||
polylines_append(dashes, to_dashes(polyline, params));
|
||||
for (const Polygon &polygon : lines_path.polygons)
|
||||
polylines_append(dashes, to_dashes(to_polyline(polygon), params));
|
||||
result = offset(dashes, stroke_width / 2, join_type, mitter, end_type);
|
||||
} else {
|
||||
result = contour_to_polygons(lines_path.polygons, stroke_width, join_type, mitter);
|
||||
polygons_append(result, offset(lines_path.polylines, stroke_width / 2, join_type, mitter, end_type));
|
||||
}
|
||||
|
||||
bool is_non_zero = true;
|
||||
return Emboss::heal_polygons(result, is_non_zero, param.max_heal_iteration);
|
||||
}
|
||||
|
||||
} // namespace
|
Loading…
Add table
Add a link
Reference in a new issue