From 85e3106fb663a7c3a34b90b91f0863e60ac3a3d5 Mon Sep 17 00:00:00 2001 From: Nobuyuki Date: Sun, 21 Apr 2024 14:16:13 +0900 Subject: [PATCH] added parse_svg.h --- asset/r.svg | 4 + src/parse_svg.h | 341 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 345 insertions(+) create mode 100644 asset/r.svg create mode 100644 src/parse_svg.h diff --git a/asset/r.svg b/asset/r.svg new file mode 100644 index 0000000..09353d1 --- /dev/null +++ b/asset/r.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/parse_svg.h b/src/parse_svg.h new file mode 100644 index 0000000..c3c9170 --- /dev/null +++ b/src/parse_svg.h @@ -0,0 +1,341 @@ +// +// Created by Nobuyuki Umetani on 2024/03/06. +// + +#ifndef PARSE_SVG_H_ +#define PARSE_SVG_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +// +#include "Eigen/Dense" + +namespace acg { + +std::vector split(const std::string &str, const char separator) { + std::vector out; + std::stringstream ss(str); + std::string buf; + while (std::getline(ss, buf, separator)) { + out.push_back(buf); + } + return out; +} + +std::optional> get_file_content( + const std::string &fpath) { + size_t size; + FILE *fp = fopen(fpath.c_str(), "rb"); + if (!fp) { + return std::nullopt; + } + fseek(fp, 0, SEEK_END); + size = ftell(fp); + fseek(fp, 0, SEEK_SET); + std::vector aC(size + 1); + if (fread(aC.data(), 1, size, fp) != size) { + fclose(fp); + return std::nullopt; + } + aC[size] = '\0'; // Must be null terminated. + fclose(fp); + return std::make_optional(aC); +} + +std::string remove_beginning( + const std::string &str, + const std::string &del) { + const size_t n = str.size(); + const size_t ndel = del.size(); + // + unsigned int istat = 0; + for (unsigned int i = 0; i < n; ++i) { + bool is_del = false; + for (unsigned int idel = 0; idel < ndel; ++idel) { + if (str[i] != del[idel]) { continue; } + is_del = true; + break; + } + if (is_del) continue; + istat = i; + break; + } + return {str.begin() + istat, str.end()}; +} + +std::vector separate_tags( + const std::vector &input) { + std::vector aStr; + std::vector buffer = input; + char *s = buffer.data(); + char *mark = s; + int state = 1; + while (*s) { + if (*s == '<' && state == 1) { + // Start of a tag + *s++ = '\0'; + aStr.emplace_back(mark); + mark = s; + state = 0; + } else if (*s == '>' && state == 0) { // Start of a content or new tag. + *s++ = '\0'; + aStr.emplace_back(mark); + mark = s; + state = 1; + } else { + s++; + } + } + for (auto &is: aStr) { + is = remove_beginning(is, " "); + } + return aStr; +} + +std::vector split_quote( + const std::string &str, + char delimiter, + char quote) { + std::vector aToken; + unsigned int is = 0; + bool is_in = false; + for (unsigned int ie = 0; ie < str.size(); ++ie) { + if (ie == str.size() - 1) { + aToken.emplace_back(str.data() + is, str.data() + ie + 1); + } + if (str[ie] == quote) { is_in = !is_in; } + if (str[ie] == delimiter && !is_in) { + if (str[is] != delimiter) { // skip the sequence of the delimiter + aToken.emplace_back(str.data() + is, str.data() + ie); + } + is = ie + 1; + } + } + return aToken; +} + +std::string remove_quote( + const std::string &str, + char quat) { + const size_t n = str.size(); + { + int nq = 0; + for (unsigned int i = 0; i < n; ++i) { + if (str[i] == quat) { ++nq; } + } + if (nq < 2) { return str; } + } + unsigned int istat = 0; + for (; istat < n; ++istat) { + if (str[istat] == quat) { break; } + } + int iend = (int) n - 1; + for (; iend >= 0; --iend) { + if (str[iend] == quat) { break; } + } + return {str.begin() + istat + 1, str.begin() + iend}; +} + +std::map parse_tag_contents( + const std::string &input) { + const std::vector aS = split_quote(input, ' ', '\"'); + std::map mapAttr; + for (const auto &is: aS) { + const std::vector aS1 = split(is, '='); + if (aS1.size() != 2) continue; + const std::string s1 = remove_quote(aS1[1], '\"'); + mapAttr.insert(std::make_pair(aS1[0], s1)); + } + return mapAttr; +} + +std::tuple svg_get_image_size_and_shape(const std::filesystem::path &file_path) { + const std::optional> aC = get_file_content(file_path.string()); + if (!aC) { + return std::make_tuple(0, 0, ""); + } + unsigned int width = 0, height = 0; + std::string shape; + const std::vector aStrTagContent = separate_tags(aC.value()); + for (const auto &str: aStrTagContent) { + if (str.compare(0, 4, "svg ") == 0) { + const std::map mapAttr = parse_tag_contents(str); + height = std::stoi(mapAttr.at("height")); + width = std::stoi(mapAttr.at("width")); + } + if (str.compare(0, 5, "path ") == 0) { + const std::map mapAttr = parse_tag_contents(str); + shape = mapAttr.at("d"); + } + } + return std::make_tuple(width, height, shape); +} + +std::vector svg_outline_path_from_shape( + const std::string &s0) { + unsigned int imark = 0; + std::vector aS; + for (unsigned int i = 0; i < s0.size(); ++i) { + if (std::isdigit(s0[i])) continue; + if (s0[i] == ',') { + std::string s1(s0.begin() + imark, s0.begin() + i); + aS.push_back(s1); + imark = i + 1; // mark should be the beginning position of the string so move next + } + if (s0[i] == ' ') { // sometimes the space act as delimiter in the SVG (inkscape version) + if (i > imark) { + std::string s1(s0.begin() + imark, s0.begin() + i); + aS.push_back(s1); + } + imark = i + 1; // mark should be the beginning position of the string so move next + } + if (s0[i] == '-') { + if (i > imark) { + std::string s1(s0.begin() + imark, s0.begin() + i); + aS.push_back(s1); + } + imark = i; + } + if (std::isalpha(s0[i])) { + if (i > imark) { + std::string s1(s0.begin() + imark, s0.begin() + i); + aS.push_back(s1); + } + const char s2[2] = {s0[i], '\0'}; + aS.emplace_back(s2); + imark = i + 1; + } + } + return aS; +} + +class Edge { + public: + Eigen::Vector2f ps; + Eigen::Vector2f pe; + Eigen::Vector2f pc; + bool is_bezier; + public: + Edge( + Eigen::Vector2f _ps, + Eigen::Vector2f _pe) : ps(std::move(_ps)), pe(std::move(_pe)), is_bezier(false) {} + Edge( + Eigen::Vector2f _ps, + Eigen::Vector2f _pc, + Eigen::Vector2f _pe) : ps(std::move(_ps)), pc(std::move(_pc)), pe(std::move(_pe)), is_bezier(true) {} +}; + +std::vector> svg_loops_from_outline_path( + const std::vector &strs) { + std::vector< std::vector> loops; + std::vector edges_buffer; + assert(strs[0] == "M" || strs[0] == "m"); + assert(strs[strs.size() - 1] == "Z" || strs[strs.size() - 1] == "z"); + Eigen::Vector2f pos_cur(0., 0.); + for (int is = 0;;) { + // std::cout << is << " " << strs[is] << std::endl; + if (strs[is] == "M") { // start absolute + is++; + pos_cur = Eigen::Vector2f(std::stof(strs[is + 0]), std::stof(strs[is + 1])); + for (; is += 2;) { + if (isalpha(strs[is][0])) { break; } + auto p1 = Eigen::Vector2f(std::stof(strs[is + 0]), std::stof(strs[is + 1])); + edges_buffer.emplace_back(pos_cur, p1); + pos_cur = p1; + } + } else if (strs[is] == "m") { // start relative + is++; + pos_cur = pos_cur + Eigen::Vector2f(std::stof(strs[is + 0]), std::stof(strs[is + 1])); + for (; is += 2;) { + if (isalpha(strs[is][0])) { break; } + auto p1 = pos_cur + Eigen::Vector2f(std::stof(strs[is + 0]), std::stof(strs[is + 1])); + edges_buffer.emplace_back(pos_cur, p1); + pos_cur = p1; + } + } else if (strs[is] == "l") { // line relative + ++is; + for (;;) { + auto p1 = pos_cur + Eigen::Vector2f(std::stof(strs[is + 0]), std::stof(strs[is + 1])); + edges_buffer.emplace_back(pos_cur, p1); + pos_cur = p1; + is += 2; + if (isalpha(strs[is][0])) { break; } + } + } else if (strs[is] == "L") { // line absolute + ++is; + for (;;) { + auto p1 = Eigen::Vector2f(std::stof(strs[is + 0]), std::stof(strs[is + 1])); + edges_buffer.emplace_back(pos_cur, p1); + pos_cur = p1; + is += 2; + if (isalpha(strs[is][0])) { break; } + } + } else if (strs[is] == "v") { // vertical relative + auto p1 = pos_cur + Eigen::Vector2f(0., std::stof(strs[is + 1])); + edges_buffer.emplace_back(pos_cur, p1); + pos_cur = p1; + is += 2; + } else if (strs[is] == "V") { // vertical absolute + auto p1 = Eigen::Vector2f(pos_cur[0], std::stof(strs[is + 1])); + edges_buffer.emplace_back(pos_cur, p1); + pos_cur = p1; + is += 2; + } else if (strs[is] == "H") { // horizontal absolute + auto p1 = Eigen::Vector2f(std::stof(strs[is + 1]), pos_cur[1]); + edges_buffer.emplace_back(pos_cur, p1); + pos_cur = p1; + is += 2; + } else if (strs[is] == "h") { // horizontal relative + auto p1 = pos_cur + Eigen::Vector2f(std::stof(strs[is + 1]), 0.); + edges_buffer.emplace_back(pos_cur, p1); + pos_cur = p1; + is += 2; + } else if (strs[is] == "q") { // relative + is++; + for (;;) { // loop for poly-Bezeir curve + auto pm0 = pos_cur + Eigen::Vector2f(std::stof(strs[is + 0]), std::stof(strs[is + 1])); + auto p1 = pos_cur + Eigen::Vector2f(std::stof(strs[is + 2]), std::stof(strs[is + 3])); + edges_buffer.emplace_back(pos_cur, pm0, p1); + pos_cur = p1; + is += 4; + if (isalpha(strs[is][0])) { break; } + } + } else if (strs[is] == "Q") { // absolute + is++; + for (;;) { // loop for poly-Bezeir curve + auto pm0 = Eigen::Vector2f(std::stof(strs[is + 0]), std::stof(strs[is + 1])); + auto p1 = Eigen::Vector2f(std::stof(strs[is + 2]), std::stof(strs[is + 3])); + edges_buffer.emplace_back(pos_cur, pm0, p1); + pos_cur = p1; + is += 4; + if (isalpha(strs[is][0])) { break; } + } + } else if (strs[is] == "z" || strs[is] == "Z") { + const Eigen::Vector2f pe = edges_buffer[0].ps; + const Eigen::Vector2f ps = edges_buffer[edges_buffer.size() - 1].pe; + double dist0 = (ps - pe).norm(); + if (dist0 > 1.0e-9) { + edges_buffer.emplace_back(ps, pe); + } + loops.push_back(edges_buffer); + edges_buffer.clear(); + // std::cout << is << " " << strs.size() << std::endl; + if (is == strs.size()-1 ) { break; } + ++is; + } else { + std::cout << "error!--> " << strs[is] << std::endl; + break; + } + } + return loops; +} + +} // acg + +#endif //PARSE_SVG_H_