diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9328a22 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,67 @@ +{ + "files.associations": { + "cctype": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "csignal": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "array": "cpp", + "atomic": "cpp", + "strstream": "cpp", + "*.tcc": "cpp", + "bitset": "cpp", + "cfenv": "cpp", + "chrono": "cpp", + "cinttypes": "cpp", + "codecvt": "cpp", + "complex": "cpp", + "condition_variable": "cpp", + "cstdint": "cpp", + "deque": "cpp", + "forward_list": "cpp", + "list": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "any": "cpp", + "filesystem": "cpp", + "functional": "cpp", + "optional": "cpp", + "ratio": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "fstream": "cpp", + "future": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "memory": "cpp", + "mutex": "cpp", + "new": "cpp", + "ostream": "cpp", + "numeric": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "thread": "cpp", + "regex": "cpp", + "utility": "cpp", + "typeindex": "cpp", + "typeinfo": "cpp", + "valarray": "cpp" + } +} diff --git a/CMakeLists.txt b/CMakeLists.txt index efe9c3a..3e0579d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,14 @@ cmake_minimum_required(VERSION 3.4...3.18) project(naive_svg) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_CXX_STANDARD 14) + include_directories(headers/include) +set(PYBIND11_CPP_STANDARD -std=c++14) add_subdirectory(pybind11) file(GLOB SRCS src/*.cpp) pybind11_add_module(_naive_svg ${SRCS}) diff --git a/README.md b/README.md index 8fc8c15..1dd94e3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # naive-svg -Online document: **[readthedocs](http://naive-svg.readthedocs.io/)** +Upgrated version of [svg.hpp](https://github.com/cubao/naive-svg/blob/master/svg.hpp). + +Online document: **[readthedocs](http://pybind11-naive-svg.readthedocs.io/)** diff --git a/mkdocs.yml b/mkdocs.yml index f8fd99b..fce66a8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,5 +1,5 @@ site_name: naive-svg -site_url: https://naive-svg.readthedocs.io +site_url: https://pybind11-naive-svg.readthedocs.io site_description: naive svg writer site_author: district10 diff --git a/setup.py b/setup.py index 71f8fd2..49fab40 100644 --- a/setup.py +++ b/setup.py @@ -127,10 +127,10 @@ def build_extension(self, ext: CMakeExtension) -> None: # logic and declaration, and simpler if you include description/version in a file. setup( name="naive_svg", - version="0.0.1", + version="0.0.2", author="tzx", author_email="dvorak4tzx@gmail.com", - url="https://naive-svg.readthedocs.io", + url="https://pybind11-naive-svg.readthedocs.io", description="naive svg writer", long_description=open("README.md", encoding="utf-8").read(), long_description_content_type="text/markdown", diff --git a/src/naive_svg.hpp b/src/naive_svg.hpp index ba8e900..bf3b56a 100644 --- a/src/naive_svg.hpp +++ b/src/naive_svg.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -32,8 +33,9 @@ namespace cubao #ifndef SETUP_FLUENT_API_FOR_SVG_ELEMENT #define SETUP_FLUENT_API_FOR_SVG_ELEMENT(KlassType) \ SETUP_FLUENT_API(KlassType, Color, stroke) \ + SETUP_FLUENT_API(KlassType, double, stroke_width) \ SETUP_FLUENT_API(KlassType, Color, fill) \ - SETUP_FLUENT_API(KlassType, double, stroke_width) + SETUP_FLUENT_API(KlassType, std::string, attrs) #endif struct SVG @@ -94,6 +96,7 @@ struct SVG write(ss); return ss.str(); } + Color clone() const { return *this; } private: int r_{-1}, g_{-1}, b_{-1}; @@ -110,23 +113,12 @@ struct SVG struct Element { - void fit_into(double xmin, double xmax, double ymin, double ymax, // - double width, double height) - { - // fit bbox[xmin:xmax, ymin:ymax] into viewBox[0:width, 0:height] - double xspan = xmax - xmin; - double yspan = ymax - ymin; - for (auto &pt : points_) { - pt[0] = (pt[0] - xmin) / xspan * width; - pt[1] = (pt[1] - ymin) / yspan * height; - } - } - protected: std::vector points_; Color stroke_{COLOR::BLACK}; double stroke_width_{1.0}; Color fill_{COLOR::NONE}; + std::string attrs_; }; struct Polyline : Element @@ -149,6 +141,9 @@ struct SVG out << pt[0] << "," << pt[1] << " "; } out << "'"; + if (!attrs_.empty()) { + out << " " << attrs_; + } out << " />"; } std::string to_string() const @@ -157,6 +152,8 @@ struct SVG write(ss); return ss.str(); } + + Polyline clone() const { return *this; } }; struct Polygon : Element @@ -178,6 +175,9 @@ struct SVG out << pt[0] << "," << pt[1] << " "; } out << "'"; + if (!attrs_.empty()) { + out << " " << attrs_; + } out << " />"; } std::string to_string() const @@ -186,6 +186,8 @@ struct SVG write(ss); return ss.str(); } + + Polygon clone() const { return *this; } }; struct Circle : Element @@ -211,11 +213,11 @@ struct SVG const double &x() const { return points_[0][0]; } Circle &y(double y) { - points_[0][0] = y; + points_[0][1] = y; return *this; } - double &y() { return points_[0][0]; } - const double &y() const { return points_[0][0]; } + double &y() { return points_[0][1]; } + const double &y() const { return points_[0][1]; } SETUP_FLUENT_API(Circle, double, r) SETUP_FLUENT_API_FOR_SVG_ELEMENT(Circle) @@ -228,8 +230,11 @@ struct SVG << " cx='" << x() << "' cy='" << y() << "'" // << " style='stroke:" << stroke_ // << ";stroke-width:" << stroke_width_ // - << ";fill:" << fill_ << "'" // - << " />"; + << ";fill:" << fill_ << "'"; + if (!attrs_.empty()) { + out << " " << attrs_; + } + out << " />"; } std::string to_string() const { @@ -238,15 +243,18 @@ struct SVG return ss.str(); } + Circle clone() const { return *this; } + protected: double r_{1.0}; }; struct Text : Element { - Text(const PointType &p, const std::string &text, int fontsize = 10.0) + Text(const PointType &position, const std::string &text, + int fontsize = 10.0) { - points_ = {p}; + points_ = {position}; text_ = text; fontsize_ = fontsize; fill_ = COLOR::BLACK; @@ -268,27 +276,17 @@ struct SVG const double &x() const { return points_[0][0]; } Text &y(double y) { - points_[0][0] = y; + points_[0][1] = y; return *this; } - double &y() { return points_[0][0]; } - const double &y() const { return points_[0][0]; } + double &y() { return points_[0][1]; } + const double &y() const { return points_[0][1]; } SETUP_FLUENT_API(Text, std::string, text) SETUP_FLUENT_API(Text, std::vector, lines) SETUP_FLUENT_API(Text, double, fontsize) SETUP_FLUENT_API_FOR_SVG_ELEMENT(Text) - // // text-anchor="start" - // - friend std::ostream &operator<<(std::ostream &out, const SVG::Text &e); void write(std::ostream &out) const @@ -297,8 +295,11 @@ struct SVG << " x='" << x() << "' y='" << y() << "'" // << " fill='" << fill_ << "'" // << " font-size='" << fontsize_ << "'" // - << " font-family='monospace'" // - << ">" << html_escape(text_); + << " font-family='monospace'"; + if (!attrs_.empty()) { + out << " " << attrs_; + } + out << ">" << html_escape(text_); if (!lines_.empty()) { double fontsize = fontsize_ / 5.0; for (auto &line : lines_) { @@ -319,37 +320,31 @@ struct SVG return ss.str(); } + Text clone() const { return *this; } + static std::string html_escape(const std::string &text) { - const static std::vector escapes = { - "&", """, "'", "<", ">"}; - std::map replace; - for (size_t pos = 0; pos != text.size(); ++pos) { - const char c = text[pos]; - if (c == '&') { - replace[pos] = 0; - } else if (c == '\"') { - replace[pos] = 1; - } else if (c == '\'') { - replace[pos] = 2; - } else if (c == '<') { - replace[pos] = 3; - } else if (c == '>') { - replace[pos] = 4; - } - } - if (replace.empty()) { - return text; - } std::string buffer; - buffer.reserve(text.size() + 6 * replace.size()); - // TODO - for (size_t pos = 0; pos != text.size(); ++pos) { - auto itr = replace.find(text[pos]); - if (itr == replace.end()) { - buffer.append(&text[pos], 1); - } else { - buffer.append(escapes[itr->second]); + for (char c : text) { + switch (c) { + case '&': + buffer.append("&"); + break; + case '\"': + buffer.append("""); + break; + case '\'': + buffer.append("'"); + break; + case '<': + buffer.append("<"); + break; + case '>': + buffer.append(">"); + break; + default: + buffer.push_back(c); + break; } } return buffer; @@ -378,22 +373,71 @@ struct SVG } } - SVG clone() const {} + // disable shallow copy + private: + SVG(const SVG &) = default; + SVG &operator=(const SVG &) = default; + SVG(SVG &&) = delete; + SVG &operator=(SVG &&) = delete; + // implement deep copy + public: + std::unique_ptr clone() const + { + // auto ptr = std::make_unique(*this); + std::unique_ptr ptr(new SVG(*this)); + for (auto &pair : elements_) { + const auto type = pair.first; + if (type == ELEMENT::POLYGON) { + ptr->add(*(Polygon *)pair.second); + } else if (type == ELEMENT::POLYLINE) { + ptr->add(*(Polyline *)pair.second); + } else if (type == ELEMENT::CIRCLE) { + ptr->add(*(Circle *)pair.second); + } else if (type == ELEMENT::TEXT) { + ptr->add(*(Text *)pair.second); + } + } + return ptr; + } SETUP_FLUENT_API(SVG, double, width) SETUP_FLUENT_API(SVG, double, height) + SETUP_FLUENT_API(SVG, std::vector, view_box) SETUP_FLUENT_API(SVG, double, grid_step) SETUP_FLUENT_API(SVG, std::vector, grid_x) SETUP_FLUENT_API(SVG, std::vector, grid_y) SETUP_FLUENT_API(SVG, Color, grid_color) SETUP_FLUENT_API(SVG, Color, background) + SETUP_FLUENT_API(SVG, std::string, attrs) - Polygon &add_polygon(const std::vector &points) + Polyline &add(const Polyline &polyline) { - auto ptr = new Polygon(points); + auto ptr = new Polyline({}); + *ptr = polyline; + elements_.push_back({ELEMENT::POLYLINE, (void *)ptr}); + return *ptr; + } + Polygon &add(const Polygon &polygon) + { + auto ptr = new Polygon({}); + *ptr = polygon; elements_.push_back({ELEMENT::POLYGON, (void *)ptr}); return *ptr; } + Circle &add(const Circle &circle) + { + auto ptr = new Circle(circle.center()); + *ptr = circle; + elements_.push_back({ELEMENT::CIRCLE, (void *)ptr}); + return *ptr; + } + Text &add(const Text &text) + { + auto ptr = new Text(text.position(), ""); + *ptr = text; + elements_.push_back({ELEMENT::TEXT, (void *)ptr}); + return *ptr; + } Polyline &add_polyline(const std::vector &points) { @@ -402,6 +446,13 @@ struct SVG return *ptr; } + Polygon &add_polygon(const std::vector &points) + { + auto ptr = new Polygon(points); + elements_.push_back({ELEMENT::POLYGON, (void *)ptr}); + return *ptr; + } + Circle &add_circle(const PointType ¢er, double r = 1.0) { auto ptr = new Circle(center, r); @@ -417,11 +468,126 @@ struct SVG return *ptr; } + size_t num_elements() const { return elements_.size(); } + + bool empty() const { return elements_.empty(); } + + void pop() + { + if (elements_.empty()) { + return; + } + auto del = elements_.back(); + elements_.pop_back(); + if (del.first == ELEMENT::POLYLINE) { + delete (Polyline *)del.second; + } else if (del.first == ELEMENT::POLYGON) { + delete (Polygon *)del.second; + } else if (del.first == ELEMENT::CIRCLE) { + delete (Circle *)del.second; + } else if (del.first == ELEMENT::TEXT) { + delete (Text *)del.second; + } + } + + bool is_polyline(int index) const + { + index = __index(index); + if (index < 0) { + return false; + } + return elements_.at(index).first == ELEMENT::POLYLINE; + } + bool is_polygon(int index) const + { + index = __index(index); + if (index < 0) { + return false; + } + return elements_.at(index).first == ELEMENT::POLYGON; + } + bool is_circle(int index) const + { + index = __index(index); + if (index < 0) { + return false; + } + return elements_.at(index).first == ELEMENT::CIRCLE; + } + bool is_text(int index) const + { + index = __index(index); + if (index < 0) { + return false; + } + return elements_.at(index).first == ELEMENT::TEXT; + } + + Polyline *as_polyline(int index) + { + if (!is_polyline(index)) { + return nullptr; + } + return (Polyline *)elements_.at(index % elements_.size()).second; + } + Polygon *as_polygon(int index) + { + if (!is_polygon(index)) { + return nullptr; + } + return (Polygon *)elements_.at(index % elements_.size()).second; + } + Circle *as_circle(int index) + { + if (!is_circle(index)) { + return nullptr; + } + return (Circle *)elements_.at(index % elements_.size()).second; + } + Text *as_text(int index) + { + if (!is_text(index)) { + return nullptr; + } + return (Text *)elements_.at(index % elements_.size()).second; + } + // const version + const Polyline *as_polyline(int index) const + { + return const_cast( + const_cast(this)->as_polyline(index)); + } + const Polygon *as_polygon(int index) const + { + return const_cast( + const_cast(this)->as_polygon(index)); + } + const Circle *as_circle(int index) const + { + return const_cast( + const_cast(this)->as_circle(index)); + } + const Text *as_text(int index) const + { + return const_cast( + const_cast(this)->as_text(index)); + } + void write(std::ostream &out) const { - out << ""; + out << ""; if (!background_.invalid()) { out << "\n\t