Generating points along SVG paths

This commit is contained in:
Natty 2023-07-16 20:51:40 +02:00
parent 48b9769777
commit a84d4c5e54
Signed by: natty
GPG Key ID: BF6CB659ADEE60EC
6 changed files with 1046 additions and 6 deletions

View File

@ -8,5 +8,5 @@ find_package(SDL2 COMPONENTS SDL2 REQUIRED)
set(CMAKE_AUTOMOC ON)
add_executable(tsp main.cpp renderwindow.cpp renderwindow.h)
add_executable(tsp main.cpp renderwindow.cpp renderwindow.h svgpathextractor.cpp svgpathextractor.h)
target_link_libraries(tsp Qt6::Core Qt6::Widgets SDL2::SDL2)

View File

@ -1,18 +1,17 @@
#include <iostream>
#include <algorithm>
#include <vector>
#include <format>
#include "renderwindow.h"
#include <QApplication>
#include <QWidget>
#include <SDL2/SDL.h>
#include <qtimer.h>
int main(int argc, char *argv[])
{
std::setlocale(LC_NUMERIC, "C");
QApplication application(argc, argv);
SDL_InitSubSystem(SDL_INIT_VIDEO);

View File

@ -3,6 +3,7 @@
//
#include "renderwindow.h"
#include "svgpathextractor.h"
#include <iostream>
#include <QPushButton>
@ -10,7 +11,11 @@
#include <QComboBox>
#include <QLabel>
#include <QTimer>
#include <QMenu>
#include <QMenuBar>
#include <QFileDialog>
#include <fstream>
#include <ranges>
std::unique_ptr<Strategy> strategyFromName(const std::string& name) {
if (name == "Anna") {
@ -23,8 +28,26 @@ std::unique_ptr<Strategy> strategyFromName(const std::string& name) {
RenderWindow::RenderWindow() : window(nullptr), renderer(nullptr) {
this->mainWindowWidget = new QWidget();
this->layout = new QVBoxLayout(this->mainWindowWidget);
auto* menu = new QMenuBar();
auto* fileMenu = menu->addMenu("File");
auto* openAction = fileMenu->addAction("Open pattern");
openAction->setShortcut(QKeySequence::Open);
QObject::connect(openAction, &QAction::triggered, this, [this](){
auto filename = QFileDialog::getOpenFileName(this, "Open pattern", "", "SVG path files (*.*)");
if (!filename.isNull()) {
this->loadPattern(filename);
}
});
auto* quitAction = fileMenu->addAction("Quit");
quitAction->setShortcut(QKeySequence::Quit);
quitAction->setMenuRole(QAction::QuitRole);
QObject::connect(quitAction, &QAction::triggered, this, &RenderWindow::close);
this->setMenuBar(menu);
this->renderArea = new QWidget();
this->layout->addWidget(this->renderArea);
this->controlsLayout = new QHBoxLayout();
this->layout->addLayout(this->controlsLayout);
@ -111,6 +134,7 @@ void RenderWindow::hardResetState()
void RenderWindow::resetState() {
this->animTimer->stop();
this->playButton->setChecked(false);
this->strategy = strategyFromName(this->strategySelector->currentText().toStdString());
@ -203,12 +227,42 @@ void RenderWindow::togglePlay(bool play)
this->resetState();
}
this->animTimer->start(50);
this->animTimer->start(5);
} else {
this->animTimer->stop();
}
}
void RenderWindow::loadPattern(const QString& qString)
{
try {
std::ifstream file(qString.toStdString());
file.seekg(0, std::ios::end);
size_t size = file.tellg();
std::string buffer(size, '\0');
file.seekg(0);
file.read(&buffer[0], static_cast<std::streamsize>(size));
auto vertices = extractVertices(buffer);
const auto min_x = std::ranges::min(vertices | std::ranges::views::transform([](const Vertex& v) { return v.x; }));
const auto min_y = std::ranges::min(vertices | std::ranges::views::transform([](const Vertex& v) { return v.y; }));
const auto max_x = std::ranges::max(vertices | std::ranges::views::transform([](const Vertex& v) { return v.x; }));
const auto max_y = std::ranges::max(vertices | std::ranges::views::transform([](const Vertex& v) { return v.y; }));
const double scale = std::min(600.0 / (max_x - min_x), 600.0 / (max_y - min_y));
for (auto& vertex : vertices) {
vertex.x = (vertex.x - min_x) * scale + 100.0;
vertex.y = (vertex.y - min_y) * scale + 100.0;
}
this->state.vertices = vertices;
this->resetState();
} catch (std::exception& e) {
std::cerr << "Error loading pattern: " << e.what() << std::endl;
}
}
bool AnnaStrategy::step(State& state) {
if (!this->init) {
this->init = true;

View File

@ -85,6 +85,8 @@ class RenderWindow : public QMainWindow {
void resetState();
void hardResetState();
void togglePlay(bool play);
void loadPattern(const QString& qString);
};
#endif //TSP_RENDERWINDOW_H

968
svgpathextractor.cpp Normal file
View File

@ -0,0 +1,968 @@
//
// Created by natty on 15.7.23.
//
#include <vector>
#include <utility>
#include <format>
#include <iostream>
#include "svgpathextractor.h"
#include "renderwindow.h"
template<typename T>
std::pair<T, std::string_view::const_iterator> map(std::pair<std::string_view, std::string_view::const_iterator> in);
template<>
std::pair<double, std::string_view::const_iterator> map(std::pair<std::string_view, std::string_view::const_iterator> in) {
return { std::stod(std::string(in.first)), in.second };
}
template<>
std::pair<bool, std::string_view::const_iterator> map(std::pair<std::string_view, std::string_view::const_iterator> in) {
if (in.first == "0") {
return { false, in.second };
} else if (in.first == "1") {
return { true, in.second };
} else {
throw std::runtime_error(std::format("Flag parse error: {}", in.first));
}
}
std::string_view::const_iterator skipWhitespace(std::string_view::const_iterator path, std::string_view::const_iterator end) {
auto it = path;
while (it != end && (*it == ' ' || *it == '\t' || *it == '\xA' || *it == '\xC' || *it == '\xD')) {
++it;
}
return it;
}
std::string_view::const_iterator skipComma(std::string_view::const_iterator path, std::string_view::const_iterator end) {
auto it = path;
while (it != end && (*it == ',')) {
++it;
}
return it;
}
std::string_view::const_iterator skipSeparators(std::string_view::const_iterator path, std::string_view::const_iterator end) {
return skipWhitespace(skipComma(skipWhitespace(path, end), end), end);
}
std::pair<std::string_view, std::string_view::const_iterator> throwIfEmpty(std::pair<std::string_view, std::string_view::const_iterator> in) {
if (in.first.empty()) {
throw std::runtime_error("Unexpect end of input");
}
return in;
}
std::pair<std::string_view, std::string_view::const_iterator> nomDouble(std::string_view::const_iterator path, std::string_view::const_iterator end) {
auto it = path;
while (it != end && (*it == '-' && it == path || *it == '.' || (*it >= '0' && *it <= '9'))) {
++it;
}
return {{ &*path, static_cast<size_t>(it - path) }, it};
}
std::pair<std::string_view, std::string_view::const_iterator> nomFlag(std::string_view::const_iterator path, std::string_view::const_iterator end) {
auto it = path;
while (it != end && ((*it == '0' || *it == '1'))) {
++it;
}
return {{ &*path, static_cast<size_t>(it - path) }, it};
}
enum class CommandType {
Move,
Line,
HorLine,
VerLine,
Curve,
SmoothCurve,
Quadratic,
SmoothQuadratic,
EllipticalArc,
ClosePath
};
std::ostream& operator<< (std::ostream& stream, const CommandType& type) {
switch (type)
{
case CommandType::Move:
stream << "Move";
break;
case CommandType::Line:
stream << "Line";
break;
case CommandType::HorLine:
stream << "HorLine";
break;
case CommandType::VerLine:
stream << "VerLine";
break;
case CommandType::Curve:
stream << "Curve";
break;
case CommandType::SmoothCurve:
stream << "SmoothCurve";
break;
case CommandType::Quadratic:
stream << "Quadratic";
break;
case CommandType::SmoothQuadratic:
stream << "SmoothQuadratic";
break;
case CommandType::EllipticalArc:
stream << "EllipticalArc";
break;
case CommandType::ClosePath:
stream << "ClosePath";
break;
}
return stream;
}
struct Command {
CommandType type;
bool relative;
};
std::ostream& operator<< (std::ostream& stream, const Command& command) {
stream << command.type;
if (command.relative) {
stream << "Relative";
}
return stream;
}
struct Token {
enum class Type {
Command,
Number,
End,
Unknown
} type;
union TokenData {
double value;
Command command;
} data;
};
std::ostream& operator<< (std::ostream& stream, const Token& token) {
switch (token.type) {
case Token::Type::Command:
stream << token.data.command;
break;
case Token::Type::Number:
stream << token.data.value;
break;
case Token::Type::End:
stream << "End";
break;
case Token::Type::Unknown:
stream << "Unknown";
break;
}
return stream;
}
std::pair<Token, std::string_view::const_iterator> nomToken(bool skipSeparator, std::string_view::const_iterator it, std::string_view::const_iterator end)
{
if (skipSeparator)
it = skipSeparators(it, end);
else
it = skipWhitespace(it, end);
if (it == end) {
return {{ .type = Token::Type::End }, it};
}
std::optional<CommandType> commandType;
switch (std::tolower(*it)) {
case 'm':
commandType = CommandType::Move;
break;
case 'z':
commandType = CommandType::ClosePath;
break;
case 'l':
commandType = CommandType::Line;
break;
case 'h':
commandType = CommandType::HorLine;
break;
case 'v':
commandType = CommandType::VerLine;
break;
case 'c':
commandType = CommandType::Curve;
break;
case 's':
commandType = CommandType::SmoothCurve;
break;
case 'q':
commandType = CommandType::Quadratic;
break;
case 't':
commandType = CommandType::SmoothQuadratic;
break;
case 'a':
commandType = CommandType::EllipticalArc;
break;
}
if (commandType) {
return {{
.type = Token::Type::Command,
.data = {
.command = {
.type = *commandType,
.relative = !static_cast<bool>(isupper(*it))
}
}
}, it + 1};
}
if (*it == '-' || *it == '.' || (*it >= '0' && *it <= '9')) {
auto [value, it2] = map<double>(nomDouble(it, end));
return {{
.type = Token::Type::Number,
.data = {
.value = value
}
}, it2};
}
return {{
.type = Token::Type::Unknown
}, it};
}
void interpolateCubic(std::vector<Vertex>& output, size_t segments, double sx, double sy, double x1, double y1, double x2, double y2, double x, double y) {
auto segmentsScaled = static_cast<size_t>(std::round(std::hypot(x - sx, y - sy) / 50.0 * static_cast<double>(segments)));
for (size_t i = 1; i < segmentsScaled; ++i) {
const double t = static_cast<double>(i) / static_cast<double>(segmentsScaled);
const double t2 = t * t;
const double t3 = t2 * t;
const double inv_t = 1.0 - t;
const double inv_t2 = inv_t * inv_t;
const double inv_t3 = inv_t2 * inv_t;
const double px = inv_t3 * sx + 3 * inv_t2 * t * x1 + 3 * inv_t * t2 * x2 + t3 * x;
const double py = inv_t3 * sy + 3 * inv_t2 * t * y1 + 3 * inv_t * t2 * y2 + t3 * y;
output.emplace_back(px, py);
}
}
void interpolateQuadratic(std::vector<Vertex>& output, size_t segments, double sx, double sy, double x1, double y1, double x, double y) {
auto segmentsScaled = static_cast<size_t>(std::round(std::hypot(x - sx, y - sy) / 50.0 * static_cast<double>(segments)));
for (size_t i = 1; i < segmentsScaled; ++i) {
const double t = static_cast<double>(i) / static_cast<double>(segmentsScaled);
const double t2 = t * t;
const double inv_t = 1.0 - t;
const double inv_t2 = inv_t * inv_t;
const double px = inv_t2 * sx + 2 * inv_t * t * x1 + t2 * x;
const double py = inv_t2 * sy + 2 * inv_t * t * y1 + t2 * y;
output.emplace_back(px, py);
}
}
void interpolateLine(std::vector<Vertex>& output, size_t segments, double sx, double sy, double x, double y) {
auto segmentsScaled = static_cast<size_t>(std::round(std::hypot(x - sx, y - sy) / 50.0 * static_cast<double>(segments)));
for (size_t i = 1; i < segmentsScaled; ++i) {
const double t = static_cast<double>(i) / static_cast<double>(segmentsScaled);
const double inv_t = 1.0 - t;
const double px = inv_t * sx + t * x;
const double py = inv_t * sy + t * y;
output.emplace_back(px, py);
}
}
double vecAngle(double ux, double uy, double vx, double vy) {
double sign = ux * vy - uy * vx < 0.0 ? -1.0 : 1.0;
double dot = ux * vx + uy * vy;
double uLen = std::hypot(ux, uy);
double vLen = std::hypot(vx, vy);
return sign * std::acos(dot / (uLen * vLen));
}
void interpolateArc(std::vector<Vertex>& output, const size_t segments, double rx, double ry, double rot,
bool largeArcFlag, bool sweepFlag, double x1, double y1, double x2, double y2)
{
constexpr double PI = 3.14159;
constexpr double PI2 = 2.0 * PI;
double phi = rot * PI / 180.0;
double dx = x1 - x2;
double dy = y1 - y2;
double dxHalf = dx / 2.0;
double dyHalf = dy / 2.0;
double xAvg = (x1 + x2) / 2.0;
double yAvg = (y1 + y2) / 2.0;
double sinPhi = std::sin(phi);
double cosPhi = std::cos(phi);
double x1p = cosPhi * dxHalf + sinPhi * dyHalf;
double y1p = -sinPhi * dxHalf + cosPhi * dyHalf;
double x1p2 = x1p * x1p;
double y1p2 = y1p * y1p;
double rx2 = rx * rx;
double ry2 = ry * ry;
double lambda = std::sqrt(x1p2 / rx2 + y1p2 / ry2);
if (lambda > 1) {
rx *= lambda;
ry *= lambda;
}
double sig = largeArcFlag == sweepFlag ? -1.0 : 1.0;
double aNum = rx2 * ry2 - rx2 * y1p2 - ry2 * x1p2;
double aDenom = rx2 * y1p2 + ry2 * x1p2;
double a = sig * std::sqrt(std::abs(aNum / aDenom));
double cxp = a * (rx * y1p / ry);
double cyp = a * -(ry * x1p / rx);
double cx = cosPhi * cxp - sinPhi * cyp + xAvg;
double cy = sinPhi * cxp + cosPhi * cyp + yAvg;
double svx = (x1p - cxp) / rx;
double svy = (y1p - cyp) / ry;
double theta = vecAngle(1.0, 0.0, svx, svy);
double deltaThetaUnbound = vecAngle(svx, svy, (-x1p - cxp) / rx, (-y1p - cyp) / ry);
double deltaTheta = std::fmod(deltaThetaUnbound, PI2);
if (deltaTheta > 0.0 && !sweepFlag)
deltaTheta -= PI2;
if (deltaTheta < 0.0 && sweepFlag)
deltaTheta += PI2;
auto segmentsScaled = static_cast<size_t>(std::round(std::hypot(rx, ry) / 50.0 * static_cast<double>(segments)));
for (size_t i = 0; i < segmentsScaled; ++i)
{
const double t = static_cast<double>(i) / static_cast<double>(segmentsScaled - 1);
double px = rx * std::cos(theta + deltaTheta * t);
double py = ry * std::sin(theta + deltaTheta * t);
double x = cx + px * cosPhi - py * sinPhi;
double y = cy + px * sinPhi + py * cosPhi;
output.emplace_back(x, y);
}
}
static constexpr size_t SEGMENTS = 20;
std::vector<Vertex> extractVertices(std::string_view path) {
std::vector<Vertex> vertices;
std::optional<Command> lastCommand{};
bool repeatLastCommand = false;
Vertex pos{};
Vertex lastControlPoint{};
Vertex subpathStart{};
auto it = path.cbegin();
while (it != path.end()) {
auto lastCommandType = lastCommand ? lastCommand->type : CommandType::ClosePath;
auto [token, itNext] = nomToken(lastCommandType != CommandType::ClosePath, it, path.end());
it = itNext;
switch (token.type) {
case Token::Type::End:
break;
case Token::Type::Number:
switch (lastCommandType) {
case CommandType::Move:
{
if (!repeatLastCommand) {
vertices.push_back(pos);
}
auto x = token.data.value;
it = skipSeparators(it, path.end());
auto [y, it2] = map<double>(throwIfEmpty(nomDouble(it, path.end())));
double xAbs = lastCommand->relative ? pos.x + x : x;
double yAbs = lastCommand->relative ? pos.y + y : y;
interpolateLine(
vertices,
SEGMENTS,
pos.x, pos.y,
xAbs, yAbs
);
pos = { xAbs, yAbs };
lastControlPoint = pos;
vertices.push_back(pos);
break;
}
case CommandType::Line:
{
auto x = token.data.value;
it = skipSeparators(it, path.end());
auto [y, it2] = map<double>(throwIfEmpty(nomDouble(it, path.end())));
double xAbs = lastCommand->relative ? pos.x + x : x;
double yAbs = lastCommand->relative ? pos.y + y : y;
interpolateLine(
vertices,
SEGMENTS,
pos.x, pos.y,
xAbs, yAbs
);
pos = { xAbs, yAbs };
lastControlPoint = pos;
vertices.push_back(pos);
break;
}
case CommandType::HorLine:
{
auto x = token.data.value;
double xAbs = lastCommand->relative ? pos.x + x : x;
interpolateLine(
vertices,
SEGMENTS,
pos.x, pos.y,
xAbs, pos.y
);
pos = { xAbs, pos.y };
lastControlPoint = pos;
vertices.push_back(pos);
break;
}
case CommandType::VerLine:
{
auto y = token.data.value;
double yAbs = lastCommand->relative ? pos.y + y : y;
interpolateLine(
vertices,
SEGMENTS,
pos.x, pos.y,
pos.x, yAbs
);
pos = { pos.x, yAbs };
lastControlPoint = pos;
vertices.push_back(pos);
break;
}
case CommandType::Curve:
{
auto x1 = token.data.value;
it = skipSeparators(it, path.end());
auto [y1, it3] = map<double>(throwIfEmpty(nomDouble(it, path.end())));
it3 = skipSeparators(it3, path.end());
auto [x2, it4] = map<double>(throwIfEmpty(nomDouble(it3, path.end())));
it4 = skipSeparators(it4, path.end());
auto [y2, it5] = map<double>(throwIfEmpty(nomDouble(it4, path.end())));
it5 = skipSeparators(it5, path.end());
auto [x, it6] = map<double>(throwIfEmpty(nomDouble(it5, path.end())));
it6 = skipSeparators(it6, path.end());
auto [y, it7] = map<double>(throwIfEmpty(nomDouble(it6, path.end())));
it = it7;
double x2abs = lastCommand->relative ? pos.x + x2 : x2;
double y2abs = lastCommand->relative ? pos.y + y2 : y2;
double xAbs = lastCommand->relative ? pos.x + x : x;
double yAbs = lastCommand->relative ? pos.y + y : y;
interpolateCubic(
vertices,
SEGMENTS,
pos.x, pos.y,
lastCommand->relative ? pos.x + x1 : x1, lastCommand->relative ? pos.y + y1 : y1,
x2abs, y2abs,
xAbs, yAbs
);
lastControlPoint = { x2abs, y2abs };
pos = { xAbs, yAbs };
vertices.push_back(pos);
break;
}
case CommandType::SmoothCurve:
{
auto x2 = token.data.value;
it = skipSeparators(it, path.end());
auto [y2, it5] = map<double>(throwIfEmpty(nomDouble(it, path.end())));
it5 = skipSeparators(it5, path.end());
auto [x, it6] = map<double>(throwIfEmpty(nomDouble(it5, path.end())));
it6 = skipSeparators(it6, path.end());
auto [y, it7] = map<double>(throwIfEmpty(nomDouble(it6, path.end())));
it = it7;
double x1abs = 2.0 * pos.x - lastControlPoint.x;
double y1abs = 2.0 * pos.y - lastControlPoint.y;
double x2abs = lastCommand->relative ? pos.x + x2 : x2;
double y2abs = lastCommand->relative ? pos.y + y2 : y2;
double xAbs = lastCommand->relative ? pos.x + x : x;
double yAbs = lastCommand->relative ? pos.y + y : y;
interpolateCubic(
vertices,
SEGMENTS,
pos.x, pos.y,
x1abs, y1abs,
x2abs, y2abs,
xAbs, yAbs
);
lastControlPoint = { x2abs, y2abs };
pos = { xAbs, yAbs };
vertices.push_back(pos);
break;
}
case CommandType::Quadratic:
{
auto x1 = token.data.value;
it = skipSeparators(it, path.end());
auto [y1, it3] = map<double>(throwIfEmpty(nomDouble(it, path.end())));
it3 = skipSeparators(it3, path.end());
auto [x, it4] = map<double>(throwIfEmpty(nomDouble(it3, path.end())));
it4 = skipSeparators(it4, path.end());
auto [y, it5] = map<double>(throwIfEmpty(nomDouble(it4, path.end())));
it = it5;
double x1abs = lastCommand->relative ? pos.x + x1 : x1;
double y1abs = lastCommand->relative ? pos.y + y1 : y1;
double xAbs = lastCommand->relative ? pos.x + x : x;
double yAbs = lastCommand->relative ? pos.y + y : y;
interpolateQuadratic(
vertices,
SEGMENTS,
pos.x, pos.y,
x1abs, y1abs,
xAbs, yAbs
);
lastControlPoint = { x1abs, y1abs };
pos = { xAbs, yAbs };
vertices.push_back(pos);
break;
}
case CommandType::SmoothQuadratic:
{
auto x = token.data.value;
it = skipSeparators(it, path.end());
auto [y, it2] = map<double>(throwIfEmpty(nomDouble(it, path.end())));
it = it2;
double x1abs = 2.0 * pos.x - lastControlPoint.x;
double y1abs = 2.0 * pos.y - lastControlPoint.y;
double xAbs = lastCommand->relative ? pos.x + x : x;
double yAbs = lastCommand->relative ? pos.y + y : y;
interpolateQuadratic(
vertices,
SEGMENTS,
pos.x, pos.y,
x1abs, y1abs,
xAbs, yAbs
);
lastControlPoint = { x1abs, y1abs };
pos = { xAbs, yAbs };
vertices.push_back(pos);
break;
}
case CommandType::EllipticalArc:
{
it = skipSeparators(it, path.end());
auto rx = token.data.value;
auto [ry, it3] = map<double>(throwIfEmpty(nomDouble(it, path.end())));
it3 = skipSeparators(it3, path.end());
auto [rot, it4] = map<double>(throwIfEmpty(nomDouble(it3, path.end())));
it4 = skipSeparators(it4, path.end());
auto [largeArcFlag, it5] = map<bool>(throwIfEmpty(nomFlag(it4, path.end())));
it5 = skipSeparators(it5, path.end());
auto [sweepFlag, it6] = map<bool>(throwIfEmpty(nomFlag(it5, path.end())));
it6 = skipSeparators(it6, path.end());
auto [x, it7] = map<double>(throwIfEmpty(nomDouble(it6, path.end())));
it7 = skipSeparators(it6, path.end());
auto [y, it8] = map<double>(throwIfEmpty(nomDouble(it7, path.end())));
it = it8;
rx = abs(rx);
ry = abs(ry);
double xAbs = lastCommand->relative ? pos.x + x : x;
double yAbs = lastCommand->relative ? pos.y + y : y;
interpolateArc(vertices, SEGMENTS, rx, ry, rot, largeArcFlag, sweepFlag, pos.x, pos.y, xAbs, yAbs);
pos = { xAbs, yAbs };
lastControlPoint = pos;
break;
}
case CommandType::ClosePath:
throw std::runtime_error("Unexpected number");
}
if (!repeatLastCommand) {
repeatLastCommand = true;
}
break;
case Token::Type::Command:
switch (token.data.command.type) {
case CommandType::Move:
{
it = skipWhitespace(it, path.end());
auto [x, it2] = map<double>(throwIfEmpty(nomDouble(it, path.end())));
it2 = skipSeparators(it2, path.end());
auto [y, it3] = map<double>(throwIfEmpty(nomDouble(it2, path.end())));
it = it3;
if (token.data.command.relative) {
pos.x += x;
pos.y += y;
} else {
pos = { x, y };
}
subpathStart = pos;
lastControlPoint = pos;
break;
}
case CommandType::Line:
{
if (!repeatLastCommand && (lastCommand && lastCommand->type == CommandType::Move)) {
vertices.push_back(pos);
}
it = skipWhitespace(it, path.end());
auto [x, it2] = map<double>(throwIfEmpty(nomDouble(it, path.end())));
it2 = skipSeparators(it2, path.end());
auto [y, it3] = map<double>(throwIfEmpty(nomDouble(it2, path.end())));
it = it3;
double xAbs = token.data.command.relative ? pos.x + x : x;
double yAbs = token.data.command.relative ? pos.y + y : y;
interpolateLine(
vertices,
SEGMENTS,
pos.x, pos.y,
xAbs, yAbs
);
pos = { xAbs, yAbs };
lastControlPoint = pos;
vertices.push_back(pos);
break;
}
case CommandType::HorLine:
{
if (!repeatLastCommand && (lastCommand && lastCommand->type == CommandType::Move)) {
vertices.push_back(pos);
}
it = skipWhitespace(it, path.end());
auto [x, it2] = map<double>(throwIfEmpty(nomDouble(it, path.end())));
it = it2;
double xAbs = token.data.command.relative ? pos.x + x : x;
interpolateLine(
vertices,
SEGMENTS,
pos.x, pos.y,
xAbs, pos.y
);
pos = { xAbs, pos.y };
lastControlPoint = pos;
vertices.push_back(pos);
break;
}
case CommandType::VerLine:
{
if (!repeatLastCommand && (lastCommand && lastCommand->type == CommandType::Move)) {
vertices.push_back(pos);
}
it = skipWhitespace(it, path.end());
auto [y, it2] = map<double>(throwIfEmpty(nomDouble(it, path.end())));
it = it2;
double yAbs = token.data.command.relative ? pos.y + y : y;
interpolateLine(
vertices,
SEGMENTS,
pos.x, pos.y,
pos.x, yAbs
);
pos = { pos.x, yAbs };
lastControlPoint = pos;
vertices.push_back(pos);
break;
}
case CommandType::Curve:
{
if (!repeatLastCommand && (lastCommand && lastCommand->type == CommandType::Move)) {
vertices.push_back(pos);
}
it = skipWhitespace(it, path.end());
auto [x1, it2] = map<double>(throwIfEmpty(nomDouble(it, path.end())));
it2 = skipSeparators(it2, path.end());
auto [y1, it3] = map<double>(throwIfEmpty(nomDouble(it2, path.end())));
it3 = skipSeparators(it3, path.end());
auto [x2, it4] = map<double>(throwIfEmpty(nomDouble(it3, path.end())));
it4 = skipSeparators(it4, path.end());
auto [y2, it5] = map<double>(throwIfEmpty(nomDouble(it4, path.end())));
it5 = skipSeparators(it5, path.end());
auto [x, it6] = map<double>(throwIfEmpty(nomDouble(it5, path.end())));
it6 = skipSeparators(it6, path.end());
auto [y, it7] = map<double>(throwIfEmpty(nomDouble(it6, path.end())));
it = it7;
double x2abs = token.data.command.relative ? pos.x + x2 : x2;
double y2abs = token.data.command.relative ? pos.y + y2 : y2;
double xAbs = token.data.command.relative ? pos.x + x : x;
double yAbs = token.data.command.relative ? pos.y + y : y;
interpolateCubic(
vertices,
SEGMENTS,
pos.x, pos.y,
token.data.command.relative ? pos.x + x1 : x1, token.data.command.relative ? pos.y + y1 : y1,
x2abs, y2abs,
xAbs, yAbs
);
lastControlPoint = { x2abs, y2abs };
pos = { xAbs, yAbs };
vertices.push_back(pos);
break;
}
case CommandType::SmoothCurve:
{
if (!repeatLastCommand && (lastCommand && lastCommand->type == CommandType::Move)) {
vertices.push_back(pos);
}
it = skipWhitespace(it, path.end());
auto [x2, it4] = map<double>(throwIfEmpty(nomDouble(it, path.end())));
it4 = skipSeparators(it4, path.end());
auto [y2, it5] = map<double>(throwIfEmpty(nomDouble(it4, path.end())));
it5 = skipSeparators(it5, path.end());
auto [x, it6] = map<double>(throwIfEmpty(nomDouble(it5, path.end())));
it6 = skipSeparators(it6, path.end());
auto [y, it7] = map<double>(throwIfEmpty(nomDouble(it6, path.end())));
it = it7;
double x1abs, y1abs;
if (!lastCommand || (lastCommand->type != CommandType::Curve && lastCommand->type != CommandType::SmoothCurve)) {
x1abs = pos.x;
y1abs = pos.y;
} else {
x1abs = 2.0 * pos.x - lastControlPoint.x;
y1abs = 2.0 * pos.y - lastControlPoint.y;
}
double x2abs = token.data.command.relative ? pos.x + x2 : x2;
double y2abs = token.data.command.relative ? pos.y + y2 : y2;
double xAbs = token.data.command.relative ? pos.x + x : x;
double yAbs = token.data.command.relative ? pos.y + y : y;
interpolateCubic(
vertices,
SEGMENTS,
pos.x, pos.y,
x1abs, y1abs,
x2abs, y2abs,
xAbs, yAbs
);
lastControlPoint = { x2abs, y2abs };
pos = { xAbs, yAbs };
vertices.push_back(pos);
break;
}
case CommandType::Quadratic:
{
if (!repeatLastCommand && (lastCommand && lastCommand->type == CommandType::Move)) {
vertices.push_back(pos);
}
it = skipWhitespace(it, path.end());
auto [x1, it4] = map<double>(throwIfEmpty(nomDouble(it, path.end())));
it4 = skipSeparators(it4, path.end());
auto [y1, it5] = map<double>(throwIfEmpty(nomDouble(it4, path.end())));
it5 = skipSeparators(it5, path.end());
auto [x, it6] = map<double>(throwIfEmpty(nomDouble(it5, path.end())));
it6 = skipSeparators(it6, path.end());
auto [y, it7] = map<double>(throwIfEmpty(nomDouble(it6, path.end())));
it = it7;
double x1abs = token.data.command.relative ? pos.x + x1 : x1;
double y1abs = token.data.command.relative ? pos.y + y1 : y1;
double xAbs = token.data.command.relative ? pos.x + x : x;
double yAbs = token.data.command.relative ? pos.y + y : y;
interpolateQuadratic(
vertices,
SEGMENTS,
pos.x, pos.y,
x1abs, y1abs,
xAbs, yAbs
);
pos = { xAbs, yAbs };
lastControlPoint = { x1abs, y1abs };
vertices.push_back(pos);
break;
}
case CommandType::SmoothQuadratic:
{
if (!repeatLastCommand && (lastCommand && lastCommand->type == CommandType::Move)) {
vertices.push_back(pos);
}
it = skipWhitespace(it, path.end());
auto [x, it2] = map<double>(throwIfEmpty(nomDouble(it, path.end())));
it2 = skipSeparators(it2, path.end());
auto [y, it3] = map<double>(throwIfEmpty(nomDouble(it2, path.end())));
it = it3;
double x1abs, y1abs;
if (!lastCommand || (lastCommand->type != CommandType::Quadratic && lastCommand->type != CommandType::SmoothQuadratic)) {
x1abs = pos.x;
y1abs = pos.y;
} else {
x1abs = 2.0 * pos.x - lastControlPoint.x;
y1abs = 2.0 * pos.y - lastControlPoint.y;
}
double xAbs = token.data.command.relative ? pos.x + x : x;
double yAbs = token.data.command.relative ? pos.y + y : y;
interpolateQuadratic(
vertices,
SEGMENTS,
pos.x, pos.y,
x1abs, y1abs,
xAbs, yAbs
);
lastControlPoint = { x1abs, y1abs };
pos = { xAbs, yAbs };
vertices.push_back(pos);
break;
}
case CommandType::EllipticalArc:
{
it = skipWhitespace(it, path.end());
auto [rx, it2] = map<double>(throwIfEmpty(nomDouble(it, path.end())));
it2 = skipSeparators(it2, path.end());
auto [ry, it3] = map<double>(throwIfEmpty(nomDouble(it2, path.end())));
it3 = skipSeparators(it3, path.end());
auto [rot, it4] = map<double>(throwIfEmpty(nomDouble(it3, path.end())));
it4 = skipSeparators(it4, path.end());
auto [largeArcFlag, it5] = map<bool>(throwIfEmpty(nomFlag(it4, path.end())));
it5 = skipSeparators(it5, path.end());
auto [sweepFlag, it6] = map<bool>(throwIfEmpty(nomFlag(it5, path.end())));
it6 = skipSeparators(it6, path.end());
auto [x, it7] = map<double>(throwIfEmpty(nomDouble(it6, path.end())));
it7 = skipSeparators(it7, path.end());
auto [y, it8] = map<double>(throwIfEmpty(nomDouble(it7, path.end())));
it = it8;
rx = abs(rx);
ry = abs(ry);
double xAbs = token.data.command.relative ? pos.x + x : x;
double yAbs = token.data.command.relative ? pos.y + y : y;
interpolateArc(vertices, SEGMENTS, rx, ry, rot, largeArcFlag, sweepFlag, pos.x, pos.y, xAbs, yAbs);
pos = { xAbs, yAbs };
lastControlPoint = pos;
break;
}
case CommandType::ClosePath:
{
interpolateLine(
vertices,
SEGMENTS,
pos.x, pos.y,
subpathStart.x, subpathStart.y
);
break;
}
}
lastCommand = token.data.command;
repeatLastCommand = false;
break;
case Token::Type::Unknown:
throw std::runtime_error("Unknown token");
}
}
return vertices;
}

17
svgpathextractor.h Normal file
View File

@ -0,0 +1,17 @@
//
// Created by natty on 15.7.23.
//
#ifndef TSP_SVGPATHEXTRACTOR_H
#define TSP_SVGPATHEXTRACTOR_H
#include <string>
#include <string_view>
#include "svgpathextractor.h"
#include "renderwindow.h"
std::vector<Vertex> extractVertices(std::string_view path);
#endif //TSP_SVGPATHEXTRACTOR_H