File indexing completed on 2025-01-05 04:01:20

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 <unordered_set>
0010 
0011 #include "utils/regexp.hpp"
0012 #include "utils/sort_gradient.hpp"
0013 #include "model/shapes/group.hpp"
0014 #include "model/shapes/layer.hpp"
0015 #include "model/shapes/precomp_layer.hpp"
0016 #include "model/shapes/rect.hpp"
0017 #include "model/shapes/ellipse.hpp"
0018 #include "model/shapes/path.hpp"
0019 #include "model/shapes/polystar.hpp"
0020 #include "model/shapes/fill.hpp"
0021 #include "model/shapes/stroke.hpp"
0022 #include "model/shapes/image.hpp"
0023 #include "model/shapes/text.hpp"
0024 #include "model/document.hpp"
0025 #include "model/assets/named_color.hpp"
0026 
0027 #include "math/math.hpp"
0028 #include "app/utils/string_view.hpp"
0029 
0030 #include "path_parser.hpp"
0031 #include "animate_parser.hpp"
0032 #include "parse_error.hpp"
0033 #include "font_weight.hpp"
0034 #include "css_parser.hpp"
0035 #include "detail.hpp"
0036 
0037 
0038 namespace glaxnimate::io::svg::detail {
0039 
0040 class SvgParserPrivate
0041 {
0042 public:
0043     using ShapeCollection = std::vector<std::unique_ptr<model::ShapeElement>>;
0044 
0045     struct ParseFuncArgs
0046     {
0047         const QDomElement& element;
0048         model::ShapeListProperty* shape_parent;
0049         const Style& parent_style;
0050         bool in_group;
0051     };
0052 
0053 
0054     SvgParserPrivate(
0055         model::Document* document,
0056         const std::function<void(const QString&)>& on_warning,
0057         ImportExport* io,
0058         QSize forced_size,
0059         model::FrameTime default_time
0060     ) : document(document),
0061         on_warning(on_warning),
0062         io(io),
0063         forced_size(forced_size),
0064         default_time(default_time == 0 ? 180 : default_time)
0065     {
0066         animate_parser.on_warning = on_warning;
0067     }
0068 
0069     void load(QIODevice* device)
0070     {
0071         SvgParseError err;
0072         if ( !dom.setContent(device, true, &err.message, &err.line, &err.column) )
0073             throw err;
0074     }
0075 
0076     virtual ~SvgParserPrivate() = default;
0077 
0078     void parse(model::Document* document = nullptr)
0079     {
0080         if ( document )
0081             this->document = document;
0082 
0083         if ( this->document->assets()->compositions->values.empty() )
0084             main = this->document->assets()->compositions->values.insert(std::make_unique<model::Composition>(this->document));
0085         else
0086             main = this->document->assets()->compositions->values[0];
0087         animate_parser.fps = main->fps.get();
0088 
0089         size = main->size();
0090         auto root = dom.documentElement();
0091 
0092         if ( forced_size.isValid() )
0093         {
0094             size = forced_size;
0095         }
0096         else
0097         {
0098             size = get_size(root);
0099         }
0100 
0101         to_process = 0;
0102         on_parse_prepare(root);
0103         if ( io )
0104             io->progress_max_changed(to_process);
0105 
0106         on_parse(root);
0107 
0108         write_document_data();
0109     }
0110 
0111 protected:
0112     QString attr(const QDomElement& e, const QString& ns, const QString& name, const QString& defval = {})
0113     {
0114         if ( ns.isEmpty() )
0115             return e.attribute(name, defval);
0116         return e.attributeNS(xmlns.at(ns), name, defval);
0117     }
0118 
0119     qreal len_attr(const QDomElement& e, const QString& name, qreal defval = 0)
0120     {
0121         if ( e.hasAttribute(name) )
0122             return parse_unit(e.attribute(name));
0123         return defval;
0124     }
0125 
0126     qreal parse_unit(const QString& svg_value)
0127     {
0128         QRegularExpressionMatch match = unit_re.match(svg_value);
0129         if ( match.hasMatch() )
0130         {
0131             qreal value = match.captured(1).toDouble();
0132             qreal mult = unit_multiplier(match.captured(2));
0133             if ( mult != 0 )
0134                 return value * mult;
0135         }
0136 
0137         warning(QString("Unknown length value %1").arg(svg_value));
0138         return 0;
0139     }
0140 
0141     qreal unit_multiplier(const QString& unit)
0142     {
0143         static const constexpr qreal cmin = 2.54;
0144 
0145         if ( unit == "px" || unit == "" || unit == "dp" || unit == "dip" || unit == "sp" )
0146             return 1;
0147         else if ( unit == "vw" )
0148             return size.width() * 0.01;
0149         else if ( unit == "vh" )
0150             return size.height() * 0.01;
0151         else if ( unit == "vmin" )
0152             return std::min(size.width(), size.height()) * 0.01;
0153         else if ( unit == "vmax" )
0154             return std::max(size.width(), size.height()) * 0.01;
0155         else if ( unit == "in" )
0156             return dpi;
0157         else if ( unit == "pc" )
0158             return dpi / 6;
0159         else if ( unit == "pt" )
0160             return dpi / 72;
0161         else if ( unit == "cm" )
0162             return dpi / cmin;
0163         else if ( unit == "mm" )
0164             return dpi / cmin / 10;
0165         else if ( unit == "Q" )
0166             return dpi / cmin / 40;
0167 
0168         return 0;
0169     }
0170 
0171     qreal unit_convert(qreal val, const QString& from, const QString& to)
0172     {
0173         return val * unit_multiplier(from) / unit_multiplier(to);
0174     }
0175 
0176     void warning(const QString& msg)
0177     {
0178         if ( on_warning )
0179             on_warning(msg);
0180     }
0181 
0182     model::Layer* add_layer(model::ShapeListProperty* parent)
0183     {
0184         model::Layer* lay = new model::Layer(document);
0185         parent->insert(std::unique_ptr<model::Layer>(lay));
0186         layers.push_back(lay);
0187         return lay;
0188     }
0189 
0190     QStringList split_attr(const QDomElement& e, const QString& name)
0191     {
0192         return e.attribute(name).split(AnimateParser::separator,
0193 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
0194         Qt::SkipEmptyParts
0195 #else
0196         QString::SkipEmptyParts
0197 #endif
0198         );
0199     }
0200 
0201     void parse_children(const ParseFuncArgs& args)
0202     {
0203         for ( const auto& domnode : ItemCountRange(args.element.childNodes()) )
0204         {
0205             if ( domnode.isElement() )
0206             {
0207                 auto child = domnode.toElement();
0208                 parse_shape({child, args.shape_parent, args.parent_style, args.in_group});
0209             }
0210         }
0211     }
0212 
0213     template<class T>
0214     T* push(ShapeCollection& sc)
0215     {
0216         T* t = new T(document);
0217         sc.emplace_back(t);
0218         return t;
0219     }
0220 
0221     void populate_ids(const QDomElement& elem)
0222     {
0223         if ( elem.hasAttribute("id") )
0224             map_ids[elem.attribute("id")] = elem;
0225 
0226         for ( const auto& domnode : ItemCountRange(elem.childNodes()) )
0227         {
0228             if ( domnode.isElement() )
0229                 populate_ids(domnode.toElement());
0230         }
0231     }
0232 
0233     QDomElement element_by_id(const QString& id)
0234     {
0235         // dom.elementById() doesn't work ;_;
0236         if ( map_ids.empty() )
0237             populate_ids(dom.documentElement());
0238         auto it = map_ids.find(id);
0239         if ( it == map_ids.end() )
0240             return {};
0241         return it->second;
0242     }
0243 
0244     void mark_progress()
0245     {
0246         processed++;
0247         if ( io && processed % 10 == 0 )
0248             io->progress(processed);
0249     }
0250 
0251     QDomElement query_element(const std::vector<QString>& path, const QDomElement& parent, std::size_t index = 0)
0252     {
0253         if ( index >= path.size() )
0254             return parent;
0255 
0256 
0257         auto head = path[index];
0258         for ( const auto& domnode : ItemCountRange(parent.childNodes()) )
0259         {
0260             if ( domnode.isElement() )
0261             {
0262                 auto child = domnode.toElement();
0263                 if ( child.tagName() == head )
0264                     return query_element(path, child, index+1);
0265             }
0266         }
0267         return {};
0268     }
0269 
0270     QString query(const std::vector<QString>& path, const QDomElement& parent, std::size_t index = 0)
0271     {
0272         return query_element(path, parent, index).text();
0273     }
0274 
0275     std::vector<qreal> double_args(const QString& str)
0276     {
0277         auto args_s = ::utils::split_ref(str, AnimateParser::separator,
0278 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
0279             Qt::SkipEmptyParts
0280 #else
0281             QString::SkipEmptyParts
0282 #endif
0283         );
0284         std::vector<qreal> args;
0285         args.reserve(args_s.size());
0286         std::transform(args_s.begin(), args_s.end(), std::back_inserter(args),
0287                         [](const ::utils::StringView& s){ return s.toDouble(); });
0288         return args;
0289     }
0290 
0291     // parse attributes like opacity where it's a value in [0-1] or a percentage
0292     static double percent_1(const QString& s)
0293     {
0294         if ( s.contains('%') )
0295             return ::utils::mid_ref(s, 0, s.size()-1).toDouble() / 100;
0296         return s.toDouble();
0297     }
0298 
0299     static model::Stroke::Cap line_cap(const QString& linecap)
0300     {
0301         if ( linecap == "round" )
0302             return model::Stroke::RoundCap;
0303         else if ( linecap == "butt" )
0304             return model::Stroke::ButtCap;
0305         else if ( linecap == "square" )
0306             return model::Stroke::SquareCap;
0307 
0308         return model::Stroke::ButtCap;
0309     }
0310 
0311     static model::Stroke::Join line_join(const QString& linecap)
0312     {
0313         if ( linecap == "round" )
0314             return model::Stroke::RoundJoin;
0315         else if ( linecap == "bevel" )
0316             return model::Stroke::BevelJoin;
0317         else if ( linecap == "miter" )
0318             return model::Stroke::MiterJoin;
0319 
0320         return model::Stroke::MiterJoin;
0321     }
0322 
0323     void path_animation(
0324         const std::vector<model::Path*>& paths,
0325         const AnimatedProperties& anim,
0326         const QString& attr
0327     )
0328     {
0329         if ( !paths.empty() )
0330         {
0331             for ( const auto& kf : anim.single(attr) )
0332             {
0333                 for ( int i = 0; i < math::min<int>(kf.values.bezier().size(), paths.size()); i++ )
0334                     paths[i]->shape.set_keyframe(kf.time, kf.values.bezier()[i])->set_transition(kf.transition);
0335             }
0336         }
0337     }
0338 
0339 private:
0340     void write_document_data()
0341     {
0342         main->width.set(size.width());
0343         main->height.set(size.height());
0344 
0345         if ( !animate_parser.kf_range_initialized )
0346         {
0347             animate_parser.min_kf = 0;
0348             animate_parser.max_kf = default_time;
0349         }
0350         else
0351         {
0352             animate_parser.max_kf = qRound(animate_parser.max_kf);
0353         }
0354 
0355         main->animation->first_frame.set(animate_parser.min_kf);
0356         main->animation->last_frame.set(animate_parser.max_kf);
0357         for ( auto lay : layers )
0358         {
0359             lay->animation->last_frame.set(animate_parser.min_kf);
0360             lay->animation->last_frame.set(animate_parser.max_kf);
0361         }
0362 
0363         document->undo_stack().clear();
0364     }
0365 
0366 protected:
0367     virtual void on_parse_prepare(const QDomElement& root) = 0;
0368     virtual QSizeF get_size(const QDomElement& root) = 0;
0369     virtual void parse_shape(const ParseFuncArgs& args) = 0;
0370     virtual void on_parse(const QDomElement& root) = 0;
0371 
0372     QDomDocument dom;
0373 
0374     qreal dpi = 96;
0375     QSizeF size;
0376 
0377     model::Document* document;
0378 
0379     AnimateParser animate_parser;
0380     std::function<void(const QString&)> on_warning;
0381     std::unordered_map<QString, QDomElement> map_ids;
0382     std::unordered_map<QString, model::BrushStyle*> brush_styles;
0383     std::unordered_map<QString, model::GradientColors*> gradients;
0384     std::vector<model::Layer*> layers;
0385 
0386     int to_process = 0;
0387     int processed = 0;
0388     ImportExport* io = nullptr;
0389     QSize forced_size;
0390     model::FrameTime default_time;
0391     model::Composition* main = nullptr;
0392 
0393     static const QRegularExpression unit_re;
0394 };
0395 
0396 } // namespace glaxnimate::io::svg::detail