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

0001 /*
0002  * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia <dev@dragon.best>
0003  *
0004  * SPDX-License-Identifier: GPL-3.0-or-later
0005  */
0006 
0007 #include "avd_parser.hpp"
0008 
0009 #include <QPalette>
0010 
0011 #include "io/svg/svg_parser_private.hpp"
0012 #include "model/shapes/trim.hpp"
0013 
0014 using namespace glaxnimate::io::svg;
0015 using namespace glaxnimate::io::svg::detail;
0016 
0017 class glaxnimate::io::avd::AvdParser::Private : public svg::detail::SvgParserPrivate
0018 {
0019 public:
0020     Private(
0021         const QDir& resource_path,
0022         model::Document* document,
0023         const std::function<void(const QString&)>& on_warning,
0024         ImportExport* io,
0025         QSize forced_size,
0026         model::FrameTime default_time
0027     ) : SvgParserPrivate(document, on_warning, io, forced_size, default_time),
0028         resource_path(resource_path)
0029     {}
0030 
0031 protected:
0032     void on_parse_prepare(const QDomElement&) override
0033     {
0034         for ( const auto& p : shape_parsers )
0035             to_process += dom.elementsByTagName(p.first).count();
0036 
0037 
0038         for ( const auto& target : ElementRange(dom.elementsByTagName("target")) )
0039         {
0040             QString name = target.attribute("name");
0041             if ( name.isEmpty() )
0042                 continue;
0043 
0044             for ( const auto& attr : ElementRange(target) )
0045             {
0046                 if ( attr.tagName() != "attr" || !attr.attribute("name").endsWith("animation") )
0047                     continue;
0048 
0049                 auto iter = animations.find(name);
0050                 if ( iter == animations.end() )
0051                     iter = animations.insert({name, {}}).first;
0052 
0053                 auto& props = iter->second;
0054 
0055                 for ( const auto& anim : ElementRange(attr.elementsByTagName("objectAnimator")) )
0056                 {
0057                     parse_animator(props, anim);
0058                 }
0059             }
0060         }
0061     }
0062 
0063     QSizeF get_size(const QDomElement& svg) override
0064     {
0065         return {
0066             len_attr(svg, "width", size.width()),
0067             len_attr(svg, "height", size.height())
0068         };
0069     }
0070 
0071     void on_parse(const QDomElement& root) override
0072     {
0073         static const Style default_style(Style::Map{
0074             {"fillColor", "black"},
0075         });
0076 
0077         if ( root.tagName() == "vector" )
0078         {
0079             parse_vector({root, &main->shapes, default_style, false});
0080         }
0081         else
0082         {
0083             if ( root.hasAttribute("drawable") )
0084             {
0085                 if ( auto res = get_resource(root.attribute("drawable")) )
0086                 {
0087                     if ( res->element.tagName() == "vector" )
0088                         parse_vector({res->element, &main->shapes, default_style, false});
0089                 }
0090             }
0091 
0092             for ( const auto& ch : ElementRange(root) )
0093             {
0094                 if ( ch.tagName() == "attr" && ch.attribute("name").endsWith("drawable") )
0095                 {
0096                     for ( const auto& e : ElementRange(ch) )
0097                         if ( e.tagName() == "vector" )
0098                             parse_vector({e, &main->shapes, default_style, false});
0099                 }
0100             }
0101         }
0102 
0103         main->name.set(
0104             attr(root, "android", "name", "")
0105         );
0106     }
0107 
0108     void parse_shape(const ParseFuncArgs& args) override
0109     {
0110         auto it = shape_parsers.find(args.element.tagName());
0111         if ( it != shape_parsers.end() )
0112         {
0113             mark_progress();
0114             (this->*it->second)(args);
0115         }
0116     }
0117 
0118 private:
0119     struct Resource
0120     {
0121         QString name;
0122         QDomElement element;
0123         model::Asset* asset = nullptr;
0124     };
0125 
0126     void parse_vector(const ParseFuncArgs& args)
0127     {
0128         QPointF pos;
0129         QVector2D scale{1, 1};
0130 
0131         model::Layer* layer = add_layer(args.shape_parent);
0132         set_name(layer, args.element);
0133 
0134         if ( args.element.hasAttribute("viewportWidth") && args.element.hasAttribute("viewportHeight") )
0135         {
0136             qreal vbw = len_attr(args.element, "viewportWidth");
0137             qreal vbh = len_attr(args.element, "viewportHeight");
0138 
0139             if ( !forced_size.isValid() )
0140             {
0141                 if ( !args.element.hasAttribute("width") )
0142                     size.setWidth(vbw);
0143                 if ( !args.element.hasAttribute("height") )
0144                     size.setHeight(vbh);
0145             }
0146 
0147             if ( vbw != 0 && vbh != 0 )
0148             {
0149                 scale = QVector2D(size.width() / vbw, size.height() / vbh);
0150 
0151                 if ( forced_size.isValid() )
0152                 {
0153                     auto single = qMin(scale.x(), scale.y());
0154                     scale = QVector2D(single, single);
0155                 }
0156             }
0157         }
0158 
0159         layer->transform.get()->position.set(-pos);
0160         layer->transform.get()->scale.set(scale);
0161 
0162         parse_children({args.element, &layer->shapes, args.parent_style, false});
0163     }
0164 
0165     void parse_animator(AnimateParser::AnimatedProperties& props, const QDomElement& anim)
0166     {
0167         model::FrameTime start_time = qRound(anim.attribute("startOffset", "0").toDouble() / 1000 * animate_parser.fps);
0168         model::FrameTime end_time = qRound(start_time + anim.attribute("duration", "0").toDouble() / 1000 * animate_parser.fps);
0169         animate_parser.register_time_range(start_time, end_time);
0170         std::vector<AnimatedProperty*> updated_props;
0171 
0172         QString name = anim.attribute("propertyName");
0173         if ( !name.isEmpty() )
0174         {
0175             auto& prop = props.properties[name];
0176             updated_props.push_back(&prop);
0177             parse_animated_prop(prop, name, anim, start_time, end_time);
0178         }
0179 
0180         for ( const auto& value_holder : ElementRange(anim) )
0181         {
0182             if ( value_holder.tagName() != "propertyValuesHolder" )
0183                 continue;
0184 
0185             name = value_holder.attribute("propertyName");
0186             if ( !name.isEmpty() )
0187             {
0188                 auto& prop = props.properties[name];
0189                 updated_props.push_back(&prop);
0190                 parse_animated_prop(prop, name, value_holder, start_time, end_time);
0191             }
0192         }
0193 
0194         for ( auto prop : updated_props )
0195             prop->sort();
0196     }
0197 
0198     model::KeyframeTransition interpolator(const QString& interpolator)
0199     {
0200         using Type = model::KeyframeTransition::Descriptive;
0201 
0202         if ( interpolator == "@android:interpolator/fast_out_slow_in" )
0203             return model::KeyframeTransition(Type::Fast, Type::Ease);
0204         if ( interpolator == "@android:interpolator/fast_out_linear_in" )
0205             return model::KeyframeTransition(Type::Fast, Type::Linear);
0206         if ( interpolator == "@android:interpolator/linear_out_slow_in" )
0207             return model::KeyframeTransition(Type::Linear, Type::Ease);
0208         if ( interpolator == "@android:anim/accelerate_decelerate_interpolator" )
0209             return model::KeyframeTransition(Type::Ease, Type::Ease);
0210         if ( interpolator == "@android:anim/accelerate_interpolator" )
0211             return model::KeyframeTransition(Type::Ease, Type::Fast);
0212         if ( interpolator == "@android:anim/decelerate_interpolator" )
0213             return model::KeyframeTransition(Type::Fast, Type::Ease);
0214         if ( interpolator == "@android:anim/linear_interpolator" )
0215             return model::KeyframeTransition(Type::Linear, Type::Linear);
0216 
0217         // TODO?
0218         // @android:anim/anticipate_interpolator
0219         // @android:anim/overshoot_interpolator
0220         // @android:anim/bounce_interpolator
0221         // @android:anim/anticipate_overshoot_interpolator
0222         if ( interpolator != "" )
0223             warning(i18n("Unknown interpolator %s", interpolator));
0224 
0225         return model::KeyframeTransition(Type::Ease, Type::Ease);
0226     }
0227 
0228     ValueVariant parse_animated_value(const QString& value, ValueVariant::Type type)
0229     {
0230         switch ( type )
0231         {
0232             case ValueVariant::Vector:
0233                 return value.toDouble();
0234             case ValueVariant::Bezier:
0235                 return PathDParser(value).parse();
0236             case ValueVariant::String:
0237                 return value;
0238             case ValueVariant::Color:
0239                 return parse_color(value);
0240         }
0241 
0242         return {};
0243     }
0244 
0245     void parse_animated_prop(
0246         AnimatedProperty& prop,
0247         const QString& name,
0248         const QDomElement& value_holder,
0249         model::FrameTime start_time,
0250         model::FrameTime end_time
0251     )
0252     {
0253         static model::KeyframeTransition transition;
0254 
0255         ValueVariant::Type type = ValueVariant::Vector;
0256         if ( name == "pathData" )
0257             type = ValueVariant::Bezier;
0258         else if ( name.endsWith("Color") )
0259             type = ValueVariant::Color;
0260 
0261         if ( value_holder.hasAttribute("valueFrom") )
0262         {
0263             prop.keyframes.push_back({
0264                 start_time,
0265                 parse_animated_value(value_holder.attribute("valueFrom"), type),
0266                 interpolator(value_holder.attribute("interpolator"))
0267             });
0268         }
0269 
0270         if ( value_holder.hasAttribute("valueTo") )
0271         {
0272             prop.keyframes.push_back({
0273                 end_time,
0274                 parse_animated_value(value_holder.attribute("valueTo"), type),
0275                 model::KeyframeTransition(model::KeyframeTransition::Ease)
0276             });
0277         }
0278 
0279         for ( const auto& kf : ElementRange(value_holder) )
0280         {
0281             if ( kf.tagName() != "keyframe" )
0282                 continue;
0283 
0284             auto fraction = kf.attribute("fraction").toDouble();
0285 
0286             prop.keyframes.push_back({
0287                 math::lerp(start_time, end_time, fraction),
0288                 parse_animated_value(kf.attribute("value"), type),
0289                 interpolator(kf.attribute("interpolator"))
0290             });
0291         }
0292     }
0293 
0294     void add_shapes(const ParseFuncArgs& args, ShapeCollection&& shapes)
0295     {
0296         Style style = parse_style(args.element, args.parent_style);
0297         auto group = std::make_unique<model::Group>(document);
0298 
0299 //         apply_common_style(group.get(), args.element, style);
0300 
0301         set_name(group.get(), args.element);
0302 
0303         add_style_shapes(args, &group->shapes, style);
0304 
0305         for ( auto& shape : shapes )
0306             group->shapes.insert(std::move(shape));
0307 
0308         args.shape_parent->insert(std::move(group));
0309     }
0310 
0311     Style parse_style(const QDomElement& element, const Style& parent_style)
0312     {
0313         Style style = parent_style;
0314 
0315         for ( const auto& domnode : ItemCountRange(element.attributes()) )
0316         {
0317             auto attr = domnode.toAttr();
0318             if ( style_atrrs.count(attr.name()) )
0319                 style[attr.name()] = attr.value();
0320         }
0321 
0322         for ( const auto& child : ItemCountRange(element.childNodes()) )
0323         {
0324             if ( child.isElement() )
0325             {
0326                 auto attr = child.toElement();
0327                 if ( attr.tagName() == "attr" )
0328                 {
0329                     auto attr_name = attr.attribute("name").split(":").back();
0330                     for ( const auto& grandchild : ItemCountRange(child.childNodes()) )
0331                     {
0332                         if ( grandchild.isElement() )
0333                         {
0334                             style[attr_name] = add_as_resource(grandchild.toElement());
0335                             break;
0336                         }
0337                     }
0338                 }
0339             }
0340         }
0341 
0342         return style;
0343     }
0344 
0345     void set_name(model::DocumentNode* node, const QDomElement& element)
0346     {
0347         QString name = attr(element, "", "name", node->type_name_human());
0348         node->name.set(name);
0349     }
0350 
0351     void add_style_shapes(const ParseFuncArgs& args, model::ShapeListProperty* shapes, const Style& style)
0352     {
0353         add_fill(args, shapes, style);
0354         add_stroke(args, shapes, style);
0355         if ( style.contains("trimPathEnd") || style.contains("trimPathStart") )
0356             add_trim(args, shapes, style);
0357     }
0358 
0359     QColor parse_color(const QString& color)
0360     {
0361         if ( !color.isEmpty() && color[0] == '#' )
0362         {
0363             if ( color.size() == 5 )
0364                 return svg::parse_color("#" + color.mid(2) + color[1]);
0365             if ( color.size() == 9 )
0366                 return svg::parse_color("#" + color.mid(3) + color.mid(1, 2));
0367         }
0368 
0369         return svg::parse_color(color);
0370     }
0371 
0372     void set_styler_style(model::Styler* styler, const QString& color)
0373     {
0374         if ( color.isEmpty() )
0375         {
0376             styler->visible.set(false);
0377         }
0378         else if ( color[0] == '@' )
0379         {
0380             auto res = get_resource(color);
0381             if ( res && res->element.tagName() == "gradient" )
0382                 styler->use.set(parse_gradient(res));
0383         }
0384         else if ( color[0] == '?' )
0385         {
0386             styler->use.set(color_from_theme(color));
0387         }
0388         else
0389         {
0390             styler->color.set(parse_color(color));
0391         }
0392 
0393     }
0394 
0395     void add_stroke(const ParseFuncArgs& args, model::ShapeListProperty* shapes, const Style& style)
0396     {
0397         auto stroke = std::make_unique<model::Stroke>(document);
0398         set_styler_style(stroke.get(), style.get("strokeColor", ""));
0399         stroke->opacity.set(percent_1(style.get("strokeAlpha", "1")));
0400 
0401         stroke->width.set(parse_unit(style.get("strokeWidth", "1")));
0402 
0403         stroke->cap.set(line_cap(style.get("strokeLineCap", "butt")));
0404         stroke->join.set(line_join(style.get("strokeLineJoin", "butt")));
0405         stroke->miter_limit.set(parse_unit(style.get("strokeMiterLimit", "4")));
0406 
0407         auto anim = get_animations(args.element);
0408         for ( const auto& kf : anim.single("strokeColor") )
0409             stroke->color.set_keyframe(kf.time, kf.values.color())->set_transition(kf.transition);
0410 
0411         for ( const auto& kf : anim.single("strokeAlpha") )
0412             stroke->opacity.set_keyframe(kf.time, kf.values.scalar())->set_transition(kf.transition);
0413 
0414         for ( const auto& kf : anim.single("strokeWidth") )
0415             stroke->width.set_keyframe(kf.time, kf.values.scalar())->set_transition(kf.transition);
0416 
0417         shapes->insert(std::move(stroke));
0418     }
0419 
0420     const AnimateParser::AnimatedProperties& get_animations(const QDomElement& element)
0421     {
0422         auto name = element.attribute("name");
0423         return animations[name];
0424     }
0425 
0426     void add_fill(const ParseFuncArgs& args, model::ShapeListProperty* shapes, const Style& style)
0427     {
0428         auto fill = std::make_unique<model::Fill>(document);
0429         set_styler_style(fill.get(), style.get("fillColor", ""));
0430         fill->opacity.set(percent_1(style.get("fillAlpha", "1")));
0431 
0432         if ( style.get("fillType", "") == "evenOdd" )
0433             fill->fill_rule.set(model::Fill::EvenOdd);
0434 
0435         auto anim = get_animations(args.element);
0436         for ( const auto& kf : anim.single("fillColor") )
0437             fill->color.set_keyframe(kf.time, kf.values.color())->set_transition(kf.transition);
0438 
0439         for ( const auto& kf : anim.single("fillAlpha") )
0440             fill->opacity.set_keyframe(kf.time, kf.values.scalar())->set_transition(kf.transition);
0441 
0442         shapes->insert(std::move(fill));
0443     }
0444 
0445     void add_trim(const ParseFuncArgs& args, model::ShapeListProperty* shapes, const Style& style)
0446     {
0447         auto trim = std::make_unique<model::Trim>(document);
0448 
0449         trim->start.set(percent_1(style.get("trimPathStart", "1")));
0450         trim->end.set(percent_1(style.get("trimPathEnd", "1")));
0451         trim->offset.set(percent_1(style.get("trimPathOffset", "1")));
0452 
0453         auto anim = get_animations(args.element);
0454 
0455         for ( const auto& kf : anim.single("trimPathStart") )
0456             trim->start.set_keyframe(kf.time, kf.values.scalar())->set_transition(kf.transition);
0457 
0458         for ( const auto& kf : anim.single("trimPathEnd") )
0459             trim->end.set_keyframe(kf.time, kf.values.scalar())->set_transition(kf.transition);
0460 
0461         for ( const auto& kf : anim.single("trimPathOffset") )
0462             trim->offset.set_keyframe(kf.time, kf.values.scalar())->set_transition(kf.transition);
0463 
0464         shapes->insert(std::move(trim));
0465     }
0466 
0467     model::Gradient* parse_gradient(Resource* res)
0468     {
0469         if ( res->element.tagName() != "gradient" )
0470             return nullptr;
0471 
0472         if ( res->asset )
0473             return res->asset->cast<model::Gradient>();
0474 
0475         // Load colors
0476         auto colors = document->assets()->add_gradient_colors();
0477 
0478         QGradientStops stops;
0479         if ( res->element.hasAttribute("startColor") )
0480         stops.push_back({0.0, parse_color(res->element.attribute("startColor"))});
0481         if ( res->element.hasAttribute("centerColor") )
0482             stops.push_back({0.5, parse_color(res->element.attribute("centerColor"))});
0483         if ( res->element.hasAttribute("endColor") )
0484             stops.push_back({1.0, parse_color(res->element.attribute("endColor"))});
0485 
0486         for ( QDomElement e : ElementRange(res->element.childNodes()) )
0487         {
0488             if ( e.tagName() == "item" )
0489                 stops.push_back({
0490                     e.attribute("offset", "0").toDouble(),
0491                     parse_color(e.attribute("color"))
0492                 });
0493         }
0494 
0495         colors->colors.set(stops);
0496 
0497         // Load gradient
0498         auto gradient = document->assets()->add_gradient();
0499         gradient->colors.set(colors);
0500 
0501         QString type = res->element.attribute("type", "linear");
0502         if ( type == "linear" )
0503             gradient->type.set(model::Gradient::Linear);
0504         else if ( type == "radial" )
0505             gradient->type.set(model::Gradient::Radial);
0506         else if ( type == "sweeo" )
0507             gradient->type.set(model::Gradient::Conical);
0508 
0509         gradient->start_point.set({
0510             len_attr(res->element, "startX"),
0511             len_attr(res->element, "startY"),
0512         });
0513 
0514         gradient->end_point.set({
0515             len_attr(res->element, "endX"),
0516             len_attr(res->element, "endY"),
0517         });
0518 
0519         // TODO center / radius
0520 
0521 
0522         res->asset = gradient;
0523         return gradient;
0524     }
0525 
0526     void parse_transform(model::Transform* trans, const ParseFuncArgs& args)
0527     {
0528         QPointF anchor = {
0529             len_attr(args.element, "pivotX"),
0530             len_attr(args.element, "pivotY"),
0531         };
0532 
0533         trans->anchor_point.set(anchor);
0534         trans->position.set(anchor + QPointF{
0535             len_attr(args.element, "translateX"),
0536             len_attr(args.element, "translateY"),
0537         });
0538 
0539         trans->scale.set(QVector2D(
0540             percent_1(args.element.attribute("scaleX", "1")),
0541             percent_1(args.element.attribute("scaleY", "1"))
0542         ));
0543 
0544         trans->rotation.set(args.element.attribute("rotation", "0").toDouble());
0545 
0546         auto anim = get_animations(args.element);
0547 
0548         for ( const auto& kf : anim.joined({"pivotX", "pivotY", "translateX", "translateY"}) )
0549         {
0550             anchor = QPointF(kf.values[0].scalar(), kf.values[1].scalar());
0551             trans->anchor_point.set_keyframe(kf.time, anchor)->set_transition(kf.transition);
0552             QPointF pos(kf.values[2].scalar(), kf.values[3].scalar());
0553             trans->position.set_keyframe(kf.time, anchor + pos)->set_transition(kf.transition);
0554         }
0555 
0556         for ( const auto& kf : anim.joined({"scaleX", "scaleY"}) )
0557         {
0558             QVector2D scale(kf.values[0].scalar(), kf.values[1].scalar());
0559             trans->scale.set_keyframe(kf.time, scale)->set_transition(kf.transition);
0560         }
0561 
0562         for ( const auto& kf : anim.single("rotation") )
0563             trans->rotation.set_keyframe(kf.time, kf.values.scalar())->set_transition(kf.transition);
0564     }
0565 
0566     std::unique_ptr<model::Group> parse_clip(const QDomElement& element)
0567     {
0568         auto clip = std::make_unique<model::Group>(document);
0569         set_name(clip.get(), element);
0570 
0571         QString d = element.attribute("pathData");
0572         math::bezier::MultiBezier bez = PathDParser(d).parse();
0573 
0574         auto fill = std::make_unique<model::Fill>(document);
0575         fill->color.set(QColor(255, 255, 255));
0576         clip->shapes.insert(std::move(fill));
0577 
0578         std::vector<model::Path*> shapes;
0579         for ( const auto& bezier : bez.beziers() )
0580         {
0581             auto shape = std::make_unique<model::Path>(document);
0582             shape->shape.set(bezier);
0583             shape->closed.set(bezier.closed());
0584             shapes.push_back(shape.get());
0585             clip->shapes.insert(std::move(shape));
0586         }
0587 
0588         path_animation(shapes, get_animations(element), "pathData");
0589 
0590         return clip;
0591     }
0592 
0593     void parseshape_group(const ParseFuncArgs& args)
0594     {
0595         std::unique_ptr<model::Group> clip;
0596 
0597         for ( auto e : ElementRange(args.element.elementsByTagName("clip-path")) )
0598         {
0599             clip = parse_clip(e);
0600             break;
0601         }
0602 
0603         model::Group* group = nullptr;
0604         if ( clip )
0605         {
0606             auto obj = std::make_unique<model::Layer>(document);
0607             group = obj.get();
0608             args.shape_parent->insert(std::move(obj));
0609         }
0610         else
0611         {
0612             auto obj = std::make_unique<model::Group>(document);
0613             group = obj.get();
0614             args.shape_parent->insert(std::move(obj));
0615         }
0616 
0617         set_name(group, args.element);
0618 
0619         parse_transform(group->transform.get(), args);
0620         parse_children({args.element, &group->shapes, args.parent_style, true});
0621     }
0622 
0623     void parseshape_path(const ParseFuncArgs& args)
0624     {
0625         QString d = args.element.attribute("pathData");
0626         math::bezier::MultiBezier bez = PathDParser(d).parse();
0627 
0628         ShapeCollection shapes;
0629         std::vector<model::Path*> paths;
0630         for ( const auto& bezier : bez.beziers() )
0631         {
0632             auto shape = push<model::Path>(shapes);
0633             shape->shape.set(bezier);
0634             shape->closed.set(bezier.closed());
0635             paths.push_back(shape);
0636         }
0637         add_shapes(args, std::move(shapes));
0638 
0639         path_animation(paths, get_animations(args.element), "pathData");
0640     }
0641 
0642     Resource* get_resource(const QString& id)
0643     {
0644         auto iter = resources.find(id);
0645         if ( iter != resources.end() )
0646             return &iter->second;
0647 
0648         if ( resource_path.isRoot() || id.isEmpty() || id[0] != '@' || id.back() == '\0' )
0649         {
0650             warning(i18n("Unknown resource id %1", id));
0651             return {};
0652         }
0653 
0654         QString path = resource_path.filePath(id.mid(1) + ".xml");
0655         QFile resource_file(path);
0656         if ( !resource_file.open(QIODevice::ReadOnly) )
0657         {
0658             warning(i18n("Could not read file %1", path));
0659             warning(i18n("Could not load resource %1", id));
0660             return {};
0661         }
0662 
0663         SvgParseError err;
0664         QDomDocument resource_dom;
0665         if ( !resource_dom.setContent(&resource_file, true, &err.message, &err.line, &err.column) )
0666         {
0667             warning(err.formatted(path));
0668             warning(i18n("Could not load resource %1", id));
0669             return {};
0670         }
0671 
0672         iter = resources.insert({id, {id, resource_dom.documentElement()}}).first;
0673         return &iter->second;
0674     }
0675 
0676     QString add_as_resource(const QDomElement& e)
0677     {
0678         internal_resource_id++;
0679         QString id = QString("@(internal)%1").arg(internal_resource_id);
0680         id.push_back(QChar(0));
0681         resources[id] = {e.tagName(), e};
0682         return id;
0683     }
0684 
0685     model::NamedColor* color_from_theme(const QString& color)
0686     {
0687         QString norm_name;
0688         if ( color.contains("/") )
0689             norm_name = color.split("/").back();
0690         else
0691             norm_name = color.mid(1);
0692 
0693         auto iter = palette_colors.find(norm_name);
0694         if ( iter != palette_colors.end() )
0695             return iter->second;
0696 
0697         QColor col = Qt::black;
0698         auto it2 = theme_colors.find(norm_name);
0699         if ( it2 != theme_colors.end() )
0700             col = it2->second;
0701 
0702         auto asset = document->assets()->add_color(col);
0703         palette_colors.emplace(norm_name, asset);
0704         return asset;
0705     }
0706 
0707     QDir resource_path;
0708     std::map<QString, Resource> resources;
0709     int internal_resource_id = 0;
0710     std::map<QString, model::NamedColor*> palette_colors;
0711     std::map<QString, AnimateParser::AnimatedProperties> animations;
0712 
0713     static const std::map<QString, void (Private::*)(const ParseFuncArgs&)> shape_parsers;
0714     static const std::unordered_set<QString> style_atrrs;
0715     static const std::unordered_map<QString, QString> theme_colors;
0716 };
0717 
0718 const std::map<QString, void (glaxnimate::io::avd::AvdParser::Private::*)(const glaxnimate::io::avd::AvdParser::Private::ParseFuncArgs&)> glaxnimate::io::avd::AvdParser::Private::shape_parsers = {
0719     {"group",       &glaxnimate::io::avd::AvdParser::Private::parseshape_group},
0720     {"path",        &glaxnimate::io::avd::AvdParser::Private::parseshape_path},
0721 };
0722 
0723 const std::unordered_set<QString> glaxnimate::io::avd::AvdParser::Private::style_atrrs = {
0724     "fillColor", "fillAlpha", "fillType",
0725     "strokeColor", "strokeAlpha", "strokeWidth", "strokeLineCap", "strokeLineJoin", "strokeMiterLimit",
0726     "trimPathStart", "trimPathEnd", "trimPathOffset",
0727 };
0728 
0729 glaxnimate::io::avd::AvdParser::AvdParser(
0730     QIODevice* device,
0731     const QDir& resource_path,
0732     model::Document* document,
0733     const std::function<void(const QString&)>& on_warning,
0734     ImportExport* io,
0735     QSize forced_size,
0736     model::FrameTime default_time
0737 )
0738     : d(std::make_unique<Private>(resource_path, document, on_warning, io, forced_size, default_time))
0739 {
0740     d->load(device);
0741 }
0742 
0743 glaxnimate::io::avd::AvdParser::~AvdParser() = default;
0744 
0745 void glaxnimate::io::avd::AvdParser::parse_to_document()
0746 {
0747     d->parse();
0748 }
0749 
0750 /// Based on the material theme
0751 /// Extracted using https://gitlab.com/-/snippets/2502132
0752 const std::unordered_map<QString, QString> glaxnimate::io::avd::AvdParser::Private::theme_colors = {
0753     {"colorForeground", "#ffffffff"},
0754     {"colorForegroundInverse", "#ff000000"},
0755     {"colorBackground", "#ff303030"},
0756     {"colorBackgroundFloating", "#ff424242"},
0757     {"colorError", "#ff7043"},
0758     {"opacityListDivider", "#1f000000"},
0759     {"textColorPrimary", "#ff000000"},
0760     {"textColorSecondary", "#ff000000"},
0761     {"textColorHighlight", "#ffffffff"},
0762     {"textColorHighlightInverse", "#ffffffff"},
0763     {"navigationBarColor", "#ff000000"},
0764     {"panelColorBackground", "#000"},
0765     {"colorPrimaryDark", "#ff000000"},
0766     {"colorPrimary", "#ff212121"},
0767     {"colorAccent", "#ff80cbc4"},
0768     {"tooltipForegroundColor", "#ff000000"},
0769     {"colorPopupBackground", "#ff303030"},
0770     {"colorListDivider", "#ffffffff"},
0771     {"textColorLink", "#ff80cbc4"},
0772     {"textColorLinkInverse", "#ff80cbc4"},
0773     {"editTextColor", "#ff000000"},
0774     {"windowBackground", "#ff303030"},
0775     {"statusBarColor", "#ff000000"},
0776     {"panelBackground", "#ff303030"},
0777     {"panelColorForeground", "#ff000000"},
0778     {"detailsElementBackground", "#ff303030"},
0779     {"actionMenuTextColor", "#ff000000"},
0780     {"colorEdgeEffect", "#ff212121"},
0781     {"colorControlNormal", "#ff000000"},
0782     {"colorControlActivated", "#ff80cbc4"},
0783     {"colorProgressBackgroundNormal", "#ff000000"},
0784 };