kopia lustrzana https://github.com/jameshball/osci-render
Fully implement SVG parser
rodzic
a063f2f988
commit
b0806ecf3d
|
@ -1,5 +1,7 @@
|
|||
#include "FileParser.h"
|
||||
#include "../shape/Line.h"
|
||||
#include "../shape/Arc.h"
|
||||
#include <numbers>
|
||||
|
||||
FileParser::FileParser() {}
|
||||
|
||||
|
@ -24,7 +26,7 @@ std::vector<std::unique_ptr<Shape>> FileParser::next() {
|
|||
return svg->draw();
|
||||
}
|
||||
auto tempShapes = std::vector<std::unique_ptr<Shape>>();
|
||||
tempShapes.push_back(std::make_unique<Line>(0.0, 0.0, 1.0, 1.0));
|
||||
tempShapes.push_back(std::make_unique<Arc>(0, 0, 0.5, 0.5, std::numbers::pi / 4.0, 2 * std::numbers::pi));
|
||||
return tempShapes;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
#include "Arc.h"
|
||||
#include <numbers>
|
||||
#include "Line.h"
|
||||
|
||||
Arc::Arc(double x, double y, double radiusX, double radiusY, double startAngle, double endAngle) : x(x), y(y), radiusX(radiusX), radiusY(radiusY), startAngle(startAngle), endAngle(endAngle) {}
|
||||
|
||||
Vector2 Arc::nextVector(double drawingProgress) {
|
||||
// scale between start and end angle in the positive direction
|
||||
double angle = startAngle + endAngle * drawingProgress;
|
||||
return Vector2(
|
||||
x + radiusX * std::cos(angle),
|
||||
y + radiusY * std::sin(angle)
|
||||
);
|
||||
}
|
||||
|
||||
void Arc::rotate(double theta) {
|
||||
double cosTheta = std::cos(theta);
|
||||
double sinTheta = std::sin(theta);
|
||||
|
||||
double newX = x * cosTheta - y * sinTheta;
|
||||
double newY = x * sinTheta + y * cosTheta;
|
||||
double newWidth = radiusX * cosTheta - radiusY * sinTheta;
|
||||
double newHeight = radiusX * sinTheta + radiusY * cosTheta;
|
||||
|
||||
x = newX;
|
||||
y = newY;
|
||||
radiusX = newWidth;
|
||||
radiusY = newHeight;
|
||||
|
||||
double newStartAngle = startAngle + theta;
|
||||
double newEndAngle = endAngle + theta;
|
||||
|
||||
if (newStartAngle > 2 * std::numbers::pi) {
|
||||
newStartAngle -= 2 * std::numbers::pi;
|
||||
}
|
||||
if (newEndAngle > 2 * std::numbers::pi) {
|
||||
newEndAngle -= 2 * std::numbers::pi;
|
||||
}
|
||||
|
||||
startAngle = newStartAngle;
|
||||
endAngle = newEndAngle;
|
||||
}
|
||||
|
||||
void Arc::scale(double x, double y) {
|
||||
this->x *= x;
|
||||
this->y *= y;
|
||||
this->radiusX *= x;
|
||||
this->radiusY *= y;
|
||||
}
|
||||
|
||||
void Arc::translate(double x, double y) {
|
||||
this->x += x;
|
||||
this->y += y;
|
||||
}
|
||||
|
||||
double Arc::length() {
|
||||
if (len < 0) {
|
||||
len = 0;
|
||||
double angle = startAngle;
|
||||
double step = (endAngle - startAngle) / 500;
|
||||
for (int i = 0; i < 500; i++) {
|
||||
Vector2 v1 = nextVector(i / 500.0);
|
||||
Vector2 v2 = nextVector((i + 1) / 500.0);
|
||||
len += Line(v1.x, v1.y, v2.x, v2.y).length();
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
std::unique_ptr<Shape> Arc::clone() {
|
||||
return std::make_unique<Arc>(x, y, radiusX, radiusY, startAngle, endAngle);
|
||||
}
|
||||
|
||||
std::string Arc::type() {
|
||||
return std::string("Arc");
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#include "Shape.h"
|
||||
#include "Vector2.h"
|
||||
|
||||
class Arc : public Shape {
|
||||
public:
|
||||
Arc(double x, double y, double radiusX, double radiusY, double startAngle, double endAngle);
|
||||
|
||||
Vector2 nextVector(double drawingProgress) override;
|
||||
void rotate(double theta) override;
|
||||
void scale(double x, double y) override;
|
||||
void translate(double x, double y) override;
|
||||
double length() override;
|
||||
std::unique_ptr<Shape> clone() override;
|
||||
std::string type() override;
|
||||
|
||||
double x, y, radiusX, radiusY, startAngle, endAngle;
|
||||
|
||||
};
|
|
@ -73,3 +73,7 @@ double CubicBezierCurve::length() {
|
|||
std::unique_ptr<Shape> CubicBezierCurve::clone() {
|
||||
return std::make_unique<CubicBezierCurve>(x1, y1, x2, y2, x3, y3, x4, y4);
|
||||
}
|
||||
|
||||
std::string CubicBezierCurve::type() {
|
||||
return std::string("CubicBezierCurve");
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ public:
|
|||
void translate(double x, double y) override;
|
||||
double length() override;
|
||||
std::unique_ptr<Shape> clone() override;
|
||||
std::string type() override;
|
||||
|
||||
double x1, y1, x2, y2, x3, y3, x4, y4;
|
||||
|
||||
|
|
|
@ -52,3 +52,7 @@ double inline Line::length() {
|
|||
std::unique_ptr<Shape> Line::clone() {
|
||||
return std::make_unique<Line>(x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
std::string Line::type() {
|
||||
return std::string("Line");
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ public:
|
|||
void translate(double x, double y) override;
|
||||
double length() override;
|
||||
std::unique_ptr<Shape> clone() override;
|
||||
std::string type() override;
|
||||
|
||||
double x1, y1, x2, y2;
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "Shape.h"
|
||||
#include "Line.h"
|
||||
|
||||
double Shape::totalLength(std::vector<std::unique_ptr<Shape>>& shapes) {
|
||||
double length = 0.0;
|
||||
|
@ -7,3 +8,106 @@ double Shape::totalLength(std::vector<std::unique_ptr<Shape>>& shapes) {
|
|||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
void Shape::normalize(std::vector<std::unique_ptr<Shape>>& shapes, double width, double height) {
|
||||
double maxDim = std::max(width, height);
|
||||
|
||||
for (auto& shape : shapes) {
|
||||
shape->scale(2.0 / maxDim, -2.0 / maxDim);
|
||||
shape->translate(-1.0, 1.0);
|
||||
}
|
||||
|
||||
removeOutOfBounds(shapes);
|
||||
}
|
||||
|
||||
void Shape::normalize(std::vector<std::unique_ptr<Shape>>& shapes) {
|
||||
double oldHeight = height(shapes);
|
||||
double oldWidth = width(shapes);
|
||||
double maxDim = std::max(oldHeight, oldWidth);
|
||||
|
||||
for (auto& shape : shapes) {
|
||||
shape->scale(2.0 / maxDim, -2.0 / maxDim);
|
||||
}
|
||||
|
||||
Vector2 max = maxVector(shapes);
|
||||
double newHeight = height(shapes);
|
||||
|
||||
for (auto& shape : shapes) {
|
||||
shape->translate(-1.0, -max.y + newHeight / 2.0);
|
||||
}
|
||||
}
|
||||
|
||||
double Shape::height(std::vector<std::unique_ptr<Shape>>& shapes) {
|
||||
double maxY = std::numeric_limits<double>::min();
|
||||
double minY = std::numeric_limits<double>::max();
|
||||
|
||||
Vector2 vectors[4];
|
||||
|
||||
for (auto& shape : shapes) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
vectors[i] = shape->nextVector(i * 1.0 / 4.0);
|
||||
}
|
||||
|
||||
for (auto& vector : vectors) {
|
||||
maxY = std::max(maxY, vector.y);
|
||||
minY = std::min(minY, vector.y);
|
||||
}
|
||||
}
|
||||
|
||||
return std::abs(maxY - minY);
|
||||
}
|
||||
|
||||
double Shape::width(std::vector<std::unique_ptr<Shape>>& shapes) {
|
||||
double maxX = std::numeric_limits<double>::min();
|
||||
double minX = std::numeric_limits<double>::max();
|
||||
|
||||
Vector2 vectors[4];
|
||||
|
||||
for (auto& shape : shapes) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
vectors[i] = shape->nextVector(i * 1.0 / 4.0);
|
||||
}
|
||||
|
||||
for (auto& vector : vectors) {
|
||||
maxX = std::max(maxX, vector.x);
|
||||
minX = std::min(minX, vector.x);
|
||||
}
|
||||
}
|
||||
|
||||
return std::abs(maxX - minX);
|
||||
}
|
||||
|
||||
Vector2 Shape::maxVector(std::vector<std::unique_ptr<Shape>>& shapes) {
|
||||
double maxX = std::numeric_limits<double>::min();
|
||||
double maxY = std::numeric_limits<double>::min();
|
||||
|
||||
for (auto& shape : shapes) {
|
||||
Vector2 startVector = shape->nextVector(0);
|
||||
Vector2 endVector = shape->nextVector(1);
|
||||
|
||||
double x = std::max(startVector.x, endVector.x);
|
||||
double y = std::max(startVector.y, endVector.y);
|
||||
|
||||
maxX = std::max(x, maxX);
|
||||
maxY = std::max(y, maxY);
|
||||
}
|
||||
|
||||
return Vector2(maxX, maxY);
|
||||
}
|
||||
|
||||
void Shape::removeOutOfBounds(std::vector<std::unique_ptr<Shape>>& shapes) {
|
||||
for (int i = 0; i < shapes.size(); i++) {
|
||||
Vector2 start = shapes[i]->nextVector(0);
|
||||
Vector2 end = shapes[i]->nextVector(1);
|
||||
|
||||
if ((start.x < 1 && start.x > -1) || (start.y < 1 && start.y > -1)) {
|
||||
if ((end.x < 1 && end.x > -1) || (end.y < 1 && end.y > -1)) {
|
||||
if (shapes[i]->type() == "Line") {
|
||||
Vector2 newStart(std::min(std::max(start.x, -1.0), 1.0), std::min(std::max(start.y, -1.0), 1.0));
|
||||
Vector2 newEnd(std::min(std::max(end.x, -1.0), 1.0), std::min(std::max(end.y, -1.0), 1.0));
|
||||
shapes[i] = std::make_unique<Line>(newStart.x, newStart.y, newEnd.x, newEnd.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <vector>
|
||||
#include <memory>
|
||||
#include "Vector2.h"
|
||||
#include <string>
|
||||
|
||||
class Shape {
|
||||
public:
|
||||
|
@ -14,8 +15,15 @@ public:
|
|||
virtual void translate(double x, double y) = 0;
|
||||
virtual double length() = 0;
|
||||
virtual std::unique_ptr<Shape> clone() = 0;
|
||||
virtual std::string type() = 0;
|
||||
|
||||
static double totalLength(std::vector<std::unique_ptr<Shape>>&);
|
||||
static void normalize(std::vector<std::unique_ptr<Shape>>&, double, double);
|
||||
static void normalize(std::vector<std::unique_ptr<Shape>>&);
|
||||
static double height(std::vector<std::unique_ptr<Shape>>&);
|
||||
static double width(std::vector<std::unique_ptr<Shape>>&);
|
||||
static Vector2 maxVector(std::vector<std::unique_ptr<Shape>>&);
|
||||
static void removeOutOfBounds(std::vector<std::unique_ptr<Shape>>&);
|
||||
|
||||
const double INVALID_LENGTH = -1.0;
|
||||
|
||||
|
|
|
@ -1,13 +1,125 @@
|
|||
#include "EllipticalArcTo.h"
|
||||
#include "../shape/Line.h"
|
||||
#include <numbers>
|
||||
#include "../shape/Arc.h"
|
||||
|
||||
std::vector<std::unique_ptr<Shape>> EllipticalArcTo::absolute(SvgState& state, std::vector<float>& args) {
|
||||
return std::vector<std::unique_ptr<Shape>>();
|
||||
return parseEllipticalArc(state, args, true);
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<Shape>> EllipticalArcTo::relative(SvgState& state, std::vector<float>& args) {
|
||||
return std::vector<std::unique_ptr<Shape>>();
|
||||
return parseEllipticalArc(state, args, false);
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<Shape>> EllipticalArcTo::parseEllipticalArc(SvgState& state, std::vector<float>& args, bool isAbsolute) {
|
||||
return std::vector<std::unique_ptr<Shape>>();
|
||||
if (args.size() % 7 != 0 || args.size() < 7) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<Shape>> shapes;
|
||||
for (int i = 0; i < args.size(); i += 7) {
|
||||
Vector2 newPoint(args[i + 5], args[i + 6]);
|
||||
if (!isAbsolute) {
|
||||
newPoint.translate(state.currPoint.x, state.currPoint.y);
|
||||
}
|
||||
|
||||
createArc(shapes, state.currPoint, args[i], args[i + 1], args[i + 2], args[i + 3] == 1, args[i + 4] == 1, newPoint);
|
||||
state.currPoint = newPoint;
|
||||
}
|
||||
|
||||
return shapes;
|
||||
}
|
||||
|
||||
// The following algorithm is completely based on https://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
|
||||
void EllipticalArcTo::createArc(std::vector<std::unique_ptr<Shape>>& shapes, Vector2 start, float rx, float ry, float theta, bool largeArcFlag, bool sweepFlag, Vector2 end) {
|
||||
double x2 = end.x;
|
||||
double y2 = end.y;
|
||||
// Ensure radii are valid
|
||||
if (rx == 0 || ry == 0) {
|
||||
shapes.push_back(std::make_unique<Line>(start.x, start.y, end.x, end.y));
|
||||
return;
|
||||
}
|
||||
double x1 = start.x;
|
||||
double y1 = start.y;
|
||||
// Compute the half distance between the current and the final point
|
||||
double dx2 = (x1 - x2) / 2.0;
|
||||
double dy2 = (y1 - y2) / 2.0;
|
||||
// Convert theta from degrees to radians
|
||||
|
||||
theta = std::fmod(theta, 360.0) * (std::numbers::pi / 180.0);
|
||||
|
||||
//
|
||||
// Step 1 : Compute (x1', y1')
|
||||
//
|
||||
double x1prime = std::cos(theta) * dx2 + std::sin(theta) * dy2;
|
||||
double y1prime = -std::sin(theta) * dx2 + std::cos(theta) * dy2;
|
||||
// Ensure radii are large enough
|
||||
rx = std::abs(rx);
|
||||
ry = std::abs(ry);
|
||||
double Prx = rx * rx;
|
||||
double Pry = ry * ry;
|
||||
double Px1prime = x1prime * x1prime;
|
||||
double Py1prime = y1prime * y1prime;
|
||||
double d = Px1prime / Prx + Py1prime / Pry;
|
||||
if (d > 1) {
|
||||
rx = std::abs(std::sqrt(d) * rx);
|
||||
ry = std::abs(std::sqrt(d) * ry);
|
||||
Prx = rx * rx;
|
||||
Pry = ry * ry;
|
||||
}
|
||||
|
||||
//
|
||||
// Step 2 : Compute (cx', cy')
|
||||
//
|
||||
double sign = (largeArcFlag == sweepFlag) ? -1.0 : 1.0;
|
||||
// Forcing the inner term to be positive. It should be >= 0 but can sometimes be negative due
|
||||
// to double precision.
|
||||
double coef = sign *
|
||||
std::sqrt(std::abs((Prx * Pry) - (Prx * Py1prime) - (Pry * Px1prime)) / ((Prx * Py1prime) + (Pry * Px1prime)));
|
||||
double cxprime = coef * ((rx * y1prime) / ry);
|
||||
double cyprime = coef * -((ry * x1prime) / rx);
|
||||
|
||||
//
|
||||
// Step 3 : Compute (cx, cy) from (cx', cy')
|
||||
//
|
||||
double sx2 = (x1 + x2) / 2.0;
|
||||
double sy2 = (y1 + y2) / 2.0;
|
||||
double cx = sx2 + std::cos(theta) * cxprime - std::sin(theta) * cyprime;
|
||||
double cy = sy2 + std::sin(theta) * cxprime + std::cos(theta) * cyprime;
|
||||
|
||||
//
|
||||
// Step 4 : Compute the angleStart (theta1) and the angleExtent (dtheta)
|
||||
//
|
||||
double ux = (x1prime - cxprime) / rx;
|
||||
double uy = (y1prime - cyprime) / ry;
|
||||
double vx = (-x1prime - cxprime) / rx;
|
||||
double vy = (-y1prime - cyprime) / ry;
|
||||
double p, n;
|
||||
// Compute the angle start
|
||||
n = std::sqrt((ux * ux) + (uy * uy));
|
||||
p = ux; // (1 * ux) + (0 * uy)
|
||||
sign = (uy < 0) ? -1.0 : 1.0;
|
||||
double angleStart = sign * std::acos(p / n);
|
||||
// Compute the angle extent
|
||||
n = std::sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy));
|
||||
p = ux * vx + uy * vy;
|
||||
sign = (ux * vy - uy * vx < 0) ? -1.0 : 1.0;
|
||||
double angleExtent = sign * std::acos(p / n);
|
||||
if (!sweepFlag && angleExtent > 0) {
|
||||
angleExtent -= 2 * std::numbers::pi;
|
||||
} else if (sweepFlag && angleExtent < 0) {
|
||||
angleExtent += 2 * std::numbers::pi;
|
||||
}
|
||||
angleExtent = std::fmod(angleExtent, 2 * std::numbers::pi);
|
||||
angleStart = std::fmod(angleStart, 2 * std::numbers::pi);
|
||||
|
||||
auto arc = std::make_unique<Arc>(cx, cy, rx, ry, angleStart, angleExtent);
|
||||
arc->translate(-cx, -cy);
|
||||
arc->rotate(theta);
|
||||
arc->translate(cx, cy);
|
||||
|
||||
Vector2 startPoint = arc->nextVector(0);
|
||||
Vector2 endPoint = arc->nextVector(1);
|
||||
|
||||
shapes.push_back(std::move(arc));
|
||||
}
|
||||
|
|
|
@ -9,4 +9,5 @@ public:
|
|||
static std::vector<std::unique_ptr<Shape>> relative(SvgState& state, std::vector<float>& args);
|
||||
private:
|
||||
static std::vector<std::unique_ptr<Shape>> parseEllipticalArc(SvgState& state, std::vector<float>& args, bool isAbsolute);
|
||||
static void createArc(std::vector<std::unique_ptr<Shape>>& shapes, Vector2 start, float rx, float ry, float theta, bool largeArcFlag, bool sweepFlag, Vector2 end);
|
||||
};
|
|
@ -31,9 +31,9 @@ SvgParser::SvgParser(juce::String svgFile) {
|
|||
double width = dimensions.first;
|
||||
double height = dimensions.second;
|
||||
|
||||
// normalise the svg
|
||||
Shape::normalize(shapes, width, height);
|
||||
} else {
|
||||
// normalise the svg
|
||||
Shape::normalize(shapes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,9 +55,6 @@ std::vector<std::string> SvgParser::preProcessPath(std::string path) {
|
|||
std::regex re2("-(?!e)");
|
||||
std::regex re3("\\s+");
|
||||
std::regex re4("(^\\s|\\s$)");
|
||||
std::regex re5("[^mlhvcsqtazMLHVCSQTAZ\\-.\\d\\s]");
|
||||
std::regex re6("[a-zA-Z.\\-]{2,}");
|
||||
std::regex re7("^[a-zA-Z]");
|
||||
|
||||
path = std::regex_replace(path, re1, " ");
|
||||
// reverse path and use a negative lookahead
|
||||
|
@ -66,14 +63,6 @@ std::vector<std::string> SvgParser::preProcessPath(std::string path) {
|
|||
std::reverse(path.begin(), path.end());
|
||||
path = std::regex_replace(path, re3, " ");
|
||||
path = std::regex_replace(path, re4, "");
|
||||
|
||||
if (std::regex_search(path, re5)) {
|
||||
throw std::invalid_argument("Illegal characters in SVG path.");
|
||||
} else if (std::regex_search(path, re6)) {
|
||||
throw std::invalid_argument("Multiple letters or delimiters found next to one another in SVG path.");
|
||||
} else if (!std::regex_search(path, re7)) {
|
||||
throw std::invalid_argument("Start of SVG path is not a letter.");
|
||||
}
|
||||
|
||||
std::regex commands("(?=[mlhvcsqtazMLHVCSQTAZ])");
|
||||
std::vector<std::string> commandsVector;
|
||||
|
@ -230,10 +219,10 @@ std::vector<std::unique_ptr<Shape>> SvgParser::parsePath(juce::String path) {
|
|||
|
||||
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') {
|
||||
if (commandChar != 'c' && commandChar != 'C' && commandChar != 's' && commandChar != 'S') {
|
||||
state.prevCubicControlPoint = std::nullopt;
|
||||
}
|
||||
if (commandChar == 'q' || commandChar == 'Q' || commandChar == 't' || commandChar == 'T') {
|
||||
if (commandChar != 'q' && commandChar != 'Q' && commandChar != 't' && commandChar != 'T') {
|
||||
state.prevQuadraticControlPoint = std::nullopt;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,6 +45,8 @@
|
|||
<FILE id="CdKZCg" name="Matching.h" compile="0" resource="0" file="Source/chinese_postman/Matching.h"/>
|
||||
</GROUP>
|
||||
<GROUP id="{92CEA658-C82C-9CEB-15EB-945EF6B6B5C8}" name="shape">
|
||||
<FILE id="b9eg7J" name="Arc.cpp" compile="1" resource="0" file="Source/shape/Arc.cpp"/>
|
||||
<FILE id="KIQgS0" name="Arc.h" compile="0" resource="0" file="Source/shape/Arc.h"/>
|
||||
<FILE id="G5fbub" name="Shape.cpp" compile="1" resource="0" file="Source/shape/Shape.cpp"/>
|
||||
<FILE id="Dbvew2" name="CubicBezierCurve.cpp" compile="1" resource="0"
|
||||
file="Source/shape/CubicBezierCurve.cpp"/>
|
||||
|
|
Ładowanie…
Reference in New Issue