This commit is contained in:
2024-02-27 10:10:02 +01:00
parent c0aea669c7
commit 547809c898
131 changed files with 65609 additions and 0 deletions

479
Engine/SVGParser.cpp Normal file
View File

@@ -0,0 +1,479 @@
#include "base.h"
#include <algorithm>
#include <iostream>
#include <fstream>
#include "SVGParser.h"
bool SVGParser::GetVerticesFromSvgFile( const std::string& filePath, std::vector<std::vector<Point2f>> &vertices )
{
// Open the file
std::ifstream svgStream( filePath.c_str( ) );
if ( !svgStream )
{
std::cerr << "SVGParser::GetVerticesFromSvgFile(..), failed to load vertices from file " << filePath << std::endl;
return false;
}
// Read the file
std::string svgLine;
std::string svgString;
while ( !svgStream.eof() )
{
getline( svgStream, svgLine );
svgString += svgLine;
}
// close the file
svgStream.close( );
// Cleanup
RemoveSpaces( svgString );
if ( ! GetVerticesFromSvgString(svgString, vertices))
{
std::cerr << "SVGParser::GetVerticesFromSvgFile(..), malformed or unsupported information in file " << filePath << std::endl;
return false;
}
// Get the viewbox rect to flip the y coordinates. SVG has org topleft, the framework bottom left.
std::string viewBoxValue;
if ( ! GetAttributeValue(svgString, "viewBox", viewBoxValue))
{
std::cerr << "SVGParser::GetVerticesFromSvgFile(..), no viewbox information found in " << filePath << std::endl;
vertices.clear();
return false;
}
Rectf viewBox{};
std::stringstream sstream{ viewBoxValue };
sstream >> viewBox.left >> viewBox.bottom >> viewBox.width >> viewBox.height;
//std::vector<std::vector<Point2f>> vertices{ vertices };
for (size_t i{}; i < vertices.size(); ++i)
{
// flip the y coordinate
for (Point2f& p : vertices[i])
{
p.y = viewBox.height - p.y;
}
}
return true;
}
void SVGParser::RemoveSpaces( std::string& svgString )
{
// Remove spaces before and = chars
size_t foundPos{};
while ( ( foundPos = svgString.find( " =" ) ) != std::string::npos )
{
svgString.replace( foundPos, 2, "=" );
}
// Remove spaces after and = chars
while ( ( foundPos = svgString.find( "= " ) ) != std::string::npos )
{
svgString.replace( foundPos, 2, "=" );
}
//std::cout << svgString.size( ) << "\n";
// Remove spaces before and > chars
while ( ( foundPos = svgString.find( " >" ) ) != std::string::npos )
{
svgString.replace( foundPos, 2, ">" );
}
// Remove spaces after and < chars
while ( ( foundPos = svgString.find( "< " ) ) != std::string::npos )
{
svgString.replace( foundPos, 2, "<" );
}
//std::cout << svgString << "\n";
}
bool SVGParser::GetVerticesFromSvgString(std::string& svgString, std::vector<std::vector<Point2f>> &vertices)
{
size_t startPosContent{};
size_t endPosContent{};
std::string pathElementContent;
// Get path element until none has been found anymore
while (GetElementContent(svgString, "path", pathElementContent, startPosContent, endPosContent))
{
// Vector of Point2f to fill with a path's vertices
std::vector<Point2f> verticesVector;
// Get d attribute value
std::string pathDataValue{};
if (!GetAttributeValue(pathElementContent, " d", pathDataValue))
{
std::cerr << "SVGParser::GetVerticesFromSvgString(..), path element doesn't contain a d-attribute.\n ";
vertices.clear();
return false;
}
// Process the path data
if (!GetVerticesFromPathData(pathDataValue, verticesVector))
{
std::cerr << "SVGParser::GetVerticesFromSvgString(..), error while extracting vertices from the path. \n";
vertices.clear();
return false;
}
if (verticesVector.size() == 0)
{
std::cerr << "Empty verticesVector in GetVerticesFromSvgString(..), no vertices found in the path element" << std::endl;
vertices.clear();
return false;
}
// DEBUG: Read vertices of current vector
//for (Point2f& p : verticesVector)
//{
// std::cout << p.x << " " << p.y << std::endl;
//}
// Add the vector to the vector array
vertices.push_back(verticesVector);
}
if (vertices.size() == 0)
{
std::cerr << "Empty vertices in GetVerticesFromSvgString(..), no path element(s) found" << std::endl;
return false;
}
return true;
}
bool SVGParser::GetVerticesFromPathData( const std::string& pathData, std::vector<Point2f> &vertices )
{
std::string pathCmdChars( ( "mMZzLlHhVvCcSsQqTtAa" ) );
// Use streamstream for parsing
std::stringstream ss( pathData );
char cmd{ 0 };
Point2f cursor{};
Point2f startPoint{};//At the end of the z command, the new current point is set to the initial point of the current subpath.
bool isOpen = true;
// http://www.w3.org/TR/SVG/paths.html#Introduction
Point2f vertex{};
char pathCommand{};
ss >> pathCommand;
while ( !ss.eof( ) )
{
//if ( strchr( pathCmdChars.c_str( ), pathCommand ) != 0 )
// if the command is a valid command letter, proceed
if(pathCmdChars.find(pathCommand) != std::string::npos)
{
cmd = pathCommand;
}
else
{
// if not a command, then put it back
// Attempts to decrease the current location in the stream by one character,
// making the last character extracted from the stream once again available to be extracted by input operation
ss.putback( pathCommand );
}
switch ( cmd )
{
case ( 'Z' ):
case ( 'z' ):
isOpen = true;
break;
case ( 'M' ):
case ( 'm' ):
if ( isOpen )
{
cursor = FirstSvgPoint( ss, cursor, cmd, isOpen, true );
startPoint = cursor;
vertices.push_back( cursor );
isOpen = false;
break;
}
// Fallthrough when isOpen
case ( 'L' )://lineto
case ( 'l' ):
vertex = NextSvgPoint( ss, cursor, cmd, isOpen, true );
vertices.push_back( vertex );
break;
case ( 'h' ): // horizontal lineto
case ( 'H' ):
vertex = NextSvgCoordX( ss, cursor, cmd, isOpen );
vertices.push_back( vertex );
break;
case ( 'v' ): // vertical lineto
case ( 'V' ):
vertex = NextSvgCoordY( ss, cursor, cmd, isOpen );
vertices.push_back( vertex );
break;
case ( 'C' ):
case ( 'c' ):
std::cerr << "SVGParser::GetVerticesFromPathData, beziers are not supported.\nHave another look at the guide, or select all nodes in inkscape and press shift + L\n";
return false;
break;
default:
std::cerr << "SVGParser::GetVerticesFromPathData, " << cmd << " is not a supported SVG command";
return false;
break;
}
// Next command
ss >> pathCommand;
}
return true;
}
bool SVGParser::GetElementContent( const std::string& svgText, const std::string& elementName, std::string& elementContent, size_t& startContentPos, size_t& endContentPos )
{
// 2 possible formats
// <ElementName> content <ElementName/>
// Temporary start and end positions for checking
size_t tempStartPos{ startContentPos };
size_t tempEndPos{ endContentPos };
std::string startElement = "<" + elementName + ">";
std::string endElement = "<" + elementName + "/>";
if ( (tempStartPos = svgText.find( startElement )) != std::string::npos )
{
tempStartPos += startElement.length( );
if ( (tempEndPos = svgText.find( endElement ) ) != std::string::npos )
{
elementContent = svgText.substr(tempStartPos, tempEndPos - tempStartPos);
startContentPos = tempStartPos;
endContentPos = tempEndPos;
return true;
}
else
{
return false;
}
}
// or
// <ElementName content />
tempStartPos = startContentPos;
tempEndPos = endContentPos;
startElement = "<" + elementName;
endElement = "/>";
if ( (tempStartPos = svgText.find( startElement, tempStartPos) ) != std::string::npos )
{
tempStartPos += startElement.length( );
if ( (tempEndPos = svgText.find( endElement ) ) != std::string::npos )
{
elementContent = svgText.substr(tempStartPos, tempEndPos - tempStartPos);
startContentPos = tempStartPos;
endContentPos = tempEndPos;
return true;
}
}
return false;
}
bool SVGParser::GetAttributeValue( const std::string& svgText, const std::string& attributeName, std::string& attributeValue )
{
std::string searchAttributeName{ attributeName + "="};
size_t attributePos = svgText.find( searchAttributeName );
if( attributePos == std::string::npos )
{
return false;
}
size_t openingDoubleQuotePos{ svgText.find( "\"", attributePos ) };
if ( openingDoubleQuotePos == std::string::npos )
{
return false;
}
size_t closingDoubleQuotePos{ svgText.find( "\"", openingDoubleQuotePos + 1) };
if ( closingDoubleQuotePos == std::string::npos )
{
return false;
}
attributeValue = svgText.substr( openingDoubleQuotePos + 1, closingDoubleQuotePos - openingDoubleQuotePos - 1);
//std::cout << attributeName << ":" << attributeValue << "\n";
return true;
}
// Skips any optional commas in the stream
// SVG has a really funky format,
// not sure this code works for all cases.
// TODO: Test cases!
void SVGParser::SkipSvgComma( std::stringstream& svgStream, bool isRequired )
{
while ( true )
{
char c = char(svgStream.get( ));
if ( svgStream.eof( ) )
{
if ( isRequired )
{
std::cerr << "SVGParser::SkipSvgComma, expected comma or whitespace\n";
}
break;
}
if ( c == ( ',' ) )
return;
if ( !isspace( c ) )
{
svgStream.unget( );
return;
}
}
}
float SVGParser::ReadSvgValue( std::stringstream& svgStream, float defaultValue )
{
float s{};
svgStream >> s;
if ( svgStream.eof( ) )
{
s = defaultValue;
}
else
{
SkipSvgComma( svgStream, false );
}
return s;
}
float SVGParser::ReadSvgValue( std::stringstream& svgStream, bool separatorRequired )
{
float s{};
svgStream >> s;
SkipSvgComma( svgStream, separatorRequired );
return s;
}
// Reads a single point
Point2f SVGParser::ReadSvgPoint( std::stringstream& svgStream )
{
//std::cout << "ReadSvgPoint: " << svgStream.str() << "\n";
Point2f p{};
p.x = ReadSvgValue( svgStream, true );
p.y = ReadSvgValue( svgStream, false );
return p;
}
Point2f SVGParser::FirstSvgPoint( std::stringstream& svgStream, Point2f& cursor, char cmd, bool isOpen, bool advance )
{
if ( !isOpen )
{
std::cerr << "SVGParser::FirstSvgPoint, expected 'Z' or 'z' command";
}
Point2f p = ReadSvgPoint( svgStream );
if ( islower( cmd ) )
{
// Relative point
p.x = cursor.x + p.x;
p.y = cursor.y + p.y;
}
if ( advance )
{
cursor = p;
}
return p;
}
// Read the next point,
// taking into account relative and absolute positioning.
// Advances the cursor if requested.
// Throws an exception if the figure is not open
Point2f SVGParser::NextSvgPoint( std::stringstream& svgStream, Point2f& cursor, char cmd, bool isOpen, bool advance )
{
if ( isOpen )
{
std::cerr << "SVGParser::NextSvgPoint, expected 'M' or 'm' command\n";
}
Point2f p = ReadSvgPoint( svgStream );
if ( islower( cmd ) )
{
// Relative point
p.x = cursor.x + p.x;
p.y = cursor.y + p.y;
}
if ( advance )
{
cursor = p;
}
return p;
}
// Reads next point, given only the new x coordinate
Point2f SVGParser::NextSvgCoordX( std::stringstream& svgStream, Point2f& cursor, char cmd, bool isOpen )
{
if ( isOpen )
{
std::cerr << "SVGParser::NextSvgCoordX, expected 'M' or 'm' command\n";
}
float c;
svgStream >> c;
if ( islower( cmd ) )
{
// Relative point
cursor.x += c;
}
else
{
cursor.x = c;
}
return cursor;
}
// Reads next point, given only the new y coordinate
Point2f SVGParser::NextSvgCoordY( std::stringstream& svgStream, Point2f& cursor, char cmd, bool isOpen )
{
if ( isOpen )
{
std::cerr << "SVGParser::NextSvgCoordY, expected 'M' or 'm' command\n";
}
float c{};
svgStream >> c;
if ( islower( cmd ) )
{
// Relative point
cursor.y += c;
}
else
{
cursor.y = c;
}
return cursor;
}