aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--demos/CMakeLists.txt2
-rw-r--r--demos/svg_path/CMakeLists.txt12
-rw-r--r--demos/svg_path/main.cpp47
-rw-r--r--include/cru/platform/graphics/Geometry.h6
-rw-r--r--src/common/String.cpp4
-rw-r--r--src/platform/graphics/Geometry.cpp212
6 files changed, 280 insertions, 3 deletions
diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt
index f2df325d..f2f781bd 100644
--- a/demos/CMakeLists.txt
+++ b/demos/CMakeLists.txt
@@ -7,11 +7,13 @@ if(WIN32)
add_subdirectory(scroll_view)
add_subdirectory(input_method)
+ add_subdirectory(svg_path)
elseif(APPLE)
add_subdirectory(main)
add_subdirectory(scroll_view)
add_subdirectory(input_method)
+ add_subdirectory(svg_path)
elseif(UNIX)
add_subdirectory(xcb)
endif()
diff --git a/demos/svg_path/CMakeLists.txt b/demos/svg_path/CMakeLists.txt
new file mode 100644
index 00000000..8e37227b
--- /dev/null
+++ b/demos/svg_path/CMakeLists.txt
@@ -0,0 +1,12 @@
+add_executable(cru_demo_svg_path main.cpp)
+
+if(APPLE)
+ set_target_properties(cru_demo_svg_path PROPERTIES
+ MACOSX_BUNDLE TRUE
+ MACOSX_BUNDLE_BUNDLE_NAME cru-demo-svg-path
+ MACOSX_BUNDLE_GUI_IDENTIFIER life.crupest.demo-svg-path
+ )
+endif()
+
+target_link_libraries(cru_demo_svg_path PRIVATE cru_demo_base)
+
diff --git a/demos/svg_path/main.cpp b/demos/svg_path/main.cpp
new file mode 100644
index 00000000..970aa317
--- /dev/null
+++ b/demos/svg_path/main.cpp
@@ -0,0 +1,47 @@
+#include "cru/platform/Color.h"
+#include "cru/platform/bootstrap/Bootstrap.h"
+#include "cru/platform/graphics/Factory.h"
+#include "cru/platform/graphics/Font.h"
+#include "cru/platform/graphics/Painter.h"
+#include "cru/platform/gui/InputMethod.h"
+#include "cru/platform/gui/UiApplication.h"
+#include "cru/platform/gui/Window.h"
+
+int main() {
+ using namespace cru;
+ using namespace cru::platform;
+ using namespace cru::platform::graphics;
+ using namespace cru::platform::gui;
+
+ IUiApplication* application = bootstrap::CreateUiApplication();
+
+ auto graphics_factory = application->GetGraphicsFactory();
+
+ auto window = application->CreateWindow();
+
+ auto brush = graphics_factory->CreateSolidColorBrush(colors::black);
+
+ auto create_geometry = [graphics_factory](StringView path_d) {
+ auto geometry_builder = graphics_factory->CreateGeometryBuilder();
+ geometry_builder->ParseAndApplySvgPathData(path_d);
+ return geometry_builder->Build();
+ };
+
+ auto geometry = create_geometry(
+ uR"(
+M8.5 5.5a.5.5 0 0 0-1 0v3.362l-1.429 2.38a.5.5 0 1 0 .858.515l1.5-2.5A.5.5 0 0 0 8.5 9V5.5z
+M6.5 0a.5.5 0 0 0 0 1H7v1.07a7.001 7.001 0 0 0-3.273 12.474l-.602.602a.5.5 0 0 0 .707.708l.746-.746A6.97 6.97 0 0 0 8 16a6.97 6.97 0 0 0 3.422-.892l.746.746a.5.5 0 0 0 .707-.708l-.601-.602A7.001 7.001 0 0 0 9 2.07V1h.5a.5.5 0 0 0 0-1h-3zm1.038 3.018a6.093 6.093 0 0 1 .924 0 6 6 0 1 1-.924 0zM0 3.5c0 .753.333 1.429.86 1.887A8.035 8.035 0 0 1 4.387 1.86 2.5 2.5 0 0 0 0 3.5zM13.5 1c-.753 0-1.429.333-1.887.86a8.035 8.035 0 0 1 3.527 3.527A2.5 2.5 0 0 0 13.5 1z
+ )");
+
+ window->PaintEvent()->AddHandler([&](auto) {
+ auto painter = window->BeginPaint();
+ painter->PushState();
+ painter->ConcatTransform(Matrix::Scale(10, 10));
+ painter->FillGeometry(geometry.get(), brush.get());
+ painter->PopState();
+ });
+
+ window->SetVisibility(WindowVisibilityType::Show);
+
+ return application->Run();
+}
diff --git a/include/cru/platform/graphics/Geometry.h b/include/cru/platform/graphics/Geometry.h
index 1a303742..a4146008 100644
--- a/include/cru/platform/graphics/Geometry.h
+++ b/include/cru/platform/graphics/Geometry.h
@@ -6,8 +6,6 @@ struct CRU_PLATFORM_GRAPHICS_API IGeometry : virtual IGraphicsResource {
virtual bool FillContains(const Point& point) = 0;
};
-// After called Build, calling every method will throw a
-
struct CRU_PLATFORM_GRAPHICS_API IGeometryBuilder : virtual IGraphicsResource {
virtual Point GetCurrentPosition() = 0;
@@ -17,9 +15,11 @@ struct CRU_PLATFORM_GRAPHICS_API IGeometryBuilder : virtual IGraphicsResource {
}
virtual void LineTo(const Point& point) = 0;
+ void LineTo(float x, float y) { LineTo(Point(x, y)); }
void RelativeLineTo(const Point& offset) {
LineTo(GetCurrentPosition() + offset);
}
+ void RelativeLineTo(float x, float y) { RelativeLineTo(Point(x, y)); }
virtual void CubicBezierTo(const Point& start_control_point,
const Point& end_control_point,
@@ -53,5 +53,7 @@ struct CRU_PLATFORM_GRAPHICS_API IGeometryBuilder : virtual IGraphicsResource {
virtual void CloseFigure(bool close) = 0;
virtual std::unique_ptr<IGeometry> Build() = 0;
+
+ void ParseAndApplySvgPathData(StringView path_d);
};
} // namespace cru::platform::graphics
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