summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew Olsson <matthewcolsson@gmail.com>2020-07-21 23:46:39 -0700
committerAndreas Kling <kling@serenityos.org>2020-07-26 14:53:43 +0200
commit5985eac81df10e3d3ce65510baa7e06e519a341c (patch)
tree791035c11aaec4a74c8b00b5f60ea1272fdcff99
parent1cffde763563bef81b5d76af85236989166a303d (diff)
downloadserenity-5985eac81df10e3d3ce65510baa7e06e519a341c.zip
LibWeb: Add elliptical curve support to svg path elements
-rw-r--r--Libraries/LibWeb/DOM/HTMLPathElement.cpp103
1 files changed, 96 insertions, 7 deletions
diff --git a/Libraries/LibWeb/DOM/HTMLPathElement.cpp b/Libraries/LibWeb/DOM/HTMLPathElement.cpp
index edc114ed7a..8c5326d71c 100644
--- a/Libraries/LibWeb/DOM/HTMLPathElement.cpp
+++ b/Libraries/LibWeb/DOM/HTMLPathElement.cpp
@@ -439,7 +439,7 @@ void HTMLPathElement::paint(const SvgPaintingContext& context, Gfx::Painter& pai
path.move_to({ data[0], data[1] });
} else {
ASSERT(!path.segments().is_empty());
- path.move_to(Gfx::FloatPoint { data[0], data[1] } + path.segments().last().point);
+ path.move_to(Gfx::FloatPoint { data[0], data[1] } + path.segments().last().point());
}
break;
case PathInstructionType::ClosePath:
@@ -450,12 +450,12 @@ void HTMLPathElement::paint(const SvgPaintingContext& context, Gfx::Painter& pai
path.line_to({ data[0], data[1] });
} else {
ASSERT(!path.segments().is_empty());
- path.line_to(Gfx::FloatPoint { data[0], data[1] } + path.segments().last().point);
+ path.line_to(Gfx::FloatPoint { data[0], data[1] } + path.segments().last().point());
}
break;
case PathInstructionType::HorizontalLine: {
ASSERT(!path.segments().is_empty());
- auto last_point = path.segments().last().point;
+ auto last_point = path.segments().last().point();
if (absolute) {
path.line_to(Gfx::FloatPoint { data[0], last_point.y() });
} else {
@@ -465,7 +465,7 @@ void HTMLPathElement::paint(const SvgPaintingContext& context, Gfx::Painter& pai
}
case PathInstructionType::VerticalLine: {
ASSERT(!path.segments().is_empty());
- auto last_point = path.segments().last().point;
+ auto last_point = path.segments().last().point();
if (absolute) {
path.line_to(Gfx::FloatPoint{ last_point.x(), data[0] });
} else {
@@ -473,27 +473,116 @@ void HTMLPathElement::paint(const SvgPaintingContext& context, Gfx::Painter& pai
}
break;
}
+ case PathInstructionType::EllipticalArc: {
+ double rx = data[0];
+ double ry = data[1];
+ double x_axis_rotation = data[2] * M_DEG2RAD;
+ double large_arc_flag = data[3];
+ double sweep_flag = data[4];
+
+ double x_axis_rotation_c = cos(x_axis_rotation);
+ double x_axis_rotation_s = sin(x_axis_rotation);
+
+ auto& last_point = path.segments().last().point();
+
+ Gfx::FloatPoint next_point;
+
+ if (absolute) {
+ next_point = { data[5], data[6] };
+ } else {
+ next_point = { data[5] + last_point.x(), data[6] + last_point.y() };
+ }
+
+ // Step 1 of out-of-range radii correction
+ if (rx == 0.0 || ry == 0.0) {
+ path.line_to(next_point);
+ break;
+ }
+
+ // Step 2 of out-of-range radii correction
+ if (rx < 0)
+ rx *= -1.0;
+ if (ry < 0)
+ ry *= -1.0;
+
+ // Find (cx, cy), theta_1, theta_delta
+ // Step 1: Compute (x1', y1')
+ auto x_avg = (last_point.x() - next_point.x()) / 2.0f;
+ auto y_avg = (last_point.y() - next_point.y()) / 2.0f;
+ auto x1p = x_axis_rotation_c * x_avg + x_axis_rotation_s * y_avg;
+ auto y1p = -x_axis_rotation_s * x_avg + x_axis_rotation_c * y_avg;
+
+ // Step 2: Compute (cx', cy')
+ double x1p_sq = pow(x1p, 2.0);
+ double y1p_sq = pow(y1p, 2.0);
+ double rx_sq = pow(rx, 2.0);
+ double ry_sq = pow(ry, 2.0);
+
+ // Step 3 of out-of-range radii correction
+ double lambda = x1p_sq / rx_sq + y1p_sq / ry_sq;
+ double multiplier;
+
+ if (lambda > 1.0) {
+ auto lambda_sqrt = sqrt(lambda);
+ rx *= lambda_sqrt;
+ ry *= lambda_sqrt;
+ multiplier = 0.0;
+ } else {
+ double numerator = rx_sq * ry_sq - rx_sq * y1p_sq - ry_sq * x1p_sq;
+ double denominator = rx_sq * y1p_sq + ry_sq * x1p_sq;
+ multiplier = sqrt(numerator / denominator);
+ }
+
+ if (large_arc_flag == sweep_flag)
+ multiplier *= -1.0;
+
+ double cxp = multiplier * rx * y1p / ry;
+ double cyp = multiplier * -ry * x1p / rx;
+
+ // Step 3: Compute (cx, cy) from (cx', cy')
+ x_avg = (last_point.x() + next_point.x()) / 2.0f;
+ y_avg = (last_point.y() + next_point.y()) / 2.0f;
+ double cx = x_axis_rotation_c * cxp - x_axis_rotation_s * cyp + x_avg;
+ double cy = x_axis_rotation_s * cxp + x_axis_rotation_c * cyp + y_avg;
+
+ double theta_1 = atan2((y1p - cyp) / ry, (x1p - cxp) / rx);
+ double theta_2 = atan2((-y1p - cyp) / ry, (-x1p - cxp) / rx);
+
+ auto theta_delta = theta_2 - theta_1;
+
+ if (sweep_flag == 0 && theta_delta > 0.0f) {
+ theta_delta -= M_TAU;
+ } else if (sweep_flag != 0 && theta_delta < 0) {
+ theta_delta += M_TAU;
+ }
+
+ path.elliptical_arc_to(next_point, { cx, cy }, { rx, ry }, x_axis_rotation, theta_1, theta_delta);
+
+ break;
+ }
case PathInstructionType::QuadraticBezierCurve:
if (absolute) {
path.quadratic_bezier_curve_to({ data[0], data[1] }, { data[2], data[3] });
} else {
ASSERT(!path.segments().is_empty());
- auto last_point = path.segments().last().point;
+ auto last_point = path.segments().last().point();
path.quadratic_bezier_curve_to({ data[0] + last_point.x(), data[1] + last_point.y() }, { data[2] + last_point.x(), data[3] + last_point.y() });
}
break;
case PathInstructionType::Curve:
case PathInstructionType::SmoothCurve:
case PathInstructionType::SmoothQuadraticBezierCurve:
- case PathInstructionType::EllipticalArc:
TODO();
case PathInstructionType::Invalid:
ASSERT_NOT_REACHED();
}
}
- painter.fill_path(path, context.fill_color, Gfx::Painter::WindingRule::EvenOdd);
painter.stroke_path(path, context.stroke_color, context.stroke_width);
+
+ // Fills are computed as though all paths are closed (https://svgwg.org/svg2-draft/painting.html#FillProperties)
+ path.close();
+ painter.fill_path(path, context.fill_color, Gfx::Painter::WindingRule::EvenOdd);
}
}