Serenity Operating System
at master 286 lines 11 kB view raw
1/* 2 * Copyright (c) 2020, Matthew Olsson <mattco@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <AK/Debug.h> 8#include <AK/ExtraMathConstants.h> 9#include <AK/Optional.h> 10#include <LibGfx/Painter.h> 11#include <LibGfx/Path.h> 12#include <LibWeb/DOM/Document.h> 13#include <LibWeb/DOM/Event.h> 14#include <LibWeb/Layout/SVGGeometryBox.h> 15#include <LibWeb/SVG/SVGPathElement.h> 16 17namespace Web::SVG { 18 19[[maybe_unused]] static void print_instruction(PathInstruction const& instruction) 20{ 21 VERIFY(PATH_DEBUG); 22 23 auto& data = instruction.data; 24 25 switch (instruction.type) { 26 case PathInstructionType::Move: 27 dbgln("Move (absolute={})", instruction.absolute); 28 for (size_t i = 0; i < data.size(); i += 2) 29 dbgln(" x={}, y={}", data[i], data[i + 1]); 30 break; 31 case PathInstructionType::ClosePath: 32 dbgln("ClosePath (absolute={})", instruction.absolute); 33 break; 34 case PathInstructionType::Line: 35 dbgln("Line (absolute={})", instruction.absolute); 36 for (size_t i = 0; i < data.size(); i += 2) 37 dbgln(" x={}, y={}", data[i], data[i + 1]); 38 break; 39 case PathInstructionType::HorizontalLine: 40 dbgln("HorizontalLine (absolute={})", instruction.absolute); 41 for (size_t i = 0; i < data.size(); ++i) 42 dbgln(" x={}", data[i]); 43 break; 44 case PathInstructionType::VerticalLine: 45 dbgln("VerticalLine (absolute={})", instruction.absolute); 46 for (size_t i = 0; i < data.size(); ++i) 47 dbgln(" y={}", data[i]); 48 break; 49 case PathInstructionType::Curve: 50 dbgln("Curve (absolute={})", instruction.absolute); 51 for (size_t i = 0; i < data.size(); i += 6) 52 dbgln(" (x1={}, y1={}, x2={}, y2={}), (x={}, y={})", data[i], data[i + 1], data[i + 2], data[i + 3], data[i + 4], data[i + 5]); 53 break; 54 case PathInstructionType::SmoothCurve: 55 dbgln("SmoothCurve (absolute={})", instruction.absolute); 56 for (size_t i = 0; i < data.size(); i += 4) 57 dbgln(" (x2={}, y2={}), (x={}, y={})", data[i], data[i + 1], data[i + 2], data[i + 3]); 58 break; 59 case PathInstructionType::QuadraticBezierCurve: 60 dbgln("QuadraticBezierCurve (absolute={})", instruction.absolute); 61 for (size_t i = 0; i < data.size(); i += 4) 62 dbgln(" (x1={}, y1={}), (x={}, y={})", data[i], data[i + 1], data[i + 2], data[i + 3]); 63 break; 64 case PathInstructionType::SmoothQuadraticBezierCurve: 65 dbgln("SmoothQuadraticBezierCurve (absolute={})", instruction.absolute); 66 for (size_t i = 0; i < data.size(); i += 2) 67 dbgln(" x={}, y={}", data[i], data[i + 1]); 68 break; 69 case PathInstructionType::EllipticalArc: 70 dbgln("EllipticalArc (absolute={})", instruction.absolute); 71 for (size_t i = 0; i < data.size(); i += 7) 72 dbgln(" (rx={}, ry={}) x-axis-rotation={}, large-arc-flag={}, sweep-flag={}, (x={}, y={})", 73 data[i], 74 data[i + 1], 75 data[i + 2], 76 data[i + 3], 77 data[i + 4], 78 data[i + 5], 79 data[i + 6]); 80 break; 81 case PathInstructionType::Invalid: 82 dbgln("Invalid"); 83 break; 84 } 85} 86 87SVGPathElement::SVGPathElement(DOM::Document& document, DOM::QualifiedName qualified_name) 88 : SVGGeometryElement(document, move(qualified_name)) 89{ 90} 91 92JS::ThrowCompletionOr<void> SVGPathElement::initialize(JS::Realm& realm) 93{ 94 MUST_OR_THROW_OOM(Base::initialize(realm)); 95 set_prototype(&Bindings::ensure_web_prototype<Bindings::SVGPathElementPrototype>(realm, "SVGPathElement")); 96 97 return {}; 98} 99 100void SVGPathElement::parse_attribute(DeprecatedFlyString const& name, DeprecatedString const& value) 101{ 102 SVGGeometryElement::parse_attribute(name, value); 103 104 if (name == "d") { 105 m_instructions = AttributeParser::parse_path_data(value); 106 m_path.clear(); 107 } 108} 109 110Gfx::Path path_from_path_instructions(ReadonlySpan<PathInstruction> instructions) 111{ 112 Gfx::Path path; 113 Optional<Gfx::FloatPoint> previous_control_point; 114 PathInstructionType last_instruction = PathInstructionType::Invalid; 115 116 for (auto& instruction : instructions) { 117 // If the first path element uses relative coordinates, we treat them as absolute by making them relative to (0, 0). 118 auto last_point = path.segments().is_empty() ? Gfx::FloatPoint { 0, 0 } : path.segments().last()->point(); 119 120 auto& absolute = instruction.absolute; 121 auto& data = instruction.data; 122 123 if constexpr (PATH_DEBUG) { 124 print_instruction(instruction); 125 } 126 127 bool clear_last_control_point = true; 128 129 switch (instruction.type) { 130 case PathInstructionType::Move: { 131 Gfx::FloatPoint point = { data[0], data[1] }; 132 if (absolute) { 133 path.move_to(point); 134 } else { 135 path.move_to(point + last_point); 136 } 137 break; 138 } 139 case PathInstructionType::ClosePath: 140 path.close(); 141 break; 142 case PathInstructionType::Line: { 143 Gfx::FloatPoint point = { data[0], data[1] }; 144 if (absolute) { 145 path.line_to(point); 146 } else { 147 path.line_to(point + last_point); 148 } 149 break; 150 } 151 case PathInstructionType::HorizontalLine: { 152 if (absolute) 153 path.line_to(Gfx::FloatPoint { data[0], last_point.y() }); 154 else 155 path.line_to(Gfx::FloatPoint { data[0] + last_point.x(), last_point.y() }); 156 break; 157 } 158 case PathInstructionType::VerticalLine: { 159 if (absolute) 160 path.line_to(Gfx::FloatPoint { last_point.x(), data[0] }); 161 else 162 path.line_to(Gfx::FloatPoint { last_point.x(), data[0] + last_point.y() }); 163 break; 164 } 165 case PathInstructionType::EllipticalArc: { 166 double rx = data[0]; 167 double ry = data[1]; 168 double x_axis_rotation = double { data[2] } * M_DEG2RAD; 169 double large_arc_flag = data[3]; 170 double sweep_flag = data[4]; 171 172 Gfx::FloatPoint next_point; 173 174 if (absolute) 175 next_point = { data[5], data[6] }; 176 else 177 next_point = { data[5] + last_point.x(), data[6] + last_point.y() }; 178 179 path.elliptical_arc_to(next_point, { rx, ry }, x_axis_rotation, large_arc_flag != 0, sweep_flag != 0); 180 break; 181 } 182 case PathInstructionType::QuadraticBezierCurve: { 183 clear_last_control_point = false; 184 185 Gfx::FloatPoint through = { data[0], data[1] }; 186 Gfx::FloatPoint point = { data[2], data[3] }; 187 188 if (absolute) { 189 path.quadratic_bezier_curve_to(through, point); 190 previous_control_point = through; 191 } else { 192 auto control_point = through + last_point; 193 path.quadratic_bezier_curve_to(control_point, point + last_point); 194 previous_control_point = control_point; 195 } 196 break; 197 } 198 case PathInstructionType::SmoothQuadraticBezierCurve: { 199 clear_last_control_point = false; 200 201 if (!previous_control_point.has_value() 202 || ((last_instruction != PathInstructionType::QuadraticBezierCurve) && (last_instruction != PathInstructionType::SmoothQuadraticBezierCurve))) { 203 previous_control_point = last_point; 204 } 205 206 auto dx_end_control = last_point.dx_relative_to(previous_control_point.value()); 207 auto dy_end_control = last_point.dy_relative_to(previous_control_point.value()); 208 auto control_point = Gfx::FloatPoint { last_point.x() + dx_end_control, last_point.y() + dy_end_control }; 209 210 Gfx::FloatPoint end_point = { data[0], data[1] }; 211 212 if (absolute) { 213 path.quadratic_bezier_curve_to(control_point, end_point); 214 } else { 215 path.quadratic_bezier_curve_to(control_point, end_point + last_point); 216 } 217 218 previous_control_point = control_point; 219 break; 220 } 221 222 case PathInstructionType::Curve: { 223 clear_last_control_point = false; 224 225 Gfx::FloatPoint c1 = { data[0], data[1] }; 226 Gfx::FloatPoint c2 = { data[2], data[3] }; 227 Gfx::FloatPoint p2 = { data[4], data[5] }; 228 if (!absolute) { 229 p2 += last_point; 230 c1 += last_point; 231 c2 += last_point; 232 } 233 path.cubic_bezier_curve_to(c1, c2, p2); 234 235 previous_control_point = c2; 236 break; 237 } 238 239 case PathInstructionType::SmoothCurve: { 240 clear_last_control_point = false; 241 242 if (!previous_control_point.has_value() 243 || ((last_instruction != PathInstructionType::Curve) && (last_instruction != PathInstructionType::SmoothCurve))) { 244 previous_control_point = last_point; 245 } 246 247 // 9.5.2. Reflected control points https://svgwg.org/svg2-draft/paths.html#ReflectedControlPoints 248 // If the current point is (curx, cury) and the final control point of the previous path segment is (oldx2, oldy2), 249 // then the reflected point (i.e., (newx1, newy1), the first control point of the current path segment) is: 250 // (newx1, newy1) = (curx - (oldx2 - curx), cury - (oldy2 - cury)) 251 auto reflected_previous_control_x = last_point.x() - previous_control_point.value().dx_relative_to(last_point); 252 auto reflected_previous_control_y = last_point.y() - previous_control_point.value().dy_relative_to(last_point); 253 Gfx::FloatPoint c1 = Gfx::FloatPoint { reflected_previous_control_x, reflected_previous_control_y }; 254 Gfx::FloatPoint c2 = { data[0], data[1] }; 255 Gfx::FloatPoint p2 = { data[2], data[3] }; 256 if (!absolute) { 257 p2 += last_point; 258 c2 += last_point; 259 } 260 path.cubic_bezier_curve_to(c1, c2, p2); 261 262 previous_control_point = c2; 263 break; 264 } 265 case PathInstructionType::Invalid: 266 VERIFY_NOT_REACHED(); 267 } 268 269 if (clear_last_control_point) { 270 previous_control_point = Gfx::FloatPoint {}; 271 } 272 last_instruction = instruction.type; 273 } 274 275 return path; 276} 277 278Gfx::Path& SVGPathElement::get_path() 279{ 280 if (!m_path.has_value()) { 281 m_path = path_from_path_instructions(m_instructions); 282 } 283 return m_path.value(); 284} 285 286}