diff --git a/Tests/LibWeb/Layout/expected/svg-transforms-and-viewboxes.txt b/Tests/LibWeb/Layout/expected/svg-transforms-and-viewboxes.txt index d9619cf222..79ea68f8c7 100644 --- a/Tests/LibWeb/Layout/expected/svg-transforms-and-viewboxes.txt +++ b/Tests/LibWeb/Layout/expected/svg-transforms-and-viewboxes.txt @@ -23,12 +23,12 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline TextNode <#text> SVGGraphicsBox at (50,150) content-size 0x0 children: inline TextNode <#text> - SVGGeometryBox at (45.693222,199.830932) content-size 118.782173x47.453796 children: not-inline + SVGGeometryBox at (45.690780,199.828884) content-size 118.784614x47.455844 children: not-inline TextNode <#text> TextNode <#text> SVGGraphicsBox at (50,150) content-size 0x0 children: inline TextNode <#text> - SVGGeometryBox at (84.5,159.504878) content-size 81x80.995117 children: not-inline + SVGGeometryBox at (84.5,159.499996) content-size 81x81 children: not-inline TextNode <#text> TextNode <#text> TextNode <#text> diff --git a/Userland/Libraries/LibGfx/Painter.cpp b/Userland/Libraries/LibGfx/Painter.cpp index 96d888c811..f86c5a9e24 100644 --- a/Userland/Libraries/LibGfx/Painter.cpp +++ b/Userland/Libraries/LibGfx/Painter.cpp @@ -2451,11 +2451,6 @@ void Painter::stroke_path(Path const& path, Color color, int thickness) cursor = segment->point(); break; } - case Segment::Type::EllipticalArcTo: - auto& arc = static_cast(*segment); - draw_elliptical_arc(cursor.to_type(), segment->point().to_type(), arc.center().to_type(), arc.radii(), arc.x_axis_rotation(), arc.theta_1(), arc.theta_delta(), color, thickness); - cursor = segment->point(); - break; } } } diff --git a/Userland/Libraries/LibGfx/Path.cpp b/Userland/Libraries/LibGfx/Path.cpp index ea10da1a0e..c09db4bf9f 100644 --- a/Userland/Libraries/LibGfx/Path.cpp +++ b/Userland/Libraries/LibGfx/Path.cpp @@ -14,6 +14,54 @@ namespace Gfx { +void Path::approximate_elliptical_arc_with_cubic_beziers(FloatPoint center, FloatSize radii, float x_axis_rotation, float theta, float theta_delta) +{ + float sin_x_rotation; + float cos_x_rotation; + AK::sincos(x_axis_rotation, sin_x_rotation, cos_x_rotation); + auto arc_point_and_derivative = [&](float t, FloatPoint& point, FloatPoint& derivative) { + float sin_angle; + float cos_angle; + AK::sincos(t, sin_angle, cos_angle); + point = FloatPoint { + center.x() + + radii.width() * cos_x_rotation * cos_angle + - radii.height() * sin_x_rotation * sin_angle, + center.y() + + radii.width() * sin_x_rotation * cos_angle + + radii.height() * cos_x_rotation * sin_angle, + }; + derivative = FloatPoint { + -radii.width() * cos_x_rotation * sin_angle + - radii.height() * sin_x_rotation * cos_angle, + -radii.width() * sin_x_rotation * sin_angle + + radii.height() * cos_x_rotation * cos_angle, + }; + }; + auto approximate_arc_between = [&](float start_angle, float end_angle) { + auto t = AK::tan((end_angle - start_angle) / 2); + auto alpha = AK::sin(end_angle - start_angle) * ((AK::sqrt(4 + 3 * t * t) - 1) / 3); + FloatPoint p1, d1; + FloatPoint p2, d2; + arc_point_and_derivative(start_angle, p1, d1); + arc_point_and_derivative(end_angle, p2, d2); + auto q1 = p1 + d1.scaled(alpha, alpha); + auto q2 = p2 - d2.scaled(alpha, alpha); + cubic_bezier_curve_to(q1, q2, p2); + }; + // FIXME: Come up with a more mathematically sound step size (using some error calculation). + auto step = theta_delta; + int step_count = 1; + while (fabs(step) > AK::Pi / 4) { + step /= 2; + step_count *= 2; + } + float prev = theta; + float t = prev + step; + for (int i = 0; i < step_count; i++, prev = t, t += step) + approximate_arc_between(prev, t); +} + void Path::elliptical_arc_to(FloatPoint point, FloatSize radii, float x_axis_rotation, bool large_arc, bool sweep) { auto next_point = point; @@ -105,15 +153,12 @@ void Path::elliptical_arc_to(FloatPoint point, FloatSize radii, float x_axis_rot theta_delta += 2 * AK::Pi; } - elliptical_arc_to( - next_point, + approximate_elliptical_arc_with_cubic_beziers( { cx, cy }, { rx, ry }, x_axis_rotation, theta_1, - theta_delta, - large_arc, - sweep); + theta_delta); } void Path::close() @@ -170,7 +215,6 @@ void Path::close_all_subpaths() case Segment::Type::LineTo: case Segment::Type::QuadraticBezierCurveTo: case Segment::Type::CubicBezierCurveTo: - case Segment::Type::EllipticalArcTo: if (is_first_point_in_subpath) { start_of_subpath = cursor; is_first_point_in_subpath = false; @@ -205,9 +249,6 @@ DeprecatedString Path::to_deprecated_string() const case Segment::Type::CubicBezierCurveTo: builder.append("CubicBezierCurveTo"sv); break; - case Segment::Type::EllipticalArcTo: - builder.append("EllipticalArcTo"sv); - break; case Segment::Type::Invalid: builder.append("Invalid"sv); break; @@ -225,16 +266,6 @@ DeprecatedString Path::to_deprecated_string() const builder.append(", "sv); builder.append(static_cast(*segment).through_1().to_deprecated_string()); break; - case Segment::Type::EllipticalArcTo: { - auto& arc = static_cast(*segment); - builder.appendff(", {}, {}, {}, {}, {}", - arc.radii().to_deprecated_string().characters(), - arc.center().to_deprecated_string().characters(), - arc.x_axis_rotation(), - arc.theta_1(), - arc.theta_delta()); - break; - } default: break; } @@ -304,14 +335,6 @@ void Path::segmentize_path() cursor = segment->point(); break; } - case Segment::Type::EllipticalArcTo: { - auto& arc = static_cast(*segment); - Painter::for_each_line_segment_on_elliptical_arc(cursor, arc.point(), arc.center(), arc.radii(), arc.x_axis_rotation(), arc.theta_1(), arc.theta_delta(), [&](FloatPoint p0, FloatPoint p1) { - add_line(p0, p1); - }); - cursor = segment->point(); - break; - } case Segment::Type::Invalid: VERIFY_NOT_REACHED(); } @@ -346,20 +369,6 @@ Path Path::copy_transformed(Gfx::AffineTransform const& transform) const result.cubic_bezier_curve_to(transform.map(cubic_segment.through_0()), transform.map(cubic_segment.through_1()), transform.map(segment->point())); break; } - case Segment::Type::EllipticalArcTo: { - auto const& arc_segment = static_cast(*segment); - auto det_negative = transform.determinant() < 0; - result.elliptical_arc_to( - transform.map(segment->point()), - transform.map(arc_segment.center()), - transform.map(arc_segment.radii()), - arc_segment.x_axis_rotation() + transform.rotation(), - det_negative ? AK::Pi * 2 - arc_segment.theta_1() : arc_segment.theta_1(), - det_negative ? -arc_segment.theta_delta() : arc_segment.theta_delta(), - arc_segment.large_arc(), - det_negative ? !arc_segment.sweep() : arc_segment.sweep()); - break; - } case Segment::Type::Invalid: VERIFY_NOT_REACHED(); } diff --git a/Userland/Libraries/LibGfx/Path.h b/Userland/Libraries/LibGfx/Path.h index 444ad7df28..32a9dde008 100644 --- a/Userland/Libraries/LibGfx/Path.h +++ b/Userland/Libraries/LibGfx/Path.h @@ -25,7 +25,6 @@ public: LineTo, QuadraticBezierCurveTo, CubicBezierCurveTo, - EllipticalArcTo, }; Segment(FloatPoint point) @@ -105,42 +104,6 @@ private: FloatPoint m_through_1; }; -class EllipticalArcSegment final : public Segment { -public: - EllipticalArcSegment(FloatPoint point, FloatPoint center, FloatSize radii, float x_axis_rotation, float theta_1, float theta_delta, bool large_arc, bool sweep) - : Segment(point) - , m_center(center) - , m_radii(radii) - , m_x_axis_rotation(x_axis_rotation) - , m_theta_1(theta_1) - , m_theta_delta(theta_delta) - , m_large_arc(large_arc) - , m_sweep(sweep) - { - } - - virtual ~EllipticalArcSegment() override = default; - - FloatPoint center() const { return m_center; } - FloatSize radii() const { return m_radii; } - float x_axis_rotation() const { return m_x_axis_rotation; } - float theta_1() const { return m_theta_1; } - float theta_delta() const { return m_theta_delta; } - bool large_arc() const { return m_large_arc; } - bool sweep() const { return m_sweep; } - -private: - virtual Type type() const override { return Segment::Type::EllipticalArcTo; } - - FloatPoint m_center; - FloatSize m_radii; - float m_x_axis_rotation; - float m_theta_1; - float m_theta_delta; - bool m_large_arc; - bool m_sweep; -}; - class Path { public: Path() = default; @@ -190,22 +153,6 @@ public: elliptical_arc_to(point, { radius, radius }, 0, large_arc, sweep); } - // Note: This does not do any sanity checks! - void elliptical_arc_to(FloatPoint endpoint, FloatPoint center, FloatSize radii, float x_axis_rotation, float theta, float theta_delta, bool large_arc, bool sweep) - { - append_segment( - endpoint, - center, - radii, - x_axis_rotation, - theta, - theta_delta, - large_arc, - sweep); - - invalidate_split_lines(); - } - void close(); void close_all_subpaths(); @@ -251,6 +198,8 @@ public: Path stroke_to_fill(float thickness) const; private: + void approximate_elliptical_arc_with_cubic_beziers(FloatPoint center, FloatSize radii, float x_axis_rotation, float theta, float theta_delta); + void invalidate_split_lines() { m_bounding_box.clear();