kopia lustrzana https://github.com/jameshball/osci-render
Add code for SvgParser that mostly works
rodzic
60b80b7812
commit
a063f2f988
|
@ -3,7 +3,7 @@
|
|||
Graph::Graph(int n, const list< pair<int, int> > & edges):
|
||||
n(n),
|
||||
m(edges.size()),
|
||||
adjMat(n, vector<bool>(n, false)),
|
||||
adjMat(n * n),
|
||||
adjList(n),
|
||||
edges(),
|
||||
edgeIndex(n, vector<int>(n, -1))
|
||||
|
@ -27,24 +27,11 @@ int Graph::GetEdgeIndex(int u, int v) const
|
|||
return edgeIndex[u][v];
|
||||
}
|
||||
|
||||
void Graph::AddVertex()
|
||||
{
|
||||
for(int i = 0; i < n; i++)
|
||||
{
|
||||
adjMat[i].push_back(false);
|
||||
edgeIndex[i].push_back(-1);
|
||||
}
|
||||
n++;
|
||||
adjMat.push_back( vector<bool>(n, false) );
|
||||
edgeIndex.push_back( vector<int>(n, -1) );
|
||||
adjList.push_back( vector<int>() );
|
||||
}
|
||||
|
||||
void Graph::AddEdge(int u, int v)
|
||||
{
|
||||
if(adjMat[u][v]) return;
|
||||
if(adjMat[u * n + v]) return;
|
||||
|
||||
adjMat[u][v] = adjMat[v][u] = true;
|
||||
adjMat[u * n + v] = adjMat[v * n + u] = true;
|
||||
adjList[u].push_back(v);
|
||||
adjList[v].push_back(u);
|
||||
|
||||
|
@ -60,7 +47,7 @@ const vector<int>& Graph::AdjList(int v) const
|
|||
return adjList[v];
|
||||
}
|
||||
|
||||
const vector< vector<bool> > & Graph::AdjMat() const
|
||||
const vector<bool> & Graph::AdjMat() const
|
||||
{
|
||||
return adjMat;
|
||||
}
|
||||
|
|
|
@ -24,8 +24,6 @@ public:
|
|||
//Given the endpoints, returns the index
|
||||
int GetEdgeIndex(int u, int v) const;
|
||||
|
||||
//Adds a new vertex to the graph
|
||||
void AddVertex();
|
||||
//Adds a new edge to the graph
|
||||
void AddEdge(int u, int v);
|
||||
|
||||
|
@ -33,7 +31,7 @@ public:
|
|||
const vector<int>& AdjList(int v) const;
|
||||
|
||||
//Returns the graph's adjacency matrix
|
||||
const vector< vector<bool> > & AdjMat() const;
|
||||
const vector<bool>& AdjMat() const;
|
||||
private:
|
||||
//Number of vertices
|
||||
int n;
|
||||
|
@ -41,7 +39,7 @@ private:
|
|||
int m;
|
||||
|
||||
//Adjacency matrix
|
||||
vector< vector<bool> > adjMat;
|
||||
vector<bool> adjMat;
|
||||
|
||||
//Adjacency lists
|
||||
vector<vector<int> > adjList;
|
||||
|
|
|
@ -100,7 +100,7 @@ void Matching::Grow()
|
|||
|
||||
bool Matching::IsAdjacent(int u, int v)
|
||||
{
|
||||
return (G.AdjMat()[u][v] and not IsEdgeBlocked(u, v));
|
||||
return (G.AdjMat()[u * G.GetNumVertices() + v] and not IsEdgeBlocked(u, v));
|
||||
}
|
||||
|
||||
bool Matching::IsEdgeBlocked(int u, int v)
|
||||
|
|
|
@ -4,20 +4,28 @@
|
|||
FileParser::FileParser() {}
|
||||
|
||||
void FileParser::parse(juce::String extension, std::unique_ptr<juce::InputStream> stream) {
|
||||
object = nullptr;
|
||||
camera = nullptr;
|
||||
svg = nullptr;
|
||||
|
||||
if (extension == ".obj") {
|
||||
object = std::make_unique<WorldObject>(stream->readEntireStreamAsString().toStdString());
|
||||
camera = std::make_unique<Camera>(1.0, 0, 0, 0.0);
|
||||
camera->findZPos(*object);
|
||||
} else if (extension == ".svg") {
|
||||
svg = std::make_unique<SvgParser>(stream->readEntireStreamAsString());
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<Shape>> FileParser::next() {
|
||||
if (object != nullptr && camera != nullptr) {
|
||||
return camera->draw(*object);
|
||||
} else if (svg != nullptr) {
|
||||
return svg->draw();
|
||||
}
|
||||
auto shapes = std::vector<std::unique_ptr<Shape>>();
|
||||
shapes.push_back(std::make_unique<Line>(0.0, 0.0, 1.0, 1.0));
|
||||
return shapes;
|
||||
auto tempShapes = std::vector<std::unique_ptr<Shape>>();
|
||||
tempShapes.push_back(std::make_unique<Line>(0.0, 0.0, 1.0, 1.0));
|
||||
return tempShapes;
|
||||
}
|
||||
|
||||
bool FileParser::isActive() {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "../shape/Shape.h"
|
||||
#include "../obj/WorldObject.h"
|
||||
#include "../obj/Camera.h"
|
||||
#include "../svg/SvgParser.h"
|
||||
|
||||
class FileParser : public FrameSource {
|
||||
public:
|
||||
|
@ -20,4 +21,5 @@ private:
|
|||
|
||||
std::unique_ptr<WorldObject> object;
|
||||
std::unique_ptr<Camera> camera;
|
||||
std::unique_ptr<SvgParser> svg;
|
||||
};
|
|
@ -69,3 +69,7 @@ double CubicBezierCurve::length() {
|
|||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
std::unique_ptr<Shape> CubicBezierCurve::clone() {
|
||||
return std::make_unique<CubicBezierCurve>(x1, y1, x2, y2, x3, y3, x4, y4);
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ public:
|
|||
void scale(double x, double y) override;
|
||||
void translate(double x, double y) override;
|
||||
double length() override;
|
||||
std::unique_ptr<Shape> clone() override;
|
||||
|
||||
double x1, y1, x2, y2, x3, y3, x4, y4;
|
||||
|
||||
|
|
|
@ -48,3 +48,7 @@ double inline Line::length() {
|
|||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
std::unique_ptr<Shape> Line::clone() {
|
||||
return std::make_unique<Line>(x1, y1, x2, y2);
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ public:
|
|||
void scale(double x, double y) override;
|
||||
void translate(double x, double y) override;
|
||||
double length() override;
|
||||
std::unique_ptr<Shape> clone() override;
|
||||
|
||||
double x1, y1, x2, y2;
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ public:
|
|||
virtual void scale(double x, double y) = 0;
|
||||
virtual void translate(double x, double y) = 0;
|
||||
virtual double length() = 0;
|
||||
virtual std::unique_ptr<Shape> clone() = 0;
|
||||
|
||||
static double totalLength(std::vector<std::unique_ptr<Shape>>&);
|
||||
|
||||
|
|
|
@ -1 +1,243 @@
|
|||
#include "SvgState.h"
|
||||
#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.child("svg");
|
||||
|
||||
shapes = std::vector<std::unique_ptr<Shape>>();
|
||||
|
||||
for (pugi::xml_node path : svg.children("path")) {
|
||||
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;
|
||||
|
||||
// normalise the svg
|
||||
} else {
|
||||
// normalise the svg
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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$)");
|
||||
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
|
||||
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, "");
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,22 @@
|
|||
#pragma once
|
||||
#include "../shape/Vector2.h"
|
||||
#include <JuceHeader.h>
|
||||
#include "../shape/Shape.h"
|
||||
#include "SvgState.h"
|
||||
#include "../xml/pugixml.hpp"
|
||||
|
||||
struct {
|
||||
Vector2 currPoint;
|
||||
Vector2 initialPoint;
|
||||
Vector2 prevCubicControlPoint;
|
||||
Vector2 prevQuadraticControlPoint;
|
||||
class SvgParser {
|
||||
public:
|
||||
SvgParser(juce::String svgFile);
|
||||
~SvgParser();
|
||||
|
||||
std::vector<std::unique_ptr<Shape>> draw();
|
||||
private:
|
||||
std::vector<std::string> preProcessPath(std::string path);
|
||||
juce::String simplifyLength(juce::String length);
|
||||
std::pair<double, double> getDimensions(pugi::xml_node&);
|
||||
std::vector<std::unique_ptr<Shape>> parsePath(juce::String);
|
||||
|
||||
std::vector<std::unique_ptr<Shape>> shapes;
|
||||
SvgState state = SvgState();
|
||||
};
|
|
@ -0,0 +1,77 @@
|
|||
/**
|
||||
* pugixml parser - version 1.13
|
||||
* --------------------------------------------------------
|
||||
* Copyright (C) 2006-2022, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)
|
||||
* Report bugs and download new versions at https://pugixml.org/
|
||||
*
|
||||
* This library is distributed under the MIT License. See notice at the end
|
||||
* of this file.
|
||||
*
|
||||
* This work is based on the pugxml parser, which is:
|
||||
* Copyright (C) 2003, by Kristen Wegner (kristen@tima.net)
|
||||
*/
|
||||
|
||||
#ifndef HEADER_PUGICONFIG_HPP
|
||||
#define HEADER_PUGICONFIG_HPP
|
||||
|
||||
// Uncomment this to enable wchar_t mode
|
||||
// #define PUGIXML_WCHAR_MODE
|
||||
|
||||
// Uncomment this to enable compact mode
|
||||
// #define PUGIXML_COMPACT
|
||||
|
||||
// Uncomment this to disable XPath
|
||||
// #define PUGIXML_NO_XPATH
|
||||
|
||||
// Uncomment this to disable STL
|
||||
// #define PUGIXML_NO_STL
|
||||
|
||||
// Uncomment this to disable exceptions
|
||||
// #define PUGIXML_NO_EXCEPTIONS
|
||||
|
||||
// Set this to control attributes for public classes/functions, i.e.:
|
||||
// #define PUGIXML_API __declspec(dllexport) // to export all public symbols from DLL
|
||||
// #define PUGIXML_CLASS __declspec(dllimport) // to import all classes from DLL
|
||||
// #define PUGIXML_FUNCTION __fastcall // to set calling conventions to all public functions to fastcall
|
||||
// In absence of PUGIXML_CLASS/PUGIXML_FUNCTION definitions PUGIXML_API is used instead
|
||||
|
||||
// Tune these constants to adjust memory-related behavior
|
||||
// #define PUGIXML_MEMORY_PAGE_SIZE 32768
|
||||
// #define PUGIXML_MEMORY_OUTPUT_STACK 10240
|
||||
// #define PUGIXML_MEMORY_XPATH_PAGE_SIZE 4096
|
||||
|
||||
// Tune this constant to adjust max nesting for XPath queries
|
||||
// #define PUGIXML_XPATH_DEPTH_LIMIT 1024
|
||||
|
||||
// Uncomment this to switch to header-only version
|
||||
// #define PUGIXML_HEADER_ONLY
|
||||
|
||||
// Uncomment this to enable long long support
|
||||
// #define PUGIXML_HAS_LONG_LONG
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Copyright (c) 2006-2022 Arseny Kapoulkine
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
Plik diff jest za duży
Load Diff
Plik diff jest za duży
Load Diff
|
@ -7,6 +7,11 @@
|
|||
cppLanguageStandard="20" projectLineFeed=" " headerPath="./include">
|
||||
<MAINGROUP id="j5Ge2T" name="osci-render">
|
||||
<GROUP id="{75439074-E50C-362F-1EDF-8B4BE9011259}" name="Source">
|
||||
<GROUP id="{022CB910-9A16-C4AE-4C3B-9CB57BE87FC2}" name="xml">
|
||||
<FILE id="pW7WRh" name="pugiconfig.hpp" compile="0" resource="0" file="Source/xml/pugiconfig.hpp"/>
|
||||
<FILE id="CnkgyF" name="pugixml.cpp" compile="1" resource="0" file="Source/xml/pugixml.cpp"/>
|
||||
<FILE id="SrvH3B" name="pugixml.hpp" compile="0" resource="0" file="Source/xml/pugixml.hpp"/>
|
||||
</GROUP>
|
||||
<GROUP id="{56A27063-1FE7-31C3-8263-98389240A8CB}" name="svg">
|
||||
<FILE id="FVWkba" name="ClosePath.cpp" compile="1" resource="0" file="Source/svg/ClosePath.cpp"/>
|
||||
<FILE id="pJz3ek" name="ClosePath.h" compile="0" resource="0" file="Source/svg/ClosePath.h"/>
|
||||
|
|
Ładowanie…
Reference in New Issue