File indexing completed on 2025-01-05 04:01:19
0001 /* 0002 * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia <dev@dragon.best> 0003 * 0004 * SPDX-License-Identifier: GPL-3.0-or-later 0005 */ 0006 0007 #pragma once 0008 0009 #include <variant> 0010 0011 #include <QString> 0012 #include <QPointF> 0013 #include <QVector> 0014 0015 #include "utils/regexp.hpp" 0016 #include "math/bezier/bezier.hpp" 0017 #include "math/ellipse_solver.hpp" 0018 0019 namespace glaxnimate::io::svg::detail { 0020 0021 class PathDParser 0022 { 0023 public: 0024 enum TokenType 0025 { 0026 Command, 0027 Parameter, 0028 }; 0029 using Token = std::variant<ushort, qreal>; 0030 0031 private: 0032 struct Lexer 0033 { 0034 Lexer(const QString& d, std::vector<Token>& tokens) 0035 : d(d), tokens(tokens) 0036 {} 0037 0038 QString d; 0039 int off = 0; 0040 std::vector<Token>& tokens; 0041 QString lexed; 0042 QChar ch; 0043 0044 bool eof() const 0045 { 0046 return off >= d.size(); 0047 } 0048 0049 bool next() 0050 { 0051 ++off; 0052 if ( !eof() ) 0053 { 0054 ch = d[off]; 0055 return true; 0056 } 0057 0058 ch = {}; 0059 return false; 0060 } 0061 0062 void lex() 0063 { 0064 static QString cmds = "MLHVCSQTAZ"; 0065 0066 ch = d[off]; 0067 0068 while ( off < d.size() ) 0069 { 0070 if ( cmds.contains(ch.toUpper()) ) 0071 { 0072 tokens.emplace_back(ch.unicode()); 0073 next(); 0074 } 0075 else if ( ch.isSpace() || ch == ',') 0076 { 0077 next(); 0078 } 0079 else 0080 { 0081 lex_value(); 0082 } 0083 } 0084 } 0085 0086 void lex_value() 0087 { 0088 lexed.clear(); 0089 0090 if ( ch == '+' || ch == '-' ) 0091 { 0092 lexed += ch; 0093 if ( !next() ) 0094 return; 0095 } 0096 0097 if ( ch.isDigit() ) 0098 lex_value_int(); 0099 0100 if ( ch == '.' ) 0101 { 0102 lexed += ch; 0103 if ( !next() ) 0104 return; 0105 lex_value_decimal(); 0106 } 0107 else if ( ch.toUpper() == 'E' ) 0108 { 0109 lexed += ch; 0110 if ( !next() ) 0111 return; 0112 lex_value_exponent(); 0113 } 0114 0115 if ( lexed.isEmpty() ) 0116 { 0117 next(); 0118 return; 0119 } 0120 0121 tokens.emplace_back(lexed.toDouble()); 0122 lexed.clear(); 0123 } 0124 0125 void lex_value_int() 0126 { 0127 while ( off < d.size() && ch.isDigit() ) 0128 { 0129 lexed += ch; 0130 next(); 0131 } 0132 } 0133 0134 void lex_value_decimal() 0135 { 0136 lex_value_int(); 0137 0138 if ( ch.toUpper() == 'E' ) 0139 { 0140 lexed += ch; 0141 if ( !next() ) 0142 return; 0143 lex_value_exponent(); 0144 } 0145 } 0146 0147 void lex_value_exponent() 0148 { 0149 if ( ch == '+' || ch == '-' ) 0150 { 0151 lexed += ch; 0152 if ( !next() ) 0153 return; 0154 } 0155 0156 lex_value_int(); 0157 } 0158 0159 }; 0160 0161 public: 0162 PathDParser(const QString& d) 0163 { 0164 tokenize(d); 0165 } 0166 0167 const math::bezier::MultiBezier& parse() 0168 { 0169 while ( !eof() ) 0170 { 0171 if ( la_type() == Command ) 0172 { 0173 ushort cmd = std::get<Command>(la()); 0174 next_token(); 0175 parse_command(cmd); 0176 } 0177 else 0178 { 0179 parse_command(implicit); 0180 } 0181 } 0182 0183 return bez; 0184 } 0185 0186 private: 0187 const Token& la() const { return tokens[index]; } 0188 void next_token() { ++index; } 0189 bool eof() const { return index >= int(tokens.size()); } 0190 TokenType la_type() const { return TokenType(la().index()); } 0191 0192 void tokenize(const QString& d) 0193 { 0194 if ( d.isEmpty() ) 0195 return; 0196 0197 Lexer(d, tokens).lex(); 0198 } 0199 0200 qreal read_param() 0201 { 0202 if ( la_type() == Parameter ) 0203 { 0204 qreal v = std::get<Parameter>(la()); 0205 next_token(); 0206 return v; 0207 } 0208 return 0; 0209 } 0210 0211 QPointF read_vector() 0212 { 0213 return {read_param(), read_param()}; 0214 } 0215 0216 void parse_M() 0217 { 0218 if ( la_type() != Parameter ) 0219 { 0220 next_token(); 0221 return; 0222 } 0223 0224 p = read_vector(); 0225 bez.move_to(p); 0226 implicit = 'L'; 0227 } 0228 0229 void parse_m() 0230 { 0231 if ( la_type() != Parameter ) 0232 { 0233 next_token(); 0234 return; 0235 } 0236 0237 p += read_vector(); 0238 bez.move_to(p); 0239 implicit = 'l'; 0240 } 0241 0242 void parse_L() 0243 { 0244 if ( la_type() != Parameter ) 0245 { 0246 next_token(); 0247 return; 0248 } 0249 0250 p = read_vector(); 0251 bez.line_to(p); 0252 implicit = 'L'; 0253 } 0254 0255 void parse_l() 0256 { 0257 if ( la_type() != Parameter ) 0258 { 0259 next_token(); 0260 return; 0261 } 0262 0263 p += read_vector(); 0264 bez.line_to(p); 0265 implicit = 'l'; 0266 } 0267 0268 void parse_H() 0269 { 0270 if ( la_type() != Parameter ) 0271 { 0272 next_token(); 0273 return; 0274 } 0275 0276 p.setX(read_param()); 0277 bez.line_to(p); 0278 implicit = 'H'; 0279 } 0280 0281 void parse_h() 0282 { 0283 if ( la_type() != Parameter ) 0284 { 0285 next_token(); 0286 return; 0287 } 0288 0289 p.setX(p.x() + read_param()); 0290 bez.line_to(p); 0291 implicit = 'h'; 0292 } 0293 0294 void parse_V() 0295 { 0296 if ( la_type() != Parameter ) 0297 { 0298 next_token(); 0299 return; 0300 } 0301 0302 p.setY(read_param()); 0303 bez.line_to(p); 0304 implicit = 'V'; 0305 } 0306 0307 void parse_v() 0308 { 0309 if ( la_type() != Parameter ) 0310 { 0311 next_token(); 0312 return; 0313 } 0314 0315 p.setY(p.y() + read_param()); 0316 bez.line_to(p); 0317 implicit = 'v'; 0318 } 0319 0320 void parse_C() 0321 { 0322 if ( la_type() != Parameter ) 0323 { 0324 next_token(); 0325 return; 0326 } 0327 QPointF tan_out = read_vector(); 0328 QPointF tan_in = read_vector(); 0329 p = read_vector(); 0330 bez.cubic_to(tan_out, tan_in, p); 0331 implicit = 'C'; 0332 } 0333 0334 void parse_c() 0335 { 0336 if ( la_type() != Parameter ) 0337 { 0338 next_token(); 0339 return; 0340 } 0341 QPointF tan_out = p + read_vector(); 0342 QPointF tan_in = p + read_vector(); 0343 p += read_vector(); 0344 bez.cubic_to(tan_out, tan_in, p); 0345 implicit = 'c'; 0346 } 0347 0348 void parse_S() 0349 { 0350 if ( la_type() != Parameter ) 0351 { 0352 next_token(); 0353 return; 0354 } 0355 0356 QPointF old_p = p; 0357 QPointF tan_in = read_vector(); 0358 p = read_vector(); 0359 0360 if ( bez.beziers().empty() || bez.beziers().back().empty() ) 0361 { 0362 bez.cubic_to(old_p, tan_in, p); 0363 } 0364 else 0365 { 0366 auto& prev = bez.beziers().back().points().back(); 0367 QPointF tan_out = prev.pos - prev.relative_tan_in(); 0368 prev.type = math::bezier::Symmetrical; 0369 bez.cubic_to(tan_out, tan_in, p); 0370 } 0371 0372 implicit = 'S'; 0373 } 0374 0375 void parse_s() 0376 { 0377 if ( la_type() != Parameter ) 0378 { 0379 next_token(); 0380 return; 0381 } 0382 0383 QPointF old_p = p; 0384 QPointF tan_in = p+read_vector(); 0385 p += read_vector(); 0386 0387 if ( bez.beziers().empty() || bez.beziers().back().empty() ) 0388 { 0389 bez.cubic_to(old_p, tan_in, p); 0390 } 0391 else 0392 { 0393 auto& prev = bez.beziers().back().points().back(); 0394 QPointF tan_out = prev.pos - prev.relative_tan_in(); 0395 prev.type = math::bezier::Symmetrical; 0396 bez.cubic_to(tan_out, tan_in, p); 0397 } 0398 0399 implicit = 's'; 0400 } 0401 0402 void parse_Q() 0403 { 0404 if ( la_type() != Parameter ) 0405 { 0406 next_token(); 0407 return; 0408 } 0409 QPointF tan = read_vector(); 0410 p = read_vector(); 0411 bez.quadratic_to(tan, p); 0412 implicit = 'Q'; 0413 } 0414 0415 void parse_q() 0416 { 0417 if ( la_type() != Parameter ) 0418 { 0419 next_token(); 0420 return; 0421 } 0422 QPointF tan = p+read_vector(); 0423 p += read_vector(); 0424 bez.quadratic_to(tan, p); 0425 implicit = 'q'; 0426 } 0427 0428 void parse_T() 0429 { 0430 if ( la_type() != Parameter ) 0431 { 0432 next_token(); 0433 return; 0434 } 0435 0436 QPointF old_p = p; 0437 p = read_vector(); 0438 0439 if ( bez.beziers().empty() || bez.beziers().back().empty() ) 0440 { 0441 bez.quadratic_to(old_p, p); 0442 } 0443 else 0444 { 0445 auto& prev = bez.beziers().back().points().back(); 0446 QPointF tan_out = prev.pos - prev.relative_tan_in(); 0447 prev.type = math::bezier::Symmetrical; 0448 bez.quadratic_to(tan_out, p); 0449 } 0450 0451 implicit = 'T'; 0452 } 0453 0454 void parse_t() 0455 { 0456 if ( la_type() != Parameter ) 0457 { 0458 next_token(); 0459 return; 0460 } 0461 0462 QPointF old_p = p; 0463 p += read_vector(); 0464 0465 if ( bez.beziers().empty() || bez.beziers().back().empty() ) 0466 { 0467 bez.quadratic_to(old_p, p); 0468 } 0469 else 0470 { 0471 auto& prev = bez.beziers().back().points().back(); 0472 QPointF tan_out = prev.pos - prev.relative_tan_in(); 0473 prev.type = math::bezier::Symmetrical; 0474 bez.quadratic_to(tan_out, p); 0475 } 0476 0477 implicit = 't'; 0478 } 0479 0480 void do_arc(qreal rx, qreal ry, qreal xrot, bool large, bool sweep, const QPointF& dest) 0481 { 0482 if ( p == dest ) 0483 return; 0484 0485 // straight line 0486 if ( rx == 0 || ry == 0 ) 0487 { 0488 p = dest; 0489 bez.line_to(p); 0490 return; 0491 } 0492 0493 if ( bez.beziers().empty() || bez.beziers().back().empty() ) 0494 return; 0495 0496 math::bezier::Bezier points = math::EllipseSolver::from_svg_arc( 0497 p, rx, ry, xrot, large, sweep, dest 0498 ); 0499 0500 auto& target_points = bez.beziers().back().points(); 0501 target_points.back().tan_out = points[0].tan_out; 0502 target_points.insert(target_points.end(), points.begin()+1, points.end()); 0503 p = dest; 0504 } 0505 0506 void parse_A() 0507 { 0508 if ( la_type() != Parameter ) 0509 { 0510 next_token(); 0511 return; 0512 } 0513 0514 QPointF r = read_vector(); 0515 qreal xrot = read_param(); 0516 qreal large = read_param(); 0517 qreal sweep = read_param(); 0518 QPointF dest = read_vector(); 0519 0520 do_arc(r.x(), r.y(), xrot, large, sweep, dest); 0521 0522 implicit = 'A'; 0523 } 0524 0525 void parse_a() 0526 { 0527 if ( la_type() != Parameter ) 0528 { 0529 next_token(); 0530 return; 0531 } 0532 0533 QPointF r = read_vector(); 0534 qreal xrot = read_param(); 0535 qreal large = read_param(); 0536 qreal sweep = read_param(); 0537 QPointF dest = p + read_vector(); 0538 0539 do_arc(r.x(), r.y(), xrot, large, sweep, dest); 0540 0541 implicit = 'a'; 0542 } 0543 0544 void parse_command(ushort c) 0545 { 0546 switch ( c ) 0547 { 0548 case 'M': return parse_M(); 0549 case 'm': return parse_m(); 0550 case 'L': return parse_L(); 0551 case 'l': return parse_l(); 0552 case 'H': return parse_H(); 0553 case 'h': return parse_h(); 0554 case 'V': return parse_V(); 0555 case 'v': return parse_v(); 0556 case 'C': return parse_C(); 0557 case 'c': return parse_c(); 0558 case 'S': return parse_S(); 0559 case 's': return parse_s(); 0560 case 'Q': return parse_Q(); 0561 case 'q': return parse_q(); 0562 case 'T': return parse_T(); 0563 case 't': return parse_t(); 0564 case 'A': return parse_A(); 0565 case 'a': return parse_a(); 0566 case 'Z': 0567 case 'z': 0568 bez.close(); 0569 if ( !bez.empty() && !bez.back().empty() ) 0570 p = bez.back()[0].pos; 0571 break; 0572 default: 0573 next_token(); 0574 0575 } 0576 } 0577 0578 std::vector<Token> tokens; 0579 int index = 0; 0580 ushort implicit = 'M'; 0581 QPointF p{0, 0}; 0582 math::bezier::MultiBezier bez; 0583 }; 0584 0585 } // namespace glaxnimate::io::svg::detail