SVGPathElement.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622
  1. /*
  2. * Copyright (c) 2020, Matthew Olsson <mattco@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/Debug.h>
  7. #include <AK/ExtraMathConstants.h>
  8. #include <AK/StringBuilder.h>
  9. #include <LibGfx/Painter.h>
  10. #include <LibGfx/Path.h>
  11. #include <LibWeb/DOM/Document.h>
  12. #include <LibWeb/DOM/Event.h>
  13. #include <LibWeb/Layout/SVGGeometryBox.h>
  14. #include <LibWeb/SVG/SVGPathElement.h>
  15. #include <ctype.h>
  16. namespace Web::SVG {
  17. [[maybe_unused]] static void print_instruction(const PathInstruction& instruction)
  18. {
  19. VERIFY(PATH_DEBUG);
  20. auto& data = instruction.data;
  21. switch (instruction.type) {
  22. case PathInstructionType::Move:
  23. dbgln("Move (absolute={})", instruction.absolute);
  24. for (size_t i = 0; i < data.size(); i += 2)
  25. dbgln(" x={}, y={}", data[i], data[i + 1]);
  26. break;
  27. case PathInstructionType::ClosePath:
  28. dbgln("ClosePath (absolute={})", instruction.absolute);
  29. break;
  30. case PathInstructionType::Line:
  31. dbgln("Line (absolute={})", instruction.absolute);
  32. for (size_t i = 0; i < data.size(); i += 2)
  33. dbgln(" x={}, y={}", data[i], data[i + 1]);
  34. break;
  35. case PathInstructionType::HorizontalLine:
  36. dbgln("HorizontalLine (absolute={})", instruction.absolute);
  37. for (size_t i = 0; i < data.size(); ++i)
  38. dbgln(" x={}", data[i]);
  39. break;
  40. case PathInstructionType::VerticalLine:
  41. dbgln("VerticalLine (absolute={})", instruction.absolute);
  42. for (size_t i = 0; i < data.size(); ++i)
  43. dbgln(" y={}", data[i]);
  44. break;
  45. case PathInstructionType::Curve:
  46. dbgln("Curve (absolute={})", instruction.absolute);
  47. for (size_t i = 0; i < data.size(); i += 6)
  48. dbgln(" (x1={}, y1={}, x2={}, y2={}), (x={}, y={})", data[i], data[i + 1], data[i + 2], data[i + 3], data[i + 4], data[i + 5]);
  49. break;
  50. case PathInstructionType::SmoothCurve:
  51. dbgln("SmoothCurve (absolute={})", instruction.absolute);
  52. for (size_t i = 0; i < data.size(); i += 4)
  53. dbgln(" (x2={}, y2={}), (x={}, y={})", data[i], data[i + 1], data[i + 2], data[i + 3]);
  54. break;
  55. case PathInstructionType::QuadraticBezierCurve:
  56. dbgln("QuadraticBezierCurve (absolute={})", instruction.absolute);
  57. for (size_t i = 0; i < data.size(); i += 4)
  58. dbgln(" (x1={}, y1={}), (x={}, y={})", data[i], data[i + 1], data[i + 2], data[i + 3]);
  59. break;
  60. case PathInstructionType::SmoothQuadraticBezierCurve:
  61. dbgln("SmoothQuadraticBezierCurve (absolute={})", instruction.absolute);
  62. for (size_t i = 0; i < data.size(); i += 2)
  63. dbgln(" x={}, y={}", data[i], data[i + 1]);
  64. break;
  65. case PathInstructionType::EllipticalArc:
  66. dbgln("EllipticalArc (absolute={})", instruction.absolute);
  67. for (size_t i = 0; i < data.size(); i += 7)
  68. dbgln(" (rx={}, ry={}) x-axis-rotation={}, large-arc-flag={}, sweep-flag={}, (x={}, y={})",
  69. data[i],
  70. data[i + 1],
  71. data[i + 2],
  72. data[i + 3],
  73. data[i + 4],
  74. data[i + 5],
  75. data[i + 6]);
  76. break;
  77. case PathInstructionType::Invalid:
  78. dbgln("Invalid");
  79. break;
  80. }
  81. }
  82. PathDataParser::PathDataParser(const String& source)
  83. : m_source(source)
  84. {
  85. }
  86. Vector<PathInstruction> PathDataParser::parse()
  87. {
  88. parse_whitespace();
  89. while (!done())
  90. parse_drawto();
  91. if (!m_instructions.is_empty() && m_instructions[0].type != PathInstructionType::Move)
  92. VERIFY_NOT_REACHED();
  93. return m_instructions;
  94. }
  95. void PathDataParser::parse_drawto()
  96. {
  97. if (match('M') || match('m')) {
  98. parse_moveto();
  99. } else if (match('Z') || match('z')) {
  100. parse_closepath();
  101. } else if (match('L') || match('l')) {
  102. parse_lineto();
  103. } else if (match('H') || match('h')) {
  104. parse_horizontal_lineto();
  105. } else if (match('V') || match('v')) {
  106. parse_vertical_lineto();
  107. } else if (match('C') || match('c')) {
  108. parse_curveto();
  109. } else if (match('S') || match('s')) {
  110. parse_smooth_curveto();
  111. } else if (match('Q') || match('q')) {
  112. parse_quadratic_bezier_curveto();
  113. } else if (match('T') || match('t')) {
  114. parse_smooth_quadratic_bezier_curveto();
  115. } else if (match('A') || match('a')) {
  116. parse_elliptical_arc();
  117. } else {
  118. dbgln("PathDataParser::parse_drawto failed to match: '{}'", ch());
  119. TODO();
  120. }
  121. }
  122. void PathDataParser::parse_moveto()
  123. {
  124. bool absolute = consume() == 'M';
  125. parse_whitespace();
  126. for (auto& pair : parse_coordinate_pair_sequence())
  127. m_instructions.append({ PathInstructionType::Move, absolute, pair });
  128. }
  129. void PathDataParser::parse_closepath()
  130. {
  131. bool absolute = consume() == 'Z';
  132. parse_whitespace();
  133. m_instructions.append({ PathInstructionType::ClosePath, absolute, {} });
  134. }
  135. void PathDataParser::parse_lineto()
  136. {
  137. bool absolute = consume() == 'L';
  138. parse_whitespace();
  139. for (auto& pair : parse_coordinate_pair_sequence())
  140. m_instructions.append({ PathInstructionType::Line, absolute, pair });
  141. }
  142. void PathDataParser::parse_horizontal_lineto()
  143. {
  144. bool absolute = consume() == 'H';
  145. parse_whitespace();
  146. m_instructions.append({ PathInstructionType::HorizontalLine, absolute, parse_coordinate_sequence() });
  147. }
  148. void PathDataParser::parse_vertical_lineto()
  149. {
  150. bool absolute = consume() == 'V';
  151. parse_whitespace();
  152. m_instructions.append({ PathInstructionType::VerticalLine, absolute, parse_coordinate_sequence() });
  153. }
  154. void PathDataParser::parse_curveto()
  155. {
  156. bool absolute = consume() == 'C';
  157. parse_whitespace();
  158. while (true) {
  159. m_instructions.append({ PathInstructionType::Curve, absolute, parse_coordinate_pair_triplet() });
  160. if (match_comma_whitespace())
  161. parse_comma_whitespace();
  162. if (!match_coordinate())
  163. break;
  164. }
  165. }
  166. void PathDataParser::parse_smooth_curveto()
  167. {
  168. bool absolute = consume() == 'S';
  169. parse_whitespace();
  170. while (true) {
  171. m_instructions.append({ PathInstructionType::SmoothCurve, absolute, parse_coordinate_pair_double() });
  172. if (match_comma_whitespace())
  173. parse_comma_whitespace();
  174. if (!match_coordinate())
  175. break;
  176. }
  177. }
  178. void PathDataParser::parse_quadratic_bezier_curveto()
  179. {
  180. bool absolute = consume() == 'Q';
  181. parse_whitespace();
  182. while (true) {
  183. m_instructions.append({ PathInstructionType::QuadraticBezierCurve, absolute, parse_coordinate_pair_double() });
  184. if (match_comma_whitespace())
  185. parse_comma_whitespace();
  186. if (!match_coordinate())
  187. break;
  188. }
  189. }
  190. void PathDataParser::parse_smooth_quadratic_bezier_curveto()
  191. {
  192. bool absolute = consume() == 'T';
  193. parse_whitespace();
  194. while (true) {
  195. m_instructions.append({ PathInstructionType::SmoothQuadraticBezierCurve, absolute, parse_coordinate_pair() });
  196. if (match_comma_whitespace())
  197. parse_comma_whitespace();
  198. if (!match_coordinate())
  199. break;
  200. }
  201. }
  202. void PathDataParser::parse_elliptical_arc()
  203. {
  204. bool absolute = consume() == 'A';
  205. parse_whitespace();
  206. while (true) {
  207. m_instructions.append({ PathInstructionType::EllipticalArc, absolute, parse_elliptical_arg_argument() });
  208. if (match_comma_whitespace())
  209. parse_comma_whitespace();
  210. if (!match_coordinate())
  211. break;
  212. }
  213. }
  214. float PathDataParser::parse_coordinate()
  215. {
  216. return parse_sign() * parse_number();
  217. }
  218. Vector<float> PathDataParser::parse_coordinate_pair()
  219. {
  220. Vector<float> coordinates;
  221. coordinates.append(parse_coordinate());
  222. if (match_comma_whitespace())
  223. parse_comma_whitespace();
  224. coordinates.append(parse_coordinate());
  225. return coordinates;
  226. }
  227. Vector<float> PathDataParser::parse_coordinate_sequence()
  228. {
  229. Vector<float> sequence;
  230. while (true) {
  231. sequence.append(parse_coordinate());
  232. if (match_comma_whitespace())
  233. parse_comma_whitespace();
  234. if (!match_comma_whitespace() && !match_coordinate())
  235. break;
  236. }
  237. return sequence;
  238. }
  239. Vector<Vector<float>> PathDataParser::parse_coordinate_pair_sequence()
  240. {
  241. Vector<Vector<float>> sequence;
  242. while (true) {
  243. sequence.append(parse_coordinate_pair());
  244. if (match_comma_whitespace())
  245. parse_comma_whitespace();
  246. if (!match_comma_whitespace() && !match_coordinate())
  247. break;
  248. }
  249. return sequence;
  250. }
  251. Vector<float> PathDataParser::parse_coordinate_pair_double()
  252. {
  253. Vector<float> coordinates;
  254. coordinates.extend(parse_coordinate_pair());
  255. if (match_comma_whitespace())
  256. parse_comma_whitespace();
  257. coordinates.extend(parse_coordinate_pair());
  258. return coordinates;
  259. }
  260. Vector<float> PathDataParser::parse_coordinate_pair_triplet()
  261. {
  262. Vector<float> coordinates;
  263. coordinates.extend(parse_coordinate_pair());
  264. if (match_comma_whitespace())
  265. parse_comma_whitespace();
  266. coordinates.extend(parse_coordinate_pair());
  267. if (match_comma_whitespace())
  268. parse_comma_whitespace();
  269. coordinates.extend(parse_coordinate_pair());
  270. return coordinates;
  271. }
  272. Vector<float> PathDataParser::parse_elliptical_arg_argument()
  273. {
  274. Vector<float> numbers;
  275. numbers.append(parse_number());
  276. if (match_comma_whitespace())
  277. parse_comma_whitespace();
  278. numbers.append(parse_number());
  279. if (match_comma_whitespace())
  280. parse_comma_whitespace();
  281. numbers.append(parse_number());
  282. parse_comma_whitespace();
  283. numbers.append(parse_flag());
  284. if (match_comma_whitespace())
  285. parse_comma_whitespace();
  286. numbers.append(parse_flag());
  287. if (match_comma_whitespace())
  288. parse_comma_whitespace();
  289. numbers.extend(parse_coordinate_pair());
  290. return numbers;
  291. }
  292. void PathDataParser::parse_whitespace(bool must_match_once)
  293. {
  294. bool matched = false;
  295. while (!done() && match_whitespace()) {
  296. consume();
  297. matched = true;
  298. }
  299. VERIFY(!must_match_once || matched);
  300. }
  301. void PathDataParser::parse_comma_whitespace()
  302. {
  303. if (match(',')) {
  304. consume();
  305. parse_whitespace();
  306. } else {
  307. parse_whitespace(1);
  308. if (match(','))
  309. consume();
  310. parse_whitespace();
  311. }
  312. }
  313. float PathDataParser::parse_fractional_constant()
  314. {
  315. StringBuilder builder;
  316. bool floating_point = false;
  317. while (!done() && isdigit(ch()))
  318. builder.append(consume());
  319. if (match('.')) {
  320. floating_point = true;
  321. builder.append('.');
  322. consume();
  323. while (!done() && isdigit(ch()))
  324. builder.append(consume());
  325. } else {
  326. VERIFY(builder.length() > 0);
  327. }
  328. if (floating_point)
  329. return strtof(builder.to_string().characters(), nullptr);
  330. return builder.to_string().to_int().value();
  331. }
  332. float PathDataParser::parse_number()
  333. {
  334. auto number = parse_fractional_constant();
  335. if (!match('e') && !match('E'))
  336. return number;
  337. consume();
  338. auto exponent_sign = parse_sign();
  339. StringBuilder exponent_builder;
  340. while (!done() && isdigit(ch()))
  341. exponent_builder.append(consume());
  342. VERIFY(exponent_builder.length() > 0);
  343. auto exponent = exponent_builder.to_string().to_int().value();
  344. // Fast path: If the number is 0, there's no point in computing the exponentiation.
  345. if (number == 0)
  346. return number;
  347. if (exponent_sign < 0) {
  348. for (int i = 0; i < exponent; ++i) {
  349. number /= 10;
  350. }
  351. } else if (exponent_sign > 0) {
  352. for (int i = 0; i < exponent; ++i) {
  353. number *= 10;
  354. }
  355. }
  356. return number;
  357. }
  358. float PathDataParser::parse_flag()
  359. {
  360. if (!match('0') && !match('1'))
  361. VERIFY_NOT_REACHED();
  362. return consume() - '0';
  363. }
  364. int PathDataParser::parse_sign()
  365. {
  366. if (match('-')) {
  367. consume();
  368. return -1;
  369. }
  370. if (match('+'))
  371. consume();
  372. return 1;
  373. }
  374. bool PathDataParser::match_whitespace() const
  375. {
  376. if (done())
  377. return false;
  378. char c = ch();
  379. return c == 0x9 || c == 0x20 || c == 0xa || c == 0xc || c == 0xd;
  380. }
  381. bool PathDataParser::match_comma_whitespace() const
  382. {
  383. return match_whitespace() || match(',');
  384. }
  385. bool PathDataParser::match_coordinate() const
  386. {
  387. return !done() && (isdigit(ch()) || ch() == '-' || ch() == '+' || ch() == '.');
  388. }
  389. SVGPathElement::SVGPathElement(DOM::Document& document, QualifiedName qualified_name)
  390. : SVGGeometryElement(document, move(qualified_name))
  391. {
  392. }
  393. void SVGPathElement::parse_attribute(const FlyString& name, const String& value)
  394. {
  395. SVGGeometryElement::parse_attribute(name, value);
  396. if (name == "d")
  397. m_instructions = PathDataParser(value).parse();
  398. }
  399. Gfx::Path& SVGPathElement::get_path()
  400. {
  401. if (m_path.has_value())
  402. return m_path.value();
  403. Gfx::Path path;
  404. PathInstructionType last_instruction = PathInstructionType::Invalid;
  405. for (auto& instruction : m_instructions) {
  406. // If the first path element uses relative coordinates, we treat them as absolute by making them relative to (0, 0).
  407. auto last_point = path.segments().is_empty() ? Gfx::FloatPoint { 0, 0 } : path.segments().last().point();
  408. auto& absolute = instruction.absolute;
  409. auto& data = instruction.data;
  410. if constexpr (PATH_DEBUG) {
  411. print_instruction(instruction);
  412. }
  413. bool clear_last_control_point = true;
  414. switch (instruction.type) {
  415. case PathInstructionType::Move: {
  416. Gfx::FloatPoint point = { data[0], data[1] };
  417. if (absolute) {
  418. path.move_to(point);
  419. } else {
  420. path.move_to(point + last_point);
  421. }
  422. break;
  423. }
  424. case PathInstructionType::ClosePath:
  425. path.close();
  426. break;
  427. case PathInstructionType::Line: {
  428. Gfx::FloatPoint point = { data[0], data[1] };
  429. if (absolute) {
  430. path.line_to(point);
  431. } else {
  432. path.line_to(point + last_point);
  433. }
  434. break;
  435. }
  436. case PathInstructionType::HorizontalLine: {
  437. if (absolute)
  438. path.line_to(Gfx::FloatPoint { data[0], last_point.y() });
  439. else
  440. path.line_to(Gfx::FloatPoint { data[0] + last_point.x(), last_point.y() });
  441. break;
  442. }
  443. case PathInstructionType::VerticalLine: {
  444. if (absolute)
  445. path.line_to(Gfx::FloatPoint { last_point.x(), data[0] });
  446. else
  447. path.line_to(Gfx::FloatPoint { last_point.x(), data[0] + last_point.y() });
  448. break;
  449. }
  450. case PathInstructionType::EllipticalArc: {
  451. double rx = data[0];
  452. double ry = data[1];
  453. double x_axis_rotation = double { data[2] } * M_DEG2RAD;
  454. double large_arc_flag = data[3];
  455. double sweep_flag = data[4];
  456. Gfx::FloatPoint next_point;
  457. if (absolute)
  458. next_point = { data[5], data[6] };
  459. else
  460. next_point = { data[5] + last_point.x(), data[6] + last_point.y() };
  461. path.elliptical_arc_to(next_point, { rx, ry }, x_axis_rotation, large_arc_flag != 0, sweep_flag != 0);
  462. break;
  463. }
  464. case PathInstructionType::QuadraticBezierCurve: {
  465. clear_last_control_point = false;
  466. Gfx::FloatPoint through = { data[0], data[1] };
  467. Gfx::FloatPoint point = { data[2], data[3] };
  468. if (absolute) {
  469. path.quadratic_bezier_curve_to(through, point);
  470. m_previous_control_point = through;
  471. } else {
  472. auto control_point = through + last_point;
  473. path.quadratic_bezier_curve_to(control_point, point + last_point);
  474. m_previous_control_point = control_point;
  475. }
  476. break;
  477. }
  478. case PathInstructionType::SmoothQuadraticBezierCurve: {
  479. clear_last_control_point = false;
  480. if (m_previous_control_point.is_null()
  481. || ((last_instruction != PathInstructionType::QuadraticBezierCurve) && (last_instruction != PathInstructionType::SmoothQuadraticBezierCurve))) {
  482. m_previous_control_point = last_point;
  483. }
  484. auto dx_end_control = last_point.dx_relative_to(m_previous_control_point);
  485. auto dy_end_control = last_point.dy_relative_to(m_previous_control_point);
  486. auto control_point = Gfx::FloatPoint { last_point.x() + dx_end_control, last_point.y() + dy_end_control };
  487. Gfx::FloatPoint end_point = { data[0], data[1] };
  488. if (absolute) {
  489. path.quadratic_bezier_curve_to(control_point, end_point);
  490. } else {
  491. path.quadratic_bezier_curve_to(control_point, end_point + last_point);
  492. }
  493. m_previous_control_point = control_point;
  494. break;
  495. }
  496. case PathInstructionType::Curve: {
  497. clear_last_control_point = false;
  498. Gfx::FloatPoint c1 = { data[0], data[1] };
  499. Gfx::FloatPoint c2 = { data[2], data[3] };
  500. Gfx::FloatPoint p2 = { data[4], data[5] };
  501. if (!absolute) {
  502. p2 += last_point;
  503. c1 += last_point;
  504. c2 += last_point;
  505. }
  506. path.cubic_bezier_curve_to(c1, c2, p2);
  507. m_previous_control_point = c2;
  508. break;
  509. }
  510. case PathInstructionType::SmoothCurve: {
  511. clear_last_control_point = false;
  512. if (m_previous_control_point.is_null()
  513. || ((last_instruction != PathInstructionType::Curve) && (last_instruction != PathInstructionType::SmoothCurve))) {
  514. m_previous_control_point = last_point;
  515. }
  516. auto reflected_previous_control_x = last_point.dx_relative_to(m_previous_control_point);
  517. auto reflected_previous_control_y = last_point.dy_relative_to(m_previous_control_point);
  518. Gfx::FloatPoint c1 = Gfx::FloatPoint { reflected_previous_control_x, reflected_previous_control_y };
  519. Gfx::FloatPoint c2 = { data[0], data[1] };
  520. Gfx::FloatPoint p2 = { data[2], data[3] };
  521. if (!absolute) {
  522. p2 += last_point;
  523. c1 += last_point;
  524. c2 += last_point;
  525. }
  526. path.cubic_bezier_curve_to(c1, c2, p2);
  527. m_previous_control_point = c2;
  528. break;
  529. }
  530. case PathInstructionType::Invalid:
  531. VERIFY_NOT_REACHED();
  532. }
  533. if (clear_last_control_point) {
  534. m_previous_control_point = Gfx::FloatPoint {};
  535. }
  536. last_instruction = instruction.type;
  537. }
  538. m_path = path;
  539. return m_path.value();
  540. }
  541. }