aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/String.cpp4
-rw-r--r--src/platform/graphics/Geometry.cpp212
2 files changed, 215 insertions, 1 deletions
diff --git a/src/common/String.cpp b/src/common/String.cpp
index 22008f6d..1abee273 100644
--- a/src/common/String.cpp
+++ b/src/common/String.cpp
@@ -1,4 +1,5 @@
#include "cru/common/String.h"
+#include <double-conversion/double-conversion.h>
#include "cru/common/Exception.h"
#include "cru/common/StringUtil.h"
@@ -349,7 +350,8 @@ double_conversion::StringToDoubleConverter
double_conversion::StringToDoubleConverter::ALLOW_LEADING_SPACES |
double_conversion::StringToDoubleConverter::ALLOW_TRAILING_SPACES |
double_conversion::StringToDoubleConverter::
- ALLOW_CASE_INSENSIBILITY,
+ ALLOW_CASE_INSENSIBILITY |
+ double_conversion::StringToDoubleConverter::ALLOW_TRAILING_JUNK,
0.0, NAN, "infinity", "nan");
int StringView::Compare(const StringView& other) const {
diff --git a/src/platform/graphics/Geometry.cpp b/src/platform/graphics/Geometry.cpp
index a9a93bf4..d4d36eb4 100644
--- a/src/platform/graphics/Geometry.cpp
+++ b/src/platform/graphics/Geometry.cpp
@@ -1,6 +1,8 @@
#include "cru/platform/graphics/Geometry.h"
#include <cmath>
+#include <unordered_set>
+#include "cru/common/Exception.h"
namespace cru::platform::graphics {
constexpr float PI = 3.14159265358979323846f;
@@ -153,4 +155,214 @@ void IGeometryBuilder::ArcTo(const Point& radius, float angle,
end_point.x, end_point.y, current_position.x, current_position.y);
}
+namespace {
+const std::unordered_set<char16_t> kSvgPathDataCommands = {
+ 'M', 'm', 'L', 'l', 'H', 'h', 'V', 'v', 'C', 'c',
+ 'S', 's', 'Q', 'q', 'T', 't', 'A', 'a', 'Z', 'z'};
+}
+
+void IGeometryBuilder::ParseAndApplySvgPathData(StringView path_d) {
+ Index position = 0;
+ const auto size = path_d.size();
+
+ char16_t last_command = 0;
+ bool last_is_cubic = false;
+ bool last_is_quad = false;
+ Point last_end_point;
+ Point last_control_point;
+
+ auto read_spaces = [&] {
+ while (position < size &&
+ (path_d[position] == ' ' || path_d[position] == '\n')) {
+ ++position;
+ }
+ return position == size;
+ };
+
+ auto read_number = [&] {
+ if (read_spaces()) {
+ throw Exception(u"Unexpected eof of svg path data command.");
+ }
+
+ if (path_d[position] == ',') {
+ ++position;
+ }
+
+ Index processed_count = 0;
+
+ auto result = path_d.substr(position).ParseToFloat(&processed_count);
+
+ if (std::isnan(result)) throw Exception(u"Invalid svg path data number.");
+
+ position += processed_count;
+
+ return result;
+ };
+
+ auto read_point = [&] {
+ auto x = read_number();
+ auto y = read_number();
+ return Point(x, y);
+ };
+
+ auto do_command = [&, this](char16_t command) {
+ last_command = command;
+ last_is_cubic = false;
+ last_is_quad = false;
+
+ switch (command) {
+ case 'M':
+ MoveTo(read_point());
+ break;
+ case 'm':
+ RelativeMoveTo(read_point());
+ break;
+ case 'L':
+ LineTo(read_point());
+ break;
+ case 'l':
+ RelativeLineTo(read_point());
+ break;
+ case 'H':
+ LineTo(read_number(), this->GetCurrentPosition().y);
+ break;
+ case 'h':
+ RelativeLineTo(read_number(), 0);
+ break;
+ case 'V':
+ LineTo(GetCurrentPosition().x, read_number());
+ break;
+ case 'v':
+ RelativeLineTo(0, read_number());
+ break;
+ case 'C': {
+ auto start_control_point = read_point(),
+ end_control_point = read_point(), end_point = read_point();
+ CubicBezierTo(start_control_point, end_control_point, end_point);
+ last_is_cubic = true;
+ last_control_point = end_control_point;
+ last_end_point = end_point;
+ break;
+ }
+ case 'c': {
+ auto current_position = GetCurrentPosition();
+ auto start_control_point = current_position + read_point(),
+ end_control_point = current_position + read_point(),
+ end_point = current_position + read_point();
+ CubicBezierTo(start_control_point, end_control_point, end_point);
+ last_is_cubic = true;
+ last_control_point = end_control_point;
+ last_end_point = end_point;
+ break;
+ }
+ case 'S': {
+ auto current_position = GetCurrentPosition();
+ auto start_control_point = last_is_cubic ? Point{last_end_point.x * 2,
+ last_end_point.y * 2} -
+ last_control_point
+ : current_position,
+ end_control_point = read_point(), end_point = read_point();
+ CubicBezierTo(start_control_point, end_control_point, end_point);
+ last_is_cubic = true;
+ last_control_point = end_control_point;
+ last_end_point = end_point;
+ break;
+ }
+ case 's': {
+ auto current_position = GetCurrentPosition();
+ auto start_control_point = last_is_cubic ? Point{last_end_point.x * 2,
+ last_end_point.y * 2} -
+ last_control_point
+ : current_position,
+ end_control_point = current_position + read_point(),
+ end_point = current_position + read_point();
+ CubicBezierTo(start_control_point, end_control_point, end_point);
+ last_is_cubic = true;
+ last_control_point = end_control_point;
+ last_end_point = end_point;
+ break;
+ }
+ case 'Q': {
+ auto control_point = read_point(), end_point = read_point();
+ QuadraticBezierTo(control_point, end_point);
+ last_is_quad = true;
+ last_control_point = control_point;
+ last_end_point = end_point;
+ break;
+ }
+ case 'q': {
+ auto current_position = GetCurrentPosition();
+ auto control_point = current_position + read_point(),
+ end_point = current_position + read_point();
+ QuadraticBezierTo(control_point, end_point);
+ last_is_quad = true;
+ last_control_point = control_point;
+ last_end_point = end_point;
+ break;
+ }
+ case 'T': {
+ auto current_position = GetCurrentPosition();
+ auto control_point = last_is_quad ? Point{last_end_point.x * 2,
+ last_end_point.y * 2} -
+ last_control_point
+ : current_position,
+ end_point = read_point();
+ QuadraticBezierTo(control_point, end_point);
+ last_is_quad = true;
+ last_control_point = control_point;
+ last_end_point = end_point;
+ break;
+ }
+ case 't': {
+ auto current_position = GetCurrentPosition();
+ auto control_point = last_is_quad ? Point{last_end_point.x * 2,
+ last_end_point.y * 2} -
+ last_control_point
+ : current_position,
+ end_point = current_position + read_point();
+ QuadraticBezierTo(control_point, end_point);
+ last_is_quad = true;
+ last_control_point = control_point;
+ last_end_point = end_point;
+ break;
+ }
+ case 'A':
+ ArcTo({read_number(), read_number()}, read_number(), read_number(),
+ read_number(), read_point());
+ break;
+ case 'a':
+ RelativeArcTo({read_number(), read_number()}, read_number(),
+ read_number(), read_number(), read_point());
+ break;
+ case 'Z':
+ case 'z':
+ CloseFigure(true);
+ break;
+ default:
+ throw Exception(u"Invalid svg path command.");
+ }
+ return true;
+ };
+
+ auto read_command = [&] {
+ if (read_spaces()) {
+ return false;
+ }
+ auto command = path_d[position];
+
+ if (kSvgPathDataCommands.contains(command)) {
+ position++;
+ do_command(command);
+ } else {
+ do_command(last_command);
+ }
+
+ return true;
+ };
+
+ while (true) {
+ if (!read_command()) break;
+ }
+}
+
} // namespace cru::platform::graphics