-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
11ee8f8
commit 85e3106
Showing
2 changed files
with
345 additions
and
0 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,341 @@ | ||
// | ||
// Created by Nobuyuki Umetani on 2024/03/06. | ||
// | ||
|
||
#ifndef PARSE_SVG_H_ | ||
#define PARSE_SVG_H_ | ||
|
||
#include <utility> | ||
#include <vector> | ||
#include <string> | ||
#include <sstream> | ||
#include <optional> | ||
#include <map> | ||
#include <filesystem> | ||
#include <iostream> | ||
// | ||
#include "Eigen/Dense" | ||
|
||
namespace acg { | ||
|
||
std::vector<std::string> split(const std::string &str, const char separator) { | ||
std::vector<std::string> out; | ||
std::stringstream ss(str); | ||
std::string buf; | ||
while (std::getline(ss, buf, separator)) { | ||
out.push_back(buf); | ||
} | ||
return out; | ||
} | ||
|
||
std::optional<std::vector<char>> 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<char> 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<std::string> separate_tags( | ||
const std::vector<char> &input) { | ||
std::vector<std::string> aStr; | ||
std::vector<char> 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<std::string> split_quote( | ||
const std::string &str, | ||
char delimiter, | ||
char quote) { | ||
std::vector<std::string> 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<std::string, std::string> parse_tag_contents( | ||
const std::string &input) { | ||
const std::vector<std::string> aS = split_quote(input, ' ', '\"'); | ||
std::map<std::string, std::string> mapAttr; | ||
for (const auto &is: aS) { | ||
const std::vector<std::string> 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<int, int, std::string> svg_get_image_size_and_shape(const std::filesystem::path &file_path) { | ||
const std::optional<std::vector<char>> 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<std::string> aStrTagContent = separate_tags(aC.value()); | ||
for (const auto &str: aStrTagContent) { | ||
if (str.compare(0, 4, "svg ") == 0) { | ||
const std::map<std::string, std::string> 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<std::string, std::string> mapAttr = parse_tag_contents(str); | ||
shape = mapAttr.at("d"); | ||
} | ||
} | ||
return std::make_tuple(width, height, shape); | ||
} | ||
|
||
std::vector<std::string> svg_outline_path_from_shape( | ||
const std::string &s0) { | ||
unsigned int imark = 0; | ||
std::vector<std::string> 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<std::vector<Edge>> svg_loops_from_outline_path( | ||
const std::vector<std::string> &strs) { | ||
std::vector< std::vector<Edge>> loops; | ||
std::vector<Edge> 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_ |