diff options
-rw-r--r-- | include/cru/platform/graphics/Geometry.h | 59 | ||||
-rw-r--r-- | include/cru/platform/graphics/SvgGeometryBuilderMixin.h (renamed from include/cru/platform/graphics/SvgGeometry.h) | 26 | ||||
-rw-r--r-- | include/cru/platform/graphics/web_canvas/WebCanvasGeometry.h | 25 | ||||
-rw-r--r-- | src/platform/graphics/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/platform/graphics/Geometry.cpp | 8 | ||||
-rw-r--r-- | src/platform/graphics/SvgGeometry.cpp | 127 | ||||
-rw-r--r-- | src/platform/graphics/SvgGeometryBuilderMixin.cpp | 121 | ||||
-rw-r--r-- | src/platform/graphics/web_canvas/Geometry.cpp | 8 |
8 files changed, 216 insertions, 160 deletions
diff --git a/include/cru/platform/graphics/Geometry.h b/include/cru/platform/graphics/Geometry.h index 0e0278d2..71d003f6 100644 --- a/include/cru/platform/graphics/Geometry.h +++ b/include/cru/platform/graphics/Geometry.h @@ -4,17 +4,65 @@ #include <memory> namespace cru::platform::graphics { +/** + * \remarks Geometry implementation is a disaster zone of platform problems. + * Here are some notes. For geometry object: + * 1. Some platforms just lack the concept of geometry, like web canvas. Even if + * there is one, it usually has limited capabilities. They only allow you to + * create path right on the context and use it directly. The geometry itself + * can't be separated from context and stored independently, not to mention run + * some functions. You can't even create a geometry from current state of + * context. In one word, context -x-> geometry, geometry is weak. + * 2. Doing hit test right on the geometry is not supported on some platform. + * The geometry needs to be put on the context first. + * 3. Transform a geometry to create a new geometry is not supported on some + * platforms. You can transform the context, but no for geometry itself + * independently. + * 4. Create a geometry of a stroke of another geometry is not suppored more + * widely. + * + * Workarounds: + * 1. If the platform didn't support path at all, although not a platform does + * so far, record the commands and replay it on context when it is used. + * 2. If the geometry can't do hit test itself, always attach a canvas with it, + * and when do hit test, put it on context first. + * 3. If transform a geometry to create a new geometry is not supported, zip the + * original geometry and a transform matrix. When it is used on context, restore + * geometry and matrix the same time. + * 4. There is no good workaround for 4, so don't use it, maybe. + * + * Also note for 3, even we can workaround like that, but there are cases it + * does not work. Like if we add the support for adding an existing path to a + * new path, transform can only be performed on context and we can't construct a + * geometry from context, then it fails. + * + * For geometry builder: + * It is hard to conclude the common commands of all platforms. Some have this, + * others not. Some don't have this, but others do. There are some common + * commands that should be used frequently. If some platforms do not support it, + * do it yourself to implement it with existing commands and fancy algorithm. + * + */ struct CRU_PLATFORM_GRAPHICS_API IGeometry : virtual IGraphicsResource { /** - * \remarks Because I designed CreateStrokeGeometry first, which might be not + * \remarks Because I designed CreateStrokeGeometry first, which might be not * that rational. So the default implementation calls that method and do the * test. New implementation should override this. */ virtual bool StrokeContains(float width, const Point& point); virtual bool FillContains(const Point& point) = 0; virtual Rect GetBounds() = 0; + + /** + * \remarks See class doc for platform limitation. + */ virtual std::unique_ptr<IGeometry> Transform(const Matrix& matrix) = 0; - virtual std::unique_ptr<IGeometry> CreateStrokeGeometry(float width) = 0; + + /** + * \remarks This method is a little quirky. It is originated from Direct2D, + * but many platforms do not support this, I guess. So don't use this anymore. + */ + virtual std::unique_ptr<IGeometry> CreateStrokeGeometry(float width); }; /** @@ -50,12 +98,13 @@ struct CRU_PLATFORM_GRAPHICS_API IGeometryBuilder : virtual IGraphicsResource { virtual void QuadraticBezierTo(const Point& control_point, const Point& end_point) = 0; virtual void RelativeQuadraticBezierTo(const Point& control_offset, - const Point& end_offset); + const Point& end_offset); virtual void ArcTo(const Point& radius, float angle, bool is_large_arc, bool is_clockwise, const Point& end_point); - virtual void RelativeArcTo(const Point& radius, float angle, bool is_large_arc, - bool is_clockwise, const Point& end_offset); + virtual void RelativeArcTo(const Point& radius, float angle, + bool is_large_arc, bool is_clockwise, + const Point& end_offset); virtual void CloseFigure(bool close) = 0; diff --git a/include/cru/platform/graphics/SvgGeometry.h b/include/cru/platform/graphics/SvgGeometryBuilderMixin.h index 65602578..d516ce6c 100644 --- a/include/cru/platform/graphics/SvgGeometry.h +++ b/include/cru/platform/graphics/SvgGeometryBuilderMixin.h @@ -1,28 +1,22 @@ #pragma once #include "Geometry.h" -#include "cru/common/Base.h" #include "cru/common/Format.h" #include <utility> namespace cru::platform::graphics { /** - * \remarks This class is purely a helper for some platforms, especially web - * canvas. It constructs a path data of svg of a list of commands. It can't - * generate a Geometry. Calling Build will throw a PlatformUnsupportedException. - * Instead, use GetPathData to get svg path data and use it to do other things. + * \remarks This class is a helper mixin for some platforms, especially web + * canvas. It constructs a svg data by recording commands. Use GetPathData to + * get svg path data and use it to build real geometry. */ -class CRU_PLATFORM_GRAPHICS_API SvgGeometryBuilder - : public Object, - public virtual IGeometryBuilder { +class CRU_PLATFORM_GRAPHICS_API SvgGeometryBuilderMixin + : public virtual IGeometryBuilder { public: - SvgGeometryBuilder(); + SvgGeometryBuilderMixin(); - CRU_DELETE_COPY(SvgGeometryBuilder) - CRU_DELETE_MOVE(SvgGeometryBuilder) - - ~SvgGeometryBuilder() override; + ~SvgGeometryBuilderMixin() override; Point GetCurrentPosition() override; @@ -51,12 +45,12 @@ class CRU_PLATFORM_GRAPHICS_API SvgGeometryBuilder void CloseFigure(bool close) override; - std::unique_ptr<IGeometry> Build() override; + void ParseAndApplySvgPathData(StringView path_d) override; + protected: String GetPathData() const { return current_; } - void ParseAndApplySvgPathData(StringView path_d) override; - + private: template <typename... Args> void Append(StringView format, Args&&... args) { current_ += Format(format, std::forward<Args>(args)...); diff --git a/include/cru/platform/graphics/web_canvas/WebCanvasGeometry.h b/include/cru/platform/graphics/web_canvas/WebCanvasGeometry.h index af4cee91..dfb5bbe7 100644 --- a/include/cru/platform/graphics/web_canvas/WebCanvasGeometry.h +++ b/include/cru/platform/graphics/web_canvas/WebCanvasGeometry.h @@ -1,27 +1,36 @@ #pragma once #include "../Geometry.h" +#include "../SvgGeometryBuilderMixin.h" #include "WebCanvasResource.h" #include <emscripten/val.h> +#include <memory> namespace cru::platform::graphics::web_canvas { +class WebCanvasGeometry : public WebCanvasResource, public virtual IGeometry { + public: + WebCanvasGeometry(WebCanvasGraphicsFactory* factory, emscripten::val path2d); + ~WebCanvasGeometry() override; + + bool StrokeContains(float width, const Point& point) override; + virtual bool FillContains(const Point& point) = 0; + virtual Rect GetBounds() = 0; + virtual std::unique_ptr<IGeometry> Transform(const Matrix& matrix) = 0; + private: + emscripten::val path2d_; +}; + /** * \remarks See IGeometryBuilder for platform limitation. */ class WebCanvasGeometryBuilder : public WebCanvasResource, - public virtual IGeometryBuilder { + public SvgGeometryBuilderMixin { public: WebCanvasGeometryBuilder(WebCanvasGraphicsFactory* factory); ~WebCanvasGeometryBuilder() override; - Point GetCurrentPosition() override; - - void MoveTo(const Point& point) override; - - private: - Point current_postion_; - emscripten::val path2d_; + std::unique_ptr<IGeometry> Build() override; }; } // namespace cru::platform::graphics::web_canvas diff --git a/src/platform/graphics/CMakeLists.txt b/src/platform/graphics/CMakeLists.txt index 1b24c22b..43ef80e0 100644 --- a/src/platform/graphics/CMakeLists.txt +++ b/src/platform/graphics/CMakeLists.txt @@ -3,7 +3,7 @@ add_library(CruPlatformGraphics Geometry.cpp Image.cpp NullPainter.cpp - SvgGeometry.cpp + SvgGeometryBuilderMixin.cpp ) target_compile_definitions(CruPlatformGraphics PRIVATE CRU_PLATFORM_GRAPHICS_EXPORT_API) target_link_libraries(CruPlatformGraphics PUBLIC CruPlatformBase) diff --git a/src/platform/graphics/Geometry.cpp b/src/platform/graphics/Geometry.cpp index b6f2a724..0c98b456 100644 --- a/src/platform/graphics/Geometry.cpp +++ b/src/platform/graphics/Geometry.cpp @@ -1,6 +1,7 @@ #include "cru/platform/graphics/Geometry.h" #include "cru/common/Exception.h" +#include "cru/platform/Exception.h" #include "cru/platform/graphics/Factory.h" #include <cmath> @@ -12,6 +13,13 @@ bool IGeometry::StrokeContains(float width, const Point& point) { return geometry->FillContains(point); } +std::unique_ptr<IGeometry> IGeometry::CreateStrokeGeometry( + [[maybe_unused]] float width) { + throw PlatformUnsupportedException(GetPlatformId(), u"CreateStrokeGeometry", + u"Create stroke geometry of a geometry is " + u"not supported on this platform."); +} + void IGeometryBuilder::RelativeMoveTo(const Point& offset) { MoveTo(GetCurrentPosition() + offset); } diff --git a/src/platform/graphics/SvgGeometry.cpp b/src/platform/graphics/SvgGeometry.cpp deleted file mode 100644 index 20ef1b56..00000000 --- a/src/platform/graphics/SvgGeometry.cpp +++ /dev/null @@ -1,127 +0,0 @@ -#include "cru/platform/graphics/SvgGeometry.h" -#include "cru/platform/Exception.h" -#include "cru/platform/graphics/Geometry.h" - -namespace cru::platform::graphics { -SvgGeometryBuilder::SvgGeometryBuilder() {} - -SvgGeometryBuilder::~SvgGeometryBuilder() {} - -Point SvgGeometryBuilder::GetCurrentPosition() { - throw PlatformUnsupportedException( - GetPlatformId(), u"GetCurrentPosition", - u"Svg geometry does not support get current position."); -} - -void SvgGeometryBuilder::MoveTo(const Point& point) { - AppendCommand(u"M"); - Append(point); -} - -void SvgGeometryBuilder::RelativeMoveTo(const Point& offset) { - AppendCommand(u"m"); - Append(offset); -} - -void SvgGeometryBuilder::LineTo(const Point& point) { - AppendCommand(u"L"); - Append(point); -} - -void SvgGeometryBuilder::RelativeLineTo(const Point& offset) { - AppendCommand(u"l"); - Append(offset); -} - -void SvgGeometryBuilder::CubicBezierTo(const Point& start_control_point, - const Point& end_control_point, - const Point& end_point) { - AppendCommand(u"C"); - Append(start_control_point); - Append(end_control_point); - Append(end_point); -} - -void SvgGeometryBuilder::RelativeCubicBezierTo( - const Point& start_control_offset, const Point& end_control_offset, - const Point& end_offset) { - AppendCommand(u"c"); - Append(start_control_offset); - Append(end_control_offset); - Append(end_offset); -} - -void SvgGeometryBuilder::QuadraticBezierTo(const Point& control_point, - const Point& end_point) { - AppendCommand(u"Q"); - Append(control_point); - Append(end_point); -} - -void SvgGeometryBuilder::RelativeQuadraticBezierTo(const Point& control_offset, - const Point& end_offset) { - AppendCommand(u"q"); - Append(control_offset); - Append(end_offset); -} - -void SvgGeometryBuilder::ArcTo(const Point& radius, float angle, - bool is_large_arc, bool is_clockwise, - const Point& end_point) { - AppendCommand(u"A"); - Append(radius.x); - Append(radius.y); - Append(angle); - Append(is_large_arc); - Append(is_clockwise); - Append(end_point); -} - -void SvgGeometryBuilder::RelativeArcTo(const Point& radius, float angle, - bool is_large_arc, bool is_clockwise, - const Point& end_offset) { - AppendCommand(u"a"); - Append(radius.x); - Append(radius.y); - Append(angle); - Append(is_large_arc); - Append(is_clockwise); - Append(end_offset); -} - -void SvgGeometryBuilder::CloseFigure(bool close) { - if (close) AppendCommand(u"z"); -} - -std::unique_ptr<IGeometry> SvgGeometryBuilder::Build() { - throw PlatformUnsupportedException( - u"SvgGeometryBuilder", u"Build", - u"SvgGeometryBuilder can't build a Geometry. Use GetPathData instead."); -} - -void SvgGeometryBuilder::ParseAndApplySvgPathData(StringView path_d) { - AppendCommand(path_d); -} - -void SvgGeometryBuilder::AppendCommand(StringView command) { - current_ += command; - current_ += u' '; -} - -void SvgGeometryBuilder::Append(bool flag) { - current_ += flag ? u'1' : u'0'; - current_ += u' '; -} - -void SvgGeometryBuilder::Append(float number) { - current_ += ToString(number); - current_ += u' '; -} - -void SvgGeometryBuilder::Append(const Point& point) { - current_ += ToString(point.x); - current_ += u','; - current_ += ToString(point.y); - current_ += u' '; -} -} // namespace cru::platform::graphics diff --git a/src/platform/graphics/SvgGeometryBuilderMixin.cpp b/src/platform/graphics/SvgGeometryBuilderMixin.cpp new file mode 100644 index 00000000..1f868f44 --- /dev/null +++ b/src/platform/graphics/SvgGeometryBuilderMixin.cpp @@ -0,0 +1,121 @@ +#include "cru/platform/graphics/SvgGeometryBuilderMixin.h" +#include "cru/platform/Exception.h" + +namespace cru::platform::graphics { +SvgGeometryBuilderMixin::SvgGeometryBuilderMixin() {} + +SvgGeometryBuilderMixin::~SvgGeometryBuilderMixin() {} + +Point SvgGeometryBuilderMixin::GetCurrentPosition() { + throw PlatformUnsupportedException( + GetPlatformId(), u"GetCurrentPosition", + u"Svg-based geometry does not support get current position."); +} + +void SvgGeometryBuilderMixin::MoveTo(const Point& point) { + AppendCommand(u"M"); + Append(point); +} + +void SvgGeometryBuilderMixin::RelativeMoveTo(const Point& offset) { + AppendCommand(u"m"); + Append(offset); +} + +void SvgGeometryBuilderMixin::LineTo(const Point& point) { + AppendCommand(u"L"); + Append(point); +} + +void SvgGeometryBuilderMixin::RelativeLineTo(const Point& offset) { + AppendCommand(u"l"); + Append(offset); +} + +void SvgGeometryBuilderMixin::CubicBezierTo(const Point& start_control_point, + const Point& end_control_point, + const Point& end_point) { + AppendCommand(u"C"); + Append(start_control_point); + Append(end_control_point); + Append(end_point); +} + +void SvgGeometryBuilderMixin::RelativeCubicBezierTo( + const Point& start_control_offset, const Point& end_control_offset, + const Point& end_offset) { + AppendCommand(u"c"); + Append(start_control_offset); + Append(end_control_offset); + Append(end_offset); +} + +void SvgGeometryBuilderMixin::QuadraticBezierTo(const Point& control_point, + const Point& end_point) { + AppendCommand(u"Q"); + Append(control_point); + Append(end_point); +} + +void SvgGeometryBuilderMixin::RelativeQuadraticBezierTo( + const Point& control_offset, const Point& end_offset) { + AppendCommand(u"q"); + Append(control_offset); + Append(end_offset); +} + +void SvgGeometryBuilderMixin::ArcTo(const Point& radius, float angle, + bool is_large_arc, bool is_clockwise, + const Point& end_point) { + AppendCommand(u"A"); + Append(radius.x); + Append(radius.y); + Append(angle); + Append(is_large_arc); + Append(is_clockwise); + Append(end_point); +} + +void SvgGeometryBuilderMixin::RelativeArcTo(const Point& radius, float angle, + bool is_large_arc, + bool is_clockwise, + const Point& end_offset) { + AppendCommand(u"a"); + Append(radius.x); + Append(radius.y); + Append(angle); + Append(is_large_arc); + Append(is_clockwise); + Append(end_offset); +} + +void SvgGeometryBuilderMixin::CloseFigure(bool close) { + if (close) AppendCommand(u"z"); +} + +void SvgGeometryBuilderMixin::ParseAndApplySvgPathData(StringView path_d) { + AppendCommand(path_d); +} + +void SvgGeometryBuilderMixin::AppendCommand(StringView command) { + current_ += command; + current_ += u' '; +} + +void SvgGeometryBuilderMixin::Append(bool flag) { + current_ += flag ? u'1' : u'0'; + current_ += u' '; +} + +void SvgGeometryBuilderMixin::Append(float number) { + current_ += ToString(number); + current_ += u' '; +} + +void SvgGeometryBuilderMixin::Append(const Point& point) { + current_ += ToString(point.x); + current_ += u','; + current_ += ToString(point.y); + current_ += u' '; +} +} // namespace cru::platform::graphics diff --git a/src/platform/graphics/web_canvas/Geometry.cpp b/src/platform/graphics/web_canvas/Geometry.cpp index eb4ce84e..0f2f5a16 100644 --- a/src/platform/graphics/web_canvas/Geometry.cpp +++ b/src/platform/graphics/web_canvas/Geometry.cpp @@ -4,9 +4,11 @@ #include "cru/platform/web/Js.h" namespace cru::platform::graphics::web_canvas { +bool WebCanvasGeometry::StrokeContains(float width, const Point& point) {} + WebCanvasGeometryBuilder::WebCanvasGeometryBuilder( WebCanvasGraphicsFactory* factory) - : WebCanvasResource(factory) { - path2d_ = web::js::Construct("Path2D"); -} + : WebCanvasResource(factory) {} + +WebCanvasGeometryBuilder::~WebCanvasGeometryBuilder() {} } // namespace cru::platform::graphics::web_canvas |