kopia lustrzana https://github.com/jameshball/osci-render
236 wiersze
6.7 KiB
C++
236 wiersze
6.7 KiB
C++
#include "SvgParser.h"
|
|
#include <regex>
|
|
#include "MoveTo.h"
|
|
#include "LineTo.h"
|
|
#include "CurveTo.h"
|
|
#include "EllipticalArcTo.h"
|
|
#include "ClosePath.h"
|
|
|
|
|
|
SvgParser::SvgParser(juce::String svgFile) {
|
|
pugi::xml_document doc;
|
|
pugi::xml_parse_result result = doc.load_string(svgFile.toStdString().c_str());
|
|
|
|
if (result) {
|
|
pugi::xml_node svg = doc.select_node("//svg").node();
|
|
pugi::xpath_node_set paths = svg.select_nodes("//path");
|
|
|
|
shapes = std::vector<std::unique_ptr<Shape>>();
|
|
|
|
for (pugi::xpath_node xPath : paths) {
|
|
pugi::xml_node path = xPath.node();
|
|
for (pugi::xml_attribute attr : path.attributes()) {
|
|
if (std::strcmp(attr.name(), "d") == 0) {
|
|
auto path = parsePath(juce::String(attr.value()));
|
|
shapes.insert(shapes.end(), std::make_move_iterator(path.begin()), std::make_move_iterator(path.end()));
|
|
}
|
|
}
|
|
}
|
|
|
|
std::pair<double, double> dimensions = getDimensions(svg);
|
|
|
|
if (dimensions.first != -1.0 && dimensions.second != -1.0) {
|
|
double width = dimensions.first;
|
|
double height = dimensions.second;
|
|
|
|
Shape::normalize(shapes, width, height);
|
|
} else {
|
|
Shape::normalize(shapes);
|
|
}
|
|
}
|
|
}
|
|
|
|
SvgParser::~SvgParser() {
|
|
}
|
|
|
|
std::vector<std::unique_ptr<Shape>> SvgParser::draw() {
|
|
// clone with deep copy
|
|
std::vector<std::unique_ptr<Shape>> tempShapes = std::vector<std::unique_ptr<Shape>>();
|
|
|
|
for (auto& shape : shapes) {
|
|
tempShapes.push_back(shape->clone());
|
|
}
|
|
return tempShapes;
|
|
}
|
|
|
|
std::vector<std::string> SvgParser::preProcessPath(std::string path) {
|
|
std::regex re1(",");
|
|
std::regex re2("-(?!e)");
|
|
std::regex re3("\\s+");
|
|
std::regex re4("(^\\s|\\s$)");
|
|
|
|
path = std::regex_replace(path, re1, " ");
|
|
// reverse path and use a negative lookahead
|
|
std::reverse(path.begin(), path.end());
|
|
path = std::regex_replace(path, re2, "- ");
|
|
std::reverse(path.begin(), path.end());
|
|
path = std::regex_replace(path, re3, " ");
|
|
path = std::regex_replace(path, re4, "");
|
|
|
|
std::regex commands("(?=[mlhvcsqtazMLHVCSQTAZ])");
|
|
std::vector<std::string> commandsVector;
|
|
|
|
std::sregex_token_iterator iter(path.begin(), path.end(), commands, -1);
|
|
|
|
for (; iter != std::sregex_token_iterator(); ++iter) {
|
|
commandsVector.push_back(iter->str());
|
|
}
|
|
|
|
return commandsVector;
|
|
}
|
|
|
|
juce::String SvgParser::simplifyLength(juce::String length) {
|
|
return length.replace("em|ex|px|in|cm|mm|pt|pc", "");
|
|
}
|
|
|
|
std::pair<double, double> SvgParser::getDimensions(pugi::xml_node& svg) {
|
|
double width = -1.0;
|
|
double height = -1.0;
|
|
|
|
if (!svg.attribute("viewBox").empty()) {
|
|
juce::String viewBox = juce::String(svg.attribute("viewBox").as_string());
|
|
juce::StringArray viewBoxValues = juce::StringArray::fromTokens(viewBox, " ", "");
|
|
|
|
if (viewBoxValues.size() == 4) {
|
|
width = viewBoxValues[2].getDoubleValue();
|
|
height = viewBoxValues[3].getDoubleValue();
|
|
}
|
|
}
|
|
|
|
std::regex re("em|ex|px|in|cm|mm|pt|pc");
|
|
|
|
if (!svg.attribute("width").empty()) {
|
|
std::string widthString = svg.attribute("width").as_string();
|
|
widthString = std::regex_replace(widthString, re, "");
|
|
width = juce::String(widthString).getDoubleValue();
|
|
}
|
|
|
|
if (!svg.attribute("height").empty()) {
|
|
std::string heightString = svg.attribute("height").as_string();
|
|
heightString = std::regex_replace(heightString, re, "");
|
|
height = juce::String(heightString).getDoubleValue();
|
|
}
|
|
|
|
return std::make_pair<>(width, height);
|
|
}
|
|
|
|
std::vector<std::unique_ptr<Shape>> SvgParser::parsePath(juce::String path) {
|
|
if (path.isEmpty()) {
|
|
return std::vector<std::unique_ptr<Shape>>();
|
|
}
|
|
|
|
state.currPoint = Vector2();
|
|
state.prevCubicControlPoint = std::nullopt;
|
|
state.prevQuadraticControlPoint = std::nullopt;
|
|
|
|
std::vector<std::string> commands = preProcessPath(path.toStdString());
|
|
std::vector<std::unique_ptr<Shape>> pathShapes;
|
|
|
|
for (auto& stdCommand : commands) {
|
|
if (!stdCommand.empty()) {
|
|
juce::String command(stdCommand);
|
|
char commandChar = command[0];
|
|
std::vector<float> nums;
|
|
|
|
if (commandChar != 'z' && commandChar != 'Z') {
|
|
juce::StringArray tokens = juce::StringArray::fromTokens(command.substring(1), " ", "");
|
|
|
|
for (juce::String token : tokens) {
|
|
token = token.trim();
|
|
if (token.isNotEmpty()) {
|
|
juce::StringArray decimalSplit = juce::StringArray::fromTokens(token, ".", "");
|
|
if (decimalSplit.size() == 1) {
|
|
auto num = decimalSplit[0].getFloatValue();
|
|
nums.push_back(num);
|
|
} else {
|
|
juce::String decimal = decimalSplit[0] + "." + decimalSplit[1];
|
|
nums.push_back(decimal.getFloatValue());
|
|
|
|
for (int i = 2; i < decimalSplit.size(); i++) {
|
|
decimal = "." + decimalSplit[i];
|
|
nums.push_back(decimal.getFloatValue());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<std::unique_ptr<Shape>> commandShapes;
|
|
|
|
switch (commandChar) {
|
|
case 'M':
|
|
commandShapes = MoveTo::absolute(state, nums);
|
|
break;
|
|
case 'm':
|
|
commandShapes = MoveTo::relative(state, nums);
|
|
break;
|
|
case 'L':
|
|
commandShapes = LineTo::absolute(state, nums);
|
|
break;
|
|
case 'l':
|
|
commandShapes = LineTo::relative(state, nums);
|
|
break;
|
|
case 'H':
|
|
commandShapes = LineTo::horizontalAbsolute(state, nums);
|
|
break;
|
|
case 'h':
|
|
commandShapes = LineTo::horizontalRelative(state, nums);
|
|
break;
|
|
case 'V':
|
|
commandShapes = LineTo::verticalAbsolute(state, nums);
|
|
break;
|
|
case 'v':
|
|
commandShapes = LineTo::verticalRelative(state, nums);
|
|
break;
|
|
case 'C':
|
|
commandShapes = CurveTo::absolute(state, nums);
|
|
break;
|
|
case 'c':
|
|
commandShapes = CurveTo::relative(state, nums);
|
|
break;
|
|
case 'S':
|
|
commandShapes = CurveTo::smoothAbsolute(state, nums);
|
|
break;
|
|
case 's':
|
|
commandShapes = CurveTo::smoothRelative(state, nums);
|
|
break;
|
|
case 'Q':
|
|
commandShapes = CurveTo::quarticAbsolute(state, nums);
|
|
break;
|
|
case 'q':
|
|
commandShapes = CurveTo::quarticRelative(state, nums);
|
|
break;
|
|
case 'T':
|
|
commandShapes = CurveTo::quarticSmoothAbsolute(state, nums);
|
|
break;
|
|
case 't':
|
|
commandShapes = CurveTo::quarticSmoothRelative(state, nums);
|
|
break;
|
|
case 'A':
|
|
commandShapes = EllipticalArcTo::absolute(state, nums);
|
|
break;
|
|
case 'a':
|
|
commandShapes = EllipticalArcTo::relative(state, nums);
|
|
break;
|
|
case 'Z':
|
|
commandShapes = ClosePath::absolute(state, nums);
|
|
break;
|
|
case 'z':
|
|
commandShapes = ClosePath::relative(state, nums);
|
|
break;
|
|
}
|
|
|
|
pathShapes.insert(pathShapes.end(), std::make_move_iterator(commandShapes.begin()), std::make_move_iterator(commandShapes.end()));
|
|
|
|
if (commandChar != 'c' && commandChar != 'C' && commandChar != 's' && commandChar != 'S') {
|
|
state.prevCubicControlPoint = std::nullopt;
|
|
}
|
|
if (commandChar != 'q' && commandChar != 'Q' && commandChar != 't' && commandChar != 'T') {
|
|
state.prevQuadraticControlPoint = std::nullopt;
|
|
}
|
|
}
|
|
}
|
|
|
|
return pathShapes;
|
|
}
|