diff options
-rw-r--r-- | demos/CMakeLists.txt | 2 | ||||
-rw-r--r-- | demos/svg_path/CMakeLists.txt | 12 | ||||
-rw-r--r-- | demos/svg_path/main.cpp | 47 | ||||
-rw-r--r-- | include/cru/platform/graphics/Geometry.h | 6 | ||||
-rw-r--r-- | src/common/String.cpp | 4 | ||||
-rw-r--r-- | src/platform/graphics/Geometry.cpp | 212 |
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 |