diff --git a/src/libslic3r/EmbossShape.hpp b/src/libslic3r/EmbossShape.hpp index 50cc97d148..a903e990e4 100644 --- a/src/libslic3r/EmbossShape.hpp +++ b/src/libslic3r/EmbossShape.hpp @@ -124,6 +124,12 @@ struct ExPolygonsWithId // flag whether expolygons are fully healed(without duplication) bool is_healed = true; + + // Color of the SVG path this shape came from, packed the way NanoSVG stores it + // (0xAABBGGRR). Zero means the path carried no plain color - it was a gradient, + // or the shape did not come from an SVG at all - and the shape is uncolored. + // Not serialized: it is derived from the SVG on import. + unsigned color = 0; }; using ExPolygonsWithIds = std::vector; diff --git a/src/libslic3r/NSVGUtils.cpp b/src/libslic3r/NSVGUtils.cpp index 14b144dfbd..489c4d8ea0 100644 --- a/src/libslic3r/NSVGUtils.cpp +++ b/src/libslic3r/NSVGUtils.cpp @@ -1,7 +1,12 @@ #include "NSVGUtils.hpp" +#include +#include +#include +#include #include #include // to_chars +#include #include #include #include "ClipperUtils.hpp" @@ -22,13 +27,34 @@ HealedExPolygons stroke_to_expolygons(const LinesPath &lines_path, const NSVGsha namespace Slic3r { -ExPolygonsWithIds create_shape_with_ids(const NSVGimage &image, const NSVGLineParams ¶m) +// Mark written onto elements a clip path applies to. The parser keeps an element's id and +// hands it down to the shapes of a group, so a marked group passes it to all it contains. +static const char *CLIP_MARK = "p7clip"; +// Mark on the copies of the clip shapes themselves, so they can be gathered and then +// left out of the model. +static const char *CLIP_DEF_MARK = "p7cdef"; + +// Trim a shape to the clip path marked on it. Returns false when nothing of it survives. +static bool trim_to_clip(const NSVGshape &shape, const std::vector *clips, ExPolygons &area) +{ + if (clips == nullptr || strncmp(shape.id, CLIP_MARK, strlen(CLIP_MARK)) != 0) + return true; + const int index = atoi(shape.id + strlen(CLIP_MARK)); + if (index < 0 || index >= int(clips->size()) || (*clips)[index].empty()) + return true; + area = intersection_ex(area, (*clips)[index]); + return !area.empty(); +} + +ExPolygonsWithIds create_shape_with_ids(const NSVGimage &image, const NSVGLineParams ¶m, + const std::vector *clips) { 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)) + if (!(shape.flags & NSVG_FLAGS_VISIBLE) || + strncmp(shape.id, CLIP_DEF_MARK, strlen(CLIP_DEF_MARK)) == 0) continue; bool is_fill_used = shape.fill.type != NSVG_PAINT_NONE; @@ -41,24 +67,340 @@ ExPolygonsWithIds create_shape_with_ids(const NSVGimage &image, const NSVGLinePa const LinesPath lines_path = linearize_path(shape.paths, param); + // Keep the path's color with its shape. Only a plain color is meaningful + // here; a gradient has no single value, so it is left uncolored. + auto plain_color = [](const NSVGpaint &paint) -> unsigned { + return paint.type == NSVG_PAINT_COLOR ? paint.color : 0u; + }; + if (is_fill_used) { unsigned unique_id = static_cast(2 * shape_id); HealedExPolygons expoly = fill_to_expolygons(lines_path, shape, param); - result.push_back({unique_id, expoly.expolygons, expoly.is_healed}); + if (trim_to_clip(shape, clips, expoly.expolygons)) + result.push_back({unique_id, expoly.expolygons, expoly.is_healed, plain_color(shape.fill)}); } if (is_stroke_used) { unsigned unique_id = static_cast(2 * shape_id + 1); HealedExPolygons expoly = stroke_to_expolygons(lines_path, shape, param); - result.push_back({unique_id, expoly.expolygons, expoly.is_healed}); + if (trim_to_clip(shape, clips, expoly.expolygons)) + result.push_back({unique_id, expoly.expolygons, expoly.is_healed, plain_color(shape.stroke)}); } } + // Report the colors the SVG carried, so an import can be checked against the + // artwork without stepping through it. NanoSVG packs the color as 0xAABBGGRR. + { + std::vector distinct; + for (const ExPolygonsWithId &shape : result) + if (shape.color != 0 && std::find(distinct.begin(), distinct.end(), shape.color) == distinct.end()) + distinct.push_back(shape.color); + std::string colors; + for (unsigned c : distinct) { + char buf[16]; + snprintf(buf, sizeof(buf), " #%02X%02X%02X", c & 0xFF, (c >> 8) & 0xFF, (c >> 16) & 0xFF); + colors += buf; + } + BOOST_LOG_TRIVIAL(info) << "SVG import: " << result.size() << " shapes, " + << distinct.size() << " distinct colors:" << colors; + } + // SVG is used as centered // Do not disturb user by settings of pivot position center(result); return result; } +namespace { + +// Whitespace as CSS and XML count it. +static const char *WS = " \t\r\n"; + +// Value of one declaration, matched as a whole word so that asking for "fill" is not +// answered with "fill-rule". +static std::string css_value(const std::string &declarations, const std::string &property) +{ + for (size_t at = declarations.find(property); at != std::string::npos; + at = declarations.find(property, at + property.size())) { + if (at > 0 && (isalnum((unsigned char) declarations[at - 1]) || declarations[at - 1] == '-')) + continue; + const size_t colon = declarations.find_first_not_of(WS, at + property.size()); + if (colon == std::string::npos || declarations[colon] != ':') + continue; + const size_t from = declarations.find_first_not_of(WS, colon + 1); + if (from == std::string::npos) + break; + const size_t to = declarations.find_first_of(";}\r\n", from); + std::string value = declarations.substr(from, (to == std::string::npos ? declarations.size() : to) - from); + while (!value.empty() && isspace((unsigned char) value.back())) + value.pop_back(); + return value; + } + return {}; +} + +// Declarations of every class rule in the document's