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