Add path rendering.

This commit is contained in:
jmsgrogan 2023-01-19 14:25:58 +00:00
parent f2ab532005
commit 97afa782a0
39 changed files with 1148 additions and 131 deletions

View file

@ -14,6 +14,7 @@ list(APPEND HEADERS
path/Line.h
path/LineSegment.h
path/Path.h
path/PathPostScriptConverter.h
path/PathElement.h
points/Point.h
points/PointCollection.h
@ -34,6 +35,7 @@ list(APPEND SOURCES
path/Line.cpp
path/LineSegment.cpp
path/Path.cpp
path/PathPostScriptConverter.cpp
path/PathElement.cpp
points/Point.cpp
points/PointCollection.cpp

View file

@ -7,6 +7,81 @@ Line::Line(const Point& start, const PointCollection& points)
}
Line::Line(const Point& start, InputBufferType bufferType, const std::vector<double>& buffer)
: mStartPoint(start)
{
if (bufferType == InputBufferType::XY_REL)
{
for (std::size_t idx = 0; idx < buffer.size(); idx += 2)
{
const auto x = buffer[idx];
const auto y = buffer[idx + 1];
mPoints.addPoint(Point(mStartPoint.getX() + x, mStartPoint.getY() + y));
}
}
else if (bufferType == InputBufferType::XY_ABS)
{
for (std::size_t idx = 0; idx < buffer.size(); idx += 2)
{
const auto x = buffer[idx];
const auto y = buffer[idx + 1];
mPoints.addPoint(Point(x, y));
}
}
else if (bufferType == InputBufferType::HORIZONTAL_REL)
{
for (std::size_t idx = 0; idx < buffer.size(); idx ++)
{
const auto x = buffer[idx];
mPoints.addPoint(Point(mStartPoint.getX() + x, mStartPoint.getY()));
}
}
else if (bufferType == InputBufferType::HORIZONTAL_ABS)
{
for (std::size_t idx = 0; idx < buffer.size(); idx++)
{
const auto x = buffer[idx];
mPoints.addPoint(Point(x, mStartPoint.getY()));
}
}
else if (bufferType == InputBufferType::VERTICAL_REL)
{
for (std::size_t idx = 0; idx < buffer.size(); idx++)
{
const auto y = buffer[idx];
mPoints.addPoint(Point(mStartPoint.getX(), mStartPoint.getY() + y));
}
}
else if (bufferType == InputBufferType::VERTICAL_ABS)
{
for (std::size_t idx = 0; idx < buffer.size(); idx++)
{
const auto y = buffer[idx];
mPoints.addPoint(Point(mStartPoint.getX(), y));
}
}
}
std::string Line::toPostScriptString() const
{
std::string path = "L ";
for (const auto& point : mPoints.getPoints())
{
path += std::to_string(point.getX()) + " " + std::to_string(point.getY()) + " ";
}
return path;
}
Point Line::getFirstPoint() const
{
return getLocation();
}
Point Line::getEndPoint() const
{
return mPoints.getEndPoint();
}
const PointCollection& Line::getPoints() const
{
return mPoints;

View file

@ -4,12 +4,25 @@
#include "PointCollection.h"
#include <vector>
#include <string>
class Line : public PathElement
{
public:
enum class InputBufferType
{
HORIZONTAL_REL,
HORIZONTAL_ABS,
VERTICAL_REL,
VERTICAL_ABS,
XY_REL,
XY_ABS
};
Line(const Point& start, const PointCollection& points);
Line(const Point& start, InputBufferType bufferType, const std::vector<double>& buffer);
const PointCollection& getPoints() const;
Line::Type getType() const override;
@ -18,8 +31,14 @@ public:
Bounds getBounds() const override;
Point getFirstPoint() const override;
Point getEndPoint() const override;
void sample(SparseGrid<bool>* grid) const override {};
std::string toPostScriptString() const override;
private:
Point mStartPoint;
PointCollection mPoints;

View file

@ -32,6 +32,11 @@ void LineSegment::sample(SparseGrid<bool>* grid) const
}
std::string LineSegment::toPostScriptString() const
{
return "L " + std::to_string(mP1.getX()) + " " + std::to_string(mP1.getY());
}
Bounds LineSegment::getBounds() const
{
const auto minX = std::min(mP0.getX(), mP1.getX());
@ -47,3 +52,18 @@ const Point& LineSegment::getLocation() const
{
return mP0;
}
Point LineSegment::getFirstPoint() const
{
return getPoint0();
}
Point LineSegment::getEndPoint() const
{
return getPoint1();
}
LineSegment::Type LineSegment::getType() const
{
return AbstractGeometricItem::Type::LINE_SEGMENT;
}

View file

@ -2,6 +2,8 @@
#include "PathElement.h"
#include <string>
class LineSegment : public PathElement
{
public:
@ -21,6 +23,14 @@ public:
const Point& getLocation() const override;
Point getFirstPoint() const override;
Point getEndPoint() const override;
std::string toPostScriptString() const override;
Type getType() const override;
private:
Point mP0;
Point mP1;

View file

@ -1,11 +1,55 @@
#include "Path.h"
Path::~Path()
#include "StringUtils.h"
#include "FileLogger.h"
#include "Line.h"
#include "LineSegment.h"
#include "PathPostScriptConverter.h"
GeometryPath::~GeometryPath()
{
}
const std::vector<PathElementPtr>& Path::getElements() const
const std::vector<GeometryPathFeaturePtr>& GeometryPath::getFeatures() const
{
return mElements;
return mFeatures;
}
void GeometryPath::buildFromPostscript(const std::string& psString)
{
PathPostScriptConverter converter;
converter.fromPostScript(this, psString);
}
void GeometryPath::addFeature(GeometryPathFeaturePtr feature)
{
mFeatures.push_back(std::move(feature));
}
const Point& GeometryPath::getLocation() const
{
return mLocation;
}
Bounds GeometryPath::getBounds() const
{
return {};
}
GeometryPath::Type GeometryPath::getType() const
{
return GeometryPath::Type::PATH;
}
void GeometryPath::sample(SparseGrid<bool>* grid) const
{
}
std::string GeometryPath::getAsPostScript() const
{
PathPostScriptConverter converter;
return converter.toPostScript(this);
}

View file

@ -4,15 +4,60 @@
#include <vector>
#include <memory>
#include <string>
using PathElementPtr = std::unique_ptr<PathElement>;
class Path : public AbstractGeometricItem
class GeometryPathFeature
{
public:
~Path();
const std::vector<PathElementPtr>& getElements() const;
void addElement(PathElementPtr element)
{
if (mElements.empty())
{
mLocation = element->getFirstPoint();
}
mElements.push_back(std::move(element));
}
const Point& getLocation() const
{
return mLocation;
}
const std::vector<PathElementPtr>& getElements() const
{
return mElements;
}
private:
Point mLocation;
std::vector<PathElementPtr> mElements;
};
using GeometryPathFeaturePtr = std::unique_ptr<GeometryPathFeature>;
class GeometryPath : public AbstractGeometricItem
{
public:
~GeometryPath();
void addFeature(GeometryPathFeaturePtr feature);
void buildFromPostscript(const std::string& psString);
std::string getAsPostScript() const;
const Point& getLocation() const override;
Bounds getBounds() const override;
Type getType() const override;
const std::vector<GeometryPathFeaturePtr>& getFeatures() const;
void sample(SparseGrid<bool>* grid) const override;
private:
Point mLocation;
std::vector<GeometryPathFeaturePtr> mFeatures;
};

View file

@ -2,8 +2,16 @@
#include "AbstractGeometricItem.h"
#include <string>
class PathElement : public AbstractGeometricItem
{
public:
~PathElement();
virtual Point getFirstPoint() const = 0;
virtual Point getEndPoint() const = 0;
virtual std::string toPostScriptString() const = 0;
};

View file

@ -0,0 +1,235 @@
#include "PathPostScriptConverter.h"
#include "Path.h"
#include "StringUtils.h"
#include "FileLogger.h"
#include "Line.h"
#include "LineSegment.h"
void PathPostScriptConverter::fromPostScript(GeometryPath* targetPath, const std::string& postScriptPath)
{
mCurrentPoint = Point();
for (auto c : postScriptPath)
{
if (c == 'M' || c == 'm')
{
mLineState = LineState::IN_FIRST_POINT;
mPositionState = getPositionState(c);
mWorkingFeature = std::make_unique<GeometryPathFeature>();
}
else if (c == 'H' || c == 'h')
{
onNewElement(c);
mLineState = LineState::IN_HORIZONTAL;
}
else if (c == 'V' || c == 'v')
{
onNewElement(c);
mLineState = LineState::IN_VERTICAL;
}
else if (c == 'L' || c == 'l')
{
onNewElement(c);
mLineState = LineState::IN_LINE;
}
else if (c == 'C' || c == 'c')
{
onNewElement(c);
mLineState = LineState::IN_CUBIC_BEZIER;
}
else if (c == 'Q' || c == 'q')
{
onNewElement(c);
mLineState = LineState::IN_QUADRATIC_BEZIER;
}
else if (c == 'A' || c == 'a')
{
onNewElement(c);
mLineState = LineState::IN_ARC;
}
else if (c == 'Z' || c == 'z')
{
onNewElement(c);
mLineState = LineState::START;
targetPath->addFeature(std::move(mWorkingFeature));
mWorkingFeature = nullptr;
}
else if (std::isblank(c))
{
onNonNumeric();
}
else if (std::isalpha(c))
{
onNewElement(c);
mLineState = LineState::IN_UNSUPPORTED;
}
else
{
mBuffer.push_back(c);
}
}
}
void PathPostScriptConverter::onNewElement(char c)
{
onNonNumeric();
onElementEnd();
mPositionState = getPositionState(c);
}
void PathPostScriptConverter::onNonNumeric()
{
if (!mBuffer.empty())
{
mPointBuffer.push_back(std::stod(mBuffer));
mBuffer.clear();
}
}
PathPostScriptConverter::PositionState PathPostScriptConverter::getPositionState(char c) const
{
return std::isupper(c) ? PositionState::ABSOLUTE : PositionState::RELATIVE;
}
void PathPostScriptConverter::onElementEnd()
{
if (mPointBuffer.empty())
{
return;
}
PathElementPtr element;
if (mLineState == LineState::IN_HORIZONTAL)
{
element = onHorizontalLineTo();
}
else if (mLineState == LineState::IN_VERTICAL)
{
element = onVerticalLineTo();
}
else if (mLineState == LineState::IN_LINE)
{
element = onLineTo();
}
else if (mLineState == LineState::IN_FIRST_POINT)
{
onMoveTo();
}
if (element)
{
mCurrentPoint = element->getEndPoint();
mWorkingFeature->addElement(std::move(element));
}
mPointBuffer.clear();
}
void PathPostScriptConverter::onMoveTo()
{
if (mPointBuffer.size() != 2)
{
return;
}
if (mPositionState == PositionState::RELATIVE)
{
mCurrentPoint = Point(mCurrentPoint.getX() + mPointBuffer[0], mCurrentPoint.getY() + mPointBuffer[1]);
}
else
{
mCurrentPoint = Point(mPointBuffer[0], mPointBuffer[1]);
}
}
PathElementPtr PathPostScriptConverter::onHorizontalLineTo()
{
PathElementPtr element;
if (mPointBuffer.size() == 1)
{
if (mPositionState == PositionState::RELATIVE)
{
element = std::make_unique<LineSegment>(mCurrentPoint, Point(mCurrentPoint.getX() + mPointBuffer[0], mCurrentPoint.getY()));
}
else
{
element = std::make_unique<LineSegment>(mCurrentPoint, Point(mPointBuffer[0], mCurrentPoint.getY()));
}
}
else
{
Line::InputBufferType buffer_type = mPositionState == PositionState::RELATIVE ? Line::InputBufferType::HORIZONTAL_REL : Line::InputBufferType::HORIZONTAL_ABS;
element = std::make_unique<Line>(mCurrentPoint, buffer_type, mPointBuffer);
}
return element;
}
PathElementPtr PathPostScriptConverter::onVerticalLineTo()
{
PathElementPtr element;
if (mPointBuffer.size() == 1)
{
if (mPositionState == PositionState::RELATIVE)
{
element = std::make_unique<LineSegment>(mCurrentPoint, Point(mCurrentPoint.getX(), mCurrentPoint.getY() + mPointBuffer[0]));
}
else
{
element = std::make_unique<LineSegment>(mCurrentPoint, Point(mCurrentPoint.getX(), mPointBuffer[0]));
}
}
else
{
Line::InputBufferType buffer_type = mPositionState == PositionState::RELATIVE ? Line::InputBufferType::VERTICAL_REL : Line::InputBufferType::VERTICAL_ABS;
element = std::make_unique<Line>(mCurrentPoint, buffer_type, mPointBuffer);
}
return element;
}
PathElementPtr PathPostScriptConverter::onLineTo()
{
PathElementPtr element;
if (mPointBuffer.size() == 2)
{
if (mPositionState == PositionState::RELATIVE)
{
const auto next_point = Point(mCurrentPoint.getX() + mPointBuffer[0], mCurrentPoint.getY() + mPointBuffer[1]);
element = std::make_unique<LineSegment>(mCurrentPoint, next_point);
}
else
{
const auto next_point = Point(mPointBuffer[0], mPointBuffer[1]);
element = std::make_unique<LineSegment>(mCurrentPoint, next_point);
}
}
else
{
Line::InputBufferType buffer_type = mPositionState == PositionState::RELATIVE ? Line::InputBufferType::XY_REL : Line::InputBufferType::XY_ABS;
element = std::make_unique<Line>(mCurrentPoint, buffer_type, mPointBuffer);
}
return element;
}
std::string PathPostScriptConverter::toPostScript(const GeometryPath* targetPath)
{
std::string path;
for (const auto& feature : targetPath->getFeatures())
{
auto start_loc = feature->getLocation();
path += "M " + std::to_string(start_loc.getX()) + " " + std::to_string(start_loc.getY());
for (const auto& path_element : feature->getElements())
{
path += " " + path_element->toPostScriptString();
}
path += "Z ";
}
return path;
}

View file

@ -0,0 +1,61 @@
#pragma once
#include "PathElement.h"
#include "Point.h"
#include <memory>
#include <vector>
#include <string>
class GeometryPath;
using PathElementPtr = std::unique_ptr<PathElement>;
class GeometryPathFeature;
using GeometryPathFeaturePtr = std::unique_ptr<GeometryPathFeature>;
class PathPostScriptConverter
{
public:
void fromPostScript(GeometryPath* targetPath, const std::string& postScriptPath);
std::string toPostScript(const GeometryPath* targetPath);
private:
enum class LineState
{
START,
IN_FIRST_POINT,
IN_HORIZONTAL,
IN_VERTICAL,
IN_LINE,
IN_CUBIC_BEZIER,
IN_QUADRATIC_BEZIER,
IN_ARC,
IN_UNSUPPORTED
};
enum class PositionState
{
ABSOLUTE,
RELATIVE
};
PositionState getPositionState(char c) const;
void onNewElement(char c);
void onNonNumeric();
void onElementEnd();
void onMoveTo();
PathElementPtr onHorizontalLineTo();
PathElementPtr onVerticalLineTo();
PathElementPtr onLineTo();
LineState mLineState{ LineState::START };
PositionState mPositionState{ PositionState::ABSOLUTE };
std::string mBuffer;
std::vector<double> mPointBuffer;
GeometryPathFeaturePtr mWorkingFeature;
Point mCurrentPoint;
};

View file

@ -14,6 +14,11 @@ void PointCollection::apply(const Transform& transform)
}
}
void PointCollection::addPoint(const Point& point)
{
mPoints.push_back(point);
}
Bounds PointCollection::getBounds() const
{
Bounds bounds{0.0, 0.0, 0.0, 0.0};
@ -29,4 +34,21 @@ Bounds PointCollection::getBounds() const
bounds.includePoint(point.getX(), point.getY(), point.getZ());
}
return bounds;
}
Point PointCollection::getEndPoint() const
{
if (mPoints.empty())
{
return {};
}
else
{
return mPoints[mPoints.size() - 1];
}
}
const std::vector<Point>& PointCollection::getPoints() const
{
return mPoints;
}

View file

@ -9,10 +9,16 @@ class PointCollection
public:
PointCollection(const std::vector<Point> points = {});
void addPoint(const Point& point);
void apply(const Transform& transform);
Bounds getBounds() const;
Point getEndPoint() const;
const std::vector<Point>& getPoints() const;
private:
std::vector<Point> mPoints;
};