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

0001 /*
0002  * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia <dev@dragon.best>
0003  *
0004  * SPDX-License-Identifier: GPL-3.0-or-later
0005  */
0006 #include "aep_loader.hpp"
0007 #include "model/shapes/rect.hpp"
0008 #include "model/shapes/ellipse.hpp"
0009 #include "model/shapes/fill.hpp"
0010 #include "model/shapes/stroke.hpp"
0011 #include "model/shapes/image.hpp"
0012 #include "model/shapes/polystar.hpp"
0013 #include "model/shapes/path.hpp"
0014 #include "model/shapes/trim.hpp"
0015 #include "model/shapes/offset_path.hpp"
0016 #include "model/shapes/inflate_deflate.hpp"
0017 #include "model/shapes/zig_zag.hpp"
0018 #include "model/shapes/round_corners.hpp"
0019 #include "model/shapes/repeater.hpp"
0020 #include "model/shapes/precomp_layer.hpp"
0021 #include "model/shapes/text.hpp"
0022 #include "model/animation/join_animatables.hpp"
0023 
0024 using namespace glaxnimate::io::aep;
0025 using namespace glaxnimate;
0026 using glaxnimate::io::ImportExport;
0027 
0028 static constexpr std::array<QRgb, 17> label_colors = {
0029     0x00000000, // None
0030     0xffb4393b, // Red
0031     0xffe2d759, // Yellow
0032     0xffabcbc8, // Aqua
0033     0xffe5bcca, // Pink
0034     0xffa9aac9, // Lavender
0035     0xffe5c19f, // Peach
0036     0xffb4c7b4, // Sea Foam
0037     0xff687fdd, // Blue
0038     0xff4ea350, // Green
0039     0xff8d3299, // Purple
0040     0xffe79228, // Orange
0041     0xff7e442c, // Brown
0042     0xfff371d5, // Fuchsia
0043     0xff43a2a4, // Cyan
0044     0xffa7967a, // Sandstone
0045     0xff203f1f // Dark Green
0046 };
0047 
0048 void glaxnimate::io::aep::AepLoader::load_project()
0049 {
0050     for ( const auto& comp : project.compositions )
0051         get_comp(comp->id);
0052 
0053     for ( const auto& pair : project.assets )
0054         load_asset(pair.second);
0055 
0056     for ( const auto& comp : project.compositions )
0057         load_comp(*comp);
0058 }
0059 
0060 void glaxnimate::io::aep::AepLoader::load_asset(const glaxnimate::io::aep::FolderItem* item)
0061 {
0062     if ( item->type() == FolderItem::Asset )
0063     {
0064         auto image = std::make_unique<glaxnimate::model::Bitmap>(document);
0065         auto asset = static_cast<const FileAsset*>(item);
0066         if ( asset->path.exists() )
0067         {
0068             image->filename.set(asset->path.filePath());
0069         }
0070         else
0071         {
0072             // Handle collected assets
0073             QFileInfo path(asset_path.filePath(asset->path.fileName()));
0074             if ( !path.exists() )
0075                 warning(i18n("External asset not found: %1", asset->path.filePath()));
0076             else
0077                 image->filename.set(path.filePath());
0078         }
0079         image->name.set(item->name);
0080         images[item->id] = image.get();
0081         document->assets()->images->values.insert(std::move(image));
0082         asset_size[item->id] = QPointF(asset->width, asset->height);
0083     }
0084     else if ( item->type() == FolderItem::Solid )
0085     {
0086         auto color = std::make_unique<glaxnimate::model::NamedColor>(document);
0087         auto solid = static_cast<const Solid*>(item);
0088         color->color.set(solid->color);
0089         color->name.set(solid->name);
0090         colors[item->id] = {color.get(), solid};
0091         document->assets()->colors->values.insert(std::move(color));
0092         asset_size[item->id] = QPointF(solid->width, solid->height);
0093     }
0094     else if ( item->type() == FolderItem::Composition )
0095     {
0096         auto aecomp = static_cast<const Composition*>(item);
0097         asset_size[item->id] = QPointF(aecomp->width, aecomp->height);
0098         auto comp = get_comp(item->id);
0099         comp->width.set(aecomp->width);
0100         comp->height.set(aecomp->height);
0101         comp->name.set(aecomp->name);
0102     }
0103 }
0104 
0105 void glaxnimate::io::aep::AepLoader::warning(const QString& msg)
0106 {
0107     io->warning(msg);
0108 }
0109 
0110 void glaxnimate::io::aep::AepLoader::info(const QString& msg)
0111 {
0112     io->information(msg);
0113 }
0114 
0115 static bool unknown_mn(glaxnimate::io::ImportExport* io, const QString& context, const QString& mn)
0116 {
0117     io->information(i18n("Unknown property \"%1\" of \"%2\"", mn, context));
0118     return true;
0119 }
0120 
0121 
0122 model::Composition * glaxnimate::io::aep::AepLoader::get_comp(glaxnimate::io::aep::Id id)
0123 {
0124     if ( !id )
0125         return nullptr;
0126 
0127     auto& comp = comps[id];
0128     if ( !comp )
0129         comp = document->assets()->add_comp_no_undo();
0130 
0131     return comp;
0132 }
0133 
0134 struct glaxnimate::io::aep::AepLoader::CompData
0135 {
0136     struct PendingLayer
0137     {
0138         model::Layer* layer;
0139         Id parent = 0;
0140         Id track_matte = 0;
0141     };
0142 
0143     void resolve()
0144     {
0145         for ( const auto& p : pending )
0146         {
0147             if ( p.parent )
0148                 p.layer->parent.set(layers.at(p.parent));
0149             /// \todo track matte
0150         }
0151     }
0152 
0153     model::Composition* comp;
0154     const Composition* ae_comp;
0155     std::unordered_map<Id, model::Layer*> layers = {};
0156     std::vector<PendingLayer> pending = {};
0157 
0158 };
0159 
0160 void glaxnimate::io::aep::AepLoader::load_comp(const glaxnimate::io::aep::Composition& ae_comp)
0161 {
0162     auto comp = get_comp(ae_comp.id);
0163     comp->name.set(ae_comp.name);
0164     comp->width.set(ae_comp.width);
0165     comp->height.set(ae_comp.height);
0166     comp->fps.set(ae_comp.framerate);
0167     comp->animation->first_frame.set(ae_comp.in_time);
0168     comp->animation->last_frame.set(ae_comp.out_time);
0169     comp->group_color.set(ae_comp.color);
0170     comp->group_color.set(label_colors[int(ae_comp.label_color)]);
0171 
0172     CompData data{comp, &ae_comp};
0173     for ( const auto& layer : ae_comp.layers )
0174         load_layer(*layer, data);
0175 
0176     data.resolve();
0177 }
0178 
0179 namespace {
0180 template<class T> T convert_value(const PropertyValue& v)
0181 {
0182     return std::get<T>(v.value);
0183 }
0184 
0185 template<> float convert_value(const PropertyValue& v)
0186 {
0187     return convert_value<qreal>(v);
0188 }
0189 
0190 template<> int convert_value(const PropertyValue& v)
0191 {
0192     return convert_value<qreal>(v);
0193 }
0194 
0195 template<> QPointF convert_value(const PropertyValue& v)
0196 {
0197     if ( v.type() == PropertyValue::Vector2D )
0198         return std::get<QPointF>(v.value);
0199     auto p = convert_value<QVector3D>(v.value);
0200     return {p.x(), p.y()};
0201 }
0202 
0203 
0204 template<> QVector2D convert_value(const PropertyValue& v)
0205 {
0206     if ( v.type() == PropertyValue::Vector2D )
0207     {
0208         auto p = std::get<QPointF>(v.value);
0209         return QVector2D(p.x(), p.y());
0210     }
0211     else
0212     {
0213         auto p = convert_value<QVector3D>(v.value);
0214         return {p.x(), p.y()};
0215     }
0216 }
0217 
0218 template<> QSizeF convert_value(const PropertyValue& v)
0219 {
0220     auto p = convert_value<QPointF>(v.value);
0221     return {p.x(), p.y()};
0222 }
0223 
0224 template<> math::bezier::Bezier convert_value(const PropertyValue& v)
0225 {
0226     const auto& aebez = std::get<BezierData>(v.value);
0227     math::bezier::Bezier bez;
0228     int count = aebez.points.size();
0229 
0230     for ( int i = 0; i < count; i += 3 )
0231     {
0232         /// \todo smooth etc?
0233         math::bezier::Point p(aebez.convert_point(aebez.points[i]));
0234         if ( i > 0 )
0235             p.tan_in = aebez.convert_point(aebez.points[i-1]);
0236         else
0237             p.tan_in = aebez.convert_point(aebez.points.back());
0238 
0239         p.tan_out = aebez.convert_point(aebez.points[i+1]);
0240 
0241         if ( i == count - 1 && aebez.closed && math::fuzzy_compare(bez[0].pos, p.pos) )
0242         {
0243             bez[0].tan_in = p.tan_in;
0244             break;
0245         }
0246 
0247         bez.push_back(p);
0248     }
0249     bez.set_closed(aebez.closed);
0250     return bez;
0251 }
0252 
0253 template<> QGradientStops convert_value(const PropertyValue& v)
0254 {
0255     return convert_value<Gradient>(v).to_qt();
0256 }
0257 
0258 template<class T> struct DefaultConverter
0259 {
0260     T operator()(const PropertyValue& v) const { return convert_value<T>(v); }
0261 };
0262 
0263 template<class T, class Converter=DefaultConverter<T>>
0264 bool load_property(model::Property<T>& prop, const Property& ae_prop, const Converter& conv = {})
0265 {
0266     if ( ae_prop.value.type() )
0267         prop.set(conv(ae_prop.value));
0268     else if ( !ae_prop.keyframes.empty() && ae_prop.keyframes[0].value.type() )
0269         prop.set(conv(ae_prop.keyframes[0].value));
0270     else
0271         return false;
0272 
0273     return true;
0274 }
0275 
0276 template<class T> void kf_extra_data(model::Keyframe<T>* kf, const Keyframe& aekf)
0277 {
0278     (void)kf;
0279     (void)aekf;
0280 }
0281 
0282 template<>
0283 void kf_extra_data(model::Keyframe<QPointF>* kf, const Keyframe& aekf)
0284 {
0285     auto p = kf->get();
0286     kf->set_point(math::bezier::Point(
0287         p,
0288         p + aekf.in_tangent,
0289         p + aekf.out_tangent
0290     ));
0291 }
0292 
0293 qreal vector_length(const std::vector<double>& v)
0294 {
0295     qreal len = 0;
0296     for ( double a : v )
0297         len += a * a;
0298     return math::sqrt(len);
0299 }
0300 
0301 model::KeyframeTransition keyframe_transition(const Property& prop, const Keyframe& kf, const Keyframe& next_kf)
0302 {
0303     qreal duration = next_kf.time - kf.time;
0304     if ( qFuzzyIsNull(duration) )
0305         return model::KeyframeTransition(model::KeyframeTransition::Linear);
0306 
0307     qreal average_speed = 0;
0308     if ( prop.type == PropertyType::Position )
0309     {
0310         math::bezier::BezierSegment bez;
0311         if ( kf.value.type() == PropertyValue::Vector2D )
0312         {
0313             bez[0] = std::get<QPointF>(kf.value.value);
0314             bez[3] = std::get<QPointF>(next_kf.value.value);
0315         }
0316         else
0317         {
0318             auto p = std::get<QVector3D>(kf.value.value);
0319             bez[0] = {p.x(), p.y()};
0320             p = std::get<QVector3D>(next_kf.value.value);
0321             bez[3] = {p.x(), p.y()};
0322         }
0323 
0324         bez[1] = kf.out_tangent;
0325         bez[2] = kf.in_tangent;
0326 
0327         average_speed = math::bezier::LengthData(math::bezier::CubicBezierSolver(bez), 20).length();
0328 
0329     }
0330     else if ( prop.type == PropertyType::NoValue )
0331     {
0332         average_speed = 1;
0333     }
0334     else
0335     {
0336         average_speed = math::abs(kf.value.magnitude() - next_kf.value.magnitude());
0337     }
0338 
0339     average_speed /= duration;
0340     qreal out_influence = vector_length(kf.out_influence);
0341     qreal in_influence = vector_length(kf.in_influence);
0342     qreal out_speed = vector_length(kf.out_speed);
0343     qreal in_speed = vector_length(kf.in_speed);
0344 
0345     QPointF ease_out;
0346     QPointF ease_in;
0347     ease_out.setX(out_influence);
0348     ease_in.setX(1 - in_influence);
0349     if ( qFuzzyIsNull(average_speed) )
0350     {
0351         ease_out.setY(out_influence);
0352         ease_in.setY(1 - in_influence);
0353     }
0354     else
0355     {
0356         ease_out.setY(out_influence * out_speed / average_speed);
0357         ease_in.setY(1 - in_influence * in_speed / average_speed);
0358     }
0359 
0360     return model::KeyframeTransition(ease_out, ease_in);
0361 }
0362 
0363 template<class T, class Converter=DefaultConverter<T>>
0364 bool load_property(
0365     model::AnimatedProperty<T>& prop, const Property& ae_prop, const Converter& conv = {}
0366 )
0367 {
0368     if ( !ae_prop.animated && ae_prop.value.type() )
0369     {
0370         prop.set(conv(ae_prop.value));
0371         return true;
0372     }
0373 
0374     for ( std::size_t i = 0; i < ae_prop.keyframes.size(); i++ )
0375     {
0376         const auto& aekf = ae_prop.keyframes[i];
0377         auto kf = prop.set_keyframe(aekf.time, conv(aekf.value));
0378 
0379         kf_extra_data(kf, aekf);
0380 
0381         /// \todo easing
0382         if ( aekf.transition_type == KeyframeTransitionType::Hold )
0383             kf->set_transition(model::KeyframeTransition(model::KeyframeTransition::Hold));
0384         else if ( aekf.transition_type == KeyframeTransitionType::Linear )
0385             kf->set_transition(model::KeyframeTransition(model::KeyframeTransition::Linear));
0386         else if ( i + 1 < ae_prop.keyframes.size() )
0387             kf->set_transition(keyframe_transition(ae_prop, aekf, ae_prop.keyframes[i+1]));
0388     }
0389 
0390     return true;
0391 }
0392 
0393 template<class PropT, class Converter=DefaultConverter<typename PropT::value_type>>
0394 void load_property_check(
0395     ImportExport* io,
0396     PropT& prop,
0397     const PropertyBase& ae_prop,
0398     const QString& match_name,
0399     const Converter& conv = {}
0400 )
0401 {
0402     if ( ae_prop.class_type() != PropertyBase::Property )
0403     {
0404         io->warning(i18n("Expected property for %1", match_name));
0405         return;
0406     }
0407 
0408     try
0409     {
0410         if ( !load_property(prop, static_cast<const Property&>(ae_prop), conv) )
0411             io->warning(i18n("Could convert %1", match_name));
0412     }
0413     catch ( const std::bad_variant_access& )
0414     {
0415         io->error(i18n("Invalid value for %1", match_name));
0416     }
0417 }
0418 
0419 template<class PropT, class Converter=DefaultConverter<typename PropT::value_type>>
0420 bool load_property(ImportExport* io, PropT& prop,
0421                    const PropertyPair& ae_prop, const char* match_name, const Converter& conv = {})
0422 {
0423     if ( ae_prop.match_name != match_name )
0424         return false;
0425 
0426     load_property_check(io, prop, *ae_prop.value, ae_prop.match_name, conv);
0427     return true;
0428 }
0429 
0430 bool convert_shape_reverse(const PropertyValue& v)
0431 {
0432     return convert_value<int>(v) == 3;
0433 }
0434 
0435 template<int Divisor, class T = qreal>
0436 T convert_divide(const PropertyValue& v)
0437 {
0438     return convert_value<T>(v) / Divisor;
0439 }
0440 
0441 template<class T>
0442 T convert_enum(const PropertyValue& v)
0443 {
0444     return T(convert_value<int>(v));
0445 }
0446 
0447 template<>
0448 model::Fill::Rule convert_enum(const PropertyValue& v)
0449 {
0450     if ( convert_value<int>(v) == 2 )
0451         return model::Fill::Rule::EvenOdd;
0452     return model::Fill::Rule::NonZero;
0453 }
0454 
0455 template<>
0456 model::Stroke::Cap convert_enum(const PropertyValue& v)
0457 {
0458     switch ( convert_value<int>(v) )
0459     {
0460         default:
0461         case 1: return model::Stroke::Cap::ButtCap;
0462         case 2: return model::Stroke::Cap::RoundCap;
0463         case 3: return model::Stroke::Cap::SquareCap;
0464     }
0465 }
0466 
0467 template<>
0468 model::Stroke::Join convert_enum(const PropertyValue& v)
0469 {
0470     switch ( convert_value<int>(v) )
0471     {
0472         default:
0473         case 1: return model::Stroke::Join::MiterJoin;
0474         case 2: return model::Stroke::Join::RoundJoin;
0475         case 3: return model::Stroke::Join::BevelJoin;
0476     }
0477 }
0478 
0479 struct AnchorMult
0480 {
0481     QPointF operator()(const PropertyValue& v) const
0482     {
0483         auto a = convert_value<QPointF>(v);
0484         return {a.x() * p.x(), a.y() * p.y()};
0485     }
0486     QPointF p;
0487 };
0488 
0489 bool load_position_component(io::ImportExport* io, const PropertyGroup& group, int suffix, model::AnimatedProperty<float>& out, bool force)
0490 {
0491     auto pair =  group.get_pair(QString("ADBE Position_%1").arg(suffix));
0492     if ( !pair )
0493         return false;
0494 
0495     if ( pair->value->class_type() != PropertyBase::Property )
0496         return false;
0497 
0498     const Property& prop = static_cast<const Property&>(*pair->value);
0499     if ( !prop.is_component && !force )
0500         return false;
0501 
0502     load_property_check(io, out, prop, pair->match_name);
0503     return true;
0504 }
0505 
0506 void load_transform(io::ImportExport* io, model::Transform* tf, const PropertyBase& prop, model::AnimatedProperty<float>* opacity, const QPointF& anchor_mult, bool divide_100)
0507 {
0508     if ( prop.class_type() != PropertyBase::PropertyGroup )
0509     {
0510         io->warning(i18n("Expected property group for transform"));
0511         return;
0512     }
0513 
0514     const PropertyGroup& g = static_cast<const PropertyGroup&>(prop);
0515 
0516     bool is_3d = false;
0517     int split_position = 1;
0518 
0519     for ( const auto& p : g.properties )
0520     {
0521         if ( p.match_name.endsWith("Anchor Point") || p.match_name.endsWith("Anchor") )
0522             load_property_check(io, tf->anchor_point, *p.value, p.match_name, AnchorMult{anchor_mult});
0523         else if ( p.match_name.endsWith("Position") )
0524         {
0525             if ( p.value->class_type() == PropertyBase::Property )
0526             {
0527                 const Property& pos_prop = static_cast<const Property&>(*p.value);
0528                 if ( pos_prop.split )
0529                 {
0530                     split_position = 2;
0531                 }
0532                 else
0533                 {
0534                     split_position = 0;
0535                     load_property_check(io, tf->position, *p.value, p.match_name);
0536                 }
0537             }
0538         }
0539         else if ( p.match_name.endsWith("Scale") )
0540             load_property_check(io, tf->scale, *p.value, p.match_name, divide_100 ? &convert_divide<100, QVector2D> : &convert_divide<1, QVector2D>);
0541         else if ( p.match_name.endsWith("Rotation") || p.match_name.endsWith("Rotate Z") )
0542             load_property_check(io, tf->rotation, *p.value, p.match_name);
0543         else if ( opacity && p.match_name.endsWith("Opacity") )
0544             load_property_check(io, *opacity, *p.value, p.match_name, divide_100 ? &convert_divide<100> : &convert_divide<1>);
0545         else if (
0546             p.match_name.endsWith("Rotate X") ||
0547             p.match_name.endsWith("Rotate Y") ||
0548             p.match_name.endsWith("Orientation") ||
0549             p.match_name.endsWith("Position_2")
0550         )
0551             is_3d = true;
0552         else if ( !p.match_name.endsWith("Position_1") &&
0553             !p.match_name.endsWith("Position_0") &&
0554             !p.match_name.endsWith("Opacity") &&
0555             !p.match_name.endsWith("Envir Appear in Reflect")
0556         )
0557             io->information(i18n("Unknown property \"%1\"", p.match_name));
0558     }
0559 
0560     if ( split_position )
0561     {
0562         model::Document dummydoc("");
0563         model::Object dummy(&dummydoc);
0564         model::AnimatedProperty<float> ax(&dummy, {}, 0);
0565         model::AnimatedProperty<float> ay(&dummy, {}, 0);
0566 
0567 
0568         bool force_split = split_position == 2;
0569         bool xok = load_position_component(io, g, 0, ax, force_split);
0570         bool yok = load_position_component(io, g, 1, ay, force_split);
0571         if ( split_position == 1 )
0572             force_split = xok || yok;
0573 
0574         if ( force_split )
0575         {
0576             model::JoinAnimatables join({&ax, &ay});
0577             join.apply_to(&tf->position, [](float x, float y) -> QPointF {
0578                 return QPointF(x, y);
0579             }, &ax, &ay);
0580         }
0581     }
0582 
0583     if ( is_3d )
0584     {
0585         /// \todo figure a way of determining whether the transform is actually 3D
0586         /// as layer transfoms seem to often have the 3D properties
0587         (void)is_3d;
0588 //         warning(i18n("3D transforms are not supported"));
0589     }
0590 }
0591 
0592 template<class Obj>
0593 struct PropertyConverterBase
0594 {
0595     virtual ~PropertyConverterBase() noexcept = default;
0596 
0597     virtual void load(ImportExport* io, Obj* object, const PropertyBase& ae_prop) const = 0;
0598     virtual void set_default(Obj* object) const = 0;
0599 };
0600 
0601 template<class Obj, class Base, class PropT, class T = typename PropT::value_type, class Converter=DefaultConverter<T>>
0602 struct PropertyConverter : PropertyConverterBase<Obj>
0603 {
0604     PropertyConverter(PropT (Base::*prop), const char* match_name, const Converter& converter, const std::optional<T>& default_value = {})
0605         : prop(prop), match_name(match_name), converter(converter), default_value(default_value)
0606     {}
0607 
0608     void load(ImportExport* io, Obj* object, const PropertyBase& ae_prop) const override
0609     {
0610         load_property_check(io, object->*prop, ae_prop, match_name, converter);
0611     }
0612 
0613     void set_default(Obj* object) const override
0614     {
0615         if ( default_value )
0616             (object->*prop).set(*default_value);
0617     }
0618 
0619     PropT Base::*prop;
0620     QString match_name;
0621     Converter converter = {};
0622     std::optional<T> default_value ;
0623 };
0624 
0625 template<class Base>
0626 struct ObjectConverterBase
0627 {
0628     virtual ~ObjectConverterBase() noexcept = default;
0629     virtual std::unique_ptr<Base> load(ImportExport* io, model::Document* document, const PropertyPair& prop) const = 0;
0630 };
0631 
0632 template<class Obj, class Base, class FuncT>
0633 struct ObjectConverterFunctor : public ObjectConverterBase<Base>
0634 {
0635     template<class F>
0636     ObjectConverterFunctor(F&& functor) : functor(std::forward<F>(functor)) {}
0637 
0638     std::unique_ptr<Base> load(ImportExport* io, model::Document* document, const PropertyPair& prop) const override
0639     {
0640         return functor(io, document, prop);
0641     }
0642 
0643     FuncT functor;
0644 };
0645 
0646 struct FallbackConverterBase
0647 {
0648     virtual ~FallbackConverterBase() noexcept = default;
0649     virtual void set_default() const = 0;
0650     virtual void load_property(ImportExport* io, model::Document* document, const PropertyPair& prop_parent, const PropertyPair& prop) const = 0;
0651 };
0652 
0653 template<class Obj, class Base> struct FallbackConverter;
0654 
0655 
0656 template<class Obj, class Base>
0657 struct ObjectConverter : public ObjectConverterBase<Base>
0658 {
0659     std::unique_ptr<Base> load(ImportExport* io, model::Document* document, const PropertyPair& prop) const override
0660     {
0661         return load_object(io, document, prop);
0662     }
0663 
0664     void set_default(Obj* object, FallbackConverterBase* fallback) const
0665     {
0666         for ( const auto& conv : converters )
0667             if ( conv.second )
0668                 conv.second->set_default(object);
0669 
0670         if ( fallback )
0671             fallback->set_default();
0672     }
0673 
0674     void load_property(Obj* object, ImportExport* io, model::Document* document, const PropertyPair& prop_parent, const PropertyPair& prop, FallbackConverterBase* fallback) const
0675     {
0676         auto it = converters.find(prop.match_name);
0677         if ( it == converters.end() )
0678         {
0679             if ( fallback )
0680                 fallback->load_property(io, document, prop_parent, prop);
0681             else
0682                 unknown_mn(io, prop_parent.match_name, prop.match_name);
0683         }
0684         else if ( it->second )
0685         {
0686             it->second->load(io, object, *prop.value);
0687         }
0688     }
0689 
0690     void load_properties(Obj* object, ImportExport* io, model::Document* document, const PropertyPair& prop, FallbackConverterBase* fallback = nullptr) const
0691     {
0692         set_default(object, fallback);
0693 
0694         for ( const auto& p : *prop.value )
0695             this->load_property(object, io, document, prop, p, fallback);
0696     }
0697 
0698     std::unique_ptr<Obj> load_object(ImportExport* io, model::Document* document, const PropertyPair& prop) const
0699     {
0700         auto object = std::make_unique<Obj>(document);
0701         load_properties(object.get(), io, document, prop);
0702         return object;
0703     }
0704 
0705     template<class O2, class PropT, class T = typename PropT::value_type, class Converter=DefaultConverter<T>>
0706     ObjectConverter& prop(PropT O2::* property, const char* match_name, const Converter& conv = {})
0707     {
0708         auto ptr = std::make_unique<PropertyConverter<Obj, O2, PropT, T, Converter>>(property, match_name, conv);
0709         converters.emplace(match_name, std::move(ptr));
0710         return *this;
0711     }
0712 
0713     template<class O2, class PropT, class T = typename PropT::value_type, class Converter=DefaultConverter<T>>
0714     ObjectConverter& prop(PropT O2::*property, const char* match_name, const Converter& conv, const T& default_value)
0715     {
0716         converters.emplace(match_name, std::make_unique<PropertyConverter<Obj, O2, PropT, T, Converter>>(property, match_name, conv, default_value));
0717         return *this;
0718     }
0719 
0720     ObjectConverter& ignore(const char* match_name)
0721     {
0722         converters.emplace(match_name, nullptr);
0723         return *this;
0724     }
0725 
0726     FallbackConverter<Obj, Base> fallback(Obj* object, FallbackConverterBase* next) const
0727     {
0728         return {object, this, next};
0729     }
0730 
0731     std::unordered_map<QString, std::unique_ptr<PropertyConverterBase<Obj>>> converters;
0732 };
0733 
0734 
0735 template<class Obj, class Base>
0736 struct FallbackConverter : public FallbackConverterBase
0737 {
0738     FallbackConverter(Obj* object, const ObjectConverter<Obj, Base>* converter, FallbackConverterBase* next)
0739         : object(object), converter(converter), next(next)
0740     {}
0741 
0742     void set_default() const override
0743     {
0744         converter->set_default(object, next);
0745     }
0746 
0747     void load_property(ImportExport* io, model::Document* document, const PropertyPair& prop_parent, const PropertyPair& prop) const override
0748     {
0749         converter->load_property(object, io, document, prop_parent, prop, next);
0750     }
0751 
0752     Obj* object;
0753     const ObjectConverter<Obj, Base>* converter;
0754     FallbackConverterBase* next;
0755 };
0756 
0757 template<class Base>
0758 struct ObjectFactory
0759 {
0760     std::unique_ptr<Base> load(ImportExport* io, model::Document* document, const PropertyPair& prop) const
0761     {
0762         auto it = converters.find(prop.match_name);
0763         if ( it == converters.end() )
0764             return {};
0765         return it->second->load(io, document, prop);
0766     }
0767 
0768     template<class Obj, class FuncT>
0769     void obj(const char* match_name, FuncT&& func)
0770     {
0771         assert(converters.count(match_name) == 0);
0772         auto up = std::make_unique<ObjectConverterFunctor<Obj, Base, std::decay_t<FuncT>>>(std::forward<FuncT>(func));
0773         converters.emplace(match_name, std::move(up));
0774     }
0775 
0776     template<class Obj>
0777     ObjectConverter<Obj, Base>& obj(const char* match_name)
0778     {
0779         assert(converters.count(match_name) == 0);
0780         auto up = std::make_unique<ObjectConverter<Obj, Base>>();
0781         auto ptr = up.get();
0782         converters.emplace(match_name, std::move(up));
0783         return *ptr;
0784     }
0785 
0786     std::unordered_map<QString, std::unique_ptr<ObjectConverterBase<Base>>> converters;
0787 };
0788 
0789 
0790 std::unique_ptr<model::ShapeElement> create_shape(ImportExport* io, model::Document* document, const PropertyPair& prop);
0791 
0792 std::unique_ptr<model::ShapeElement> load_shape(ImportExport* io, model::Document* document, const PropertyPair& prop)
0793 {
0794     auto shape = create_shape(io, document, prop);
0795     if ( shape && prop.value->class_type() == PropertyBase::PropertyGroup )
0796     {
0797         const auto& gp = static_cast<const PropertyGroup&>(*prop.value);
0798         shape->visible.set(gp.visible);
0799     }
0800     return shape;
0801 }
0802 
0803 const ObjectConverter<model::Gradient, model::Gradient>& gradient_converter()
0804 {
0805     static ObjectConverter<model::Gradient, model::Gradient> gradient;
0806     static bool initialized = false;
0807     if ( !initialized )
0808     {
0809         initialized = true;
0810         gradient
0811             .prop(&model::Gradient::type, "ADBE Vector Grad Type", &convert_enum<model::Gradient::GradientType>)
0812             .prop(&model::Gradient::start_point, "ADBE Vector Grad Start Pt")
0813             .prop(&model::Gradient::end_point, "ADBE Vector Grad End Pt")
0814             .ignore("ADBE Vector Grad HiLite Length") /// \todo
0815             .ignore("ADBE Vector Grad HiLite Angle") /// \todo
0816         ;
0817     }
0818 
0819     return gradient;
0820 }
0821 
0822 const ObjectConverter<model::GradientColors, model::GradientColors>& gradient_stop_converter()
0823 {
0824     static ObjectConverter<model::GradientColors, model::GradientColors> gradient;
0825     static bool initialized = false;
0826     if ( !initialized )
0827     {
0828         initialized = true;
0829         gradient
0830             .prop(&model::GradientColors::colors, "ADBE Vector Grad Colors")
0831         ;
0832     }
0833 
0834     return gradient;
0835 }
0836 
0837 template<class T>
0838 std::unique_ptr<model::ShapeElement> load_gradient(const ObjectConverter<T, model::ShapeElement>* base_converter, ImportExport* io, model::Document* document, const PropertyPair& prop)
0839 {
0840     auto shape = std::make_unique<T>(document);
0841     auto grad_colors = document->assets()->gradient_colors->values.insert(
0842         std::make_unique<glaxnimate::model::GradientColors>(document)
0843     );
0844     auto grad = document->assets()->gradients->values.insert(
0845         std::make_unique<glaxnimate::model::Gradient>(document)
0846     );
0847     grad->end_point.set({100, 0}); // default value
0848     grad->colors.set(grad_colors);
0849     shape->use.set(grad);
0850 
0851     auto f1 = gradient_stop_converter().fallback(grad_colors, nullptr);
0852     auto f2 = gradient_converter().fallback(grad, &f1);
0853     base_converter->load_properties(shape.get(), io, document, prop, &f2);
0854 
0855     auto* highlight_len = prop.value->get_pair("ADBE Vector Grad HiLite Length");
0856     auto* highlight_angle = prop.value->get_pair("ADBE Vector Grad HiLite Angle");
0857     if ( highlight_len || highlight_angle )
0858     {
0859         model::Document dummydoc("");
0860         model::Object dummy(&dummydoc);
0861         model::AnimatedProperty<float> length(&dummy, {}, 0);
0862         model::AnimatedProperty<float> angle(&dummy, {}, 0);
0863         if ( highlight_len )
0864             load_property_check(io, length, *highlight_len->value, highlight_len->match_name);
0865         if ( highlight_angle )
0866             load_property_check(io, angle, *highlight_angle->value, highlight_angle->match_name);
0867         model::JoinAnimatables join({&grad->start_point, &grad->end_point, &length, &angle});
0868         join.apply_to(&grad->highlight, [](const QPointF& p, const QPointF& e, float length, float angle) -> QPointF {
0869             angle = math::deg2rad(angle + 90);
0870             length = math::length(e - p) * length / 100;
0871             return p + math::from_polar<QPointF>(length, angle);
0872         }, &grad->start_point, &grad->end_point, &length, &angle);
0873     }
0874     else
0875     {
0876         grad->highlight.set(grad->start_point.get());
0877     }
0878 
0879     return shape;
0880 }
0881 
0882 /**
0883  * \brief Checks for the default rectangle created by AE with merge path
0884  * bodymovin has a similar check on export
0885  */
0886 bool is_merge_rect(const model::ShapeElement& shape)
0887 {
0888     auto path = shape.cast<model::Path>();
0889 
0890     if ( !path )
0891         return false;
0892 
0893     if ( path->shape.animated() )
0894         return false;
0895 
0896     const auto& bez = path->shape.get();
0897 
0898     if ( !bez.closed() || bez.size() != 4 )
0899         return false;
0900 
0901     for ( const auto& p : bez )
0902         if ( !math::fuzzy_compare(p.pos, p.tan_in) || !math::fuzzy_compare(p.pos, p.tan_out) )
0903             return false;
0904 
0905     std::array<qreal, 4> x;
0906     std::array<qreal, 4> y;
0907     for ( int i = 0; i < 4; i++ )
0908     {
0909         x[i] = bez[i].pos.x();
0910         y[i] = bez[i].pos.y();
0911     }
0912     std::sort(x.begin(), x.end());
0913     std::sort(y.begin(), y.end());
0914 
0915     return qFuzzyIsNull(x[0]-x[1]) &&
0916            qFuzzyIsNull(x[2]-x[3]) &&
0917            qFuzzyIsNull(y[0]-y[1]) &&
0918            qFuzzyIsNull(y[2]-y[3]);
0919 }
0920 
0921 bool skip_merge(const PropertyPair& prop)
0922 {
0923     if ( prop.match_name != "ADBE Vector Filter - Merge" )
0924         return false;
0925 
0926     auto type = prop.value->get("ADBE Vector Merge Type");
0927     if ( !type || type->class_type() != PropertyBase::Property )
0928         return false;
0929 
0930     auto type_prop = static_cast<const Property*>(type);
0931     if ( type_prop->animated )
0932         return false;
0933 
0934     if ( !qFuzzyCompare(type_prop->value.magnitude(), 4) )
0935         return false;
0936 
0937     return true;
0938 
0939 }
0940 
0941 void load_shape_list(ImportExport* io, model::Document* document, const PropertyBase& properties, model::ShapeListProperty& shapes)
0942 {
0943     model::ShapeElement* merge_rect = nullptr;
0944 
0945     for ( const auto& prop : properties )
0946     {
0947         if ( merge_rect && skip_merge(prop) )
0948         {
0949             shapes.remove(shapes.index_of(merge_rect));
0950             merge_rect = nullptr;
0951             continue;
0952         }
0953 
0954         if ( auto shape = load_shape(io, document, prop) )
0955         {
0956             if ( !merge_rect && is_merge_rect(*shape) )
0957                 merge_rect = shape.get();
0958 
0959             shapes.insert(std::move(shape), 0);
0960         }
0961     }
0962 }
0963 
0964 const ObjectFactory<model::ShapeElement>& shape_factory()
0965 {
0966     static ObjectFactory<model::ShapeElement> factory;
0967     static bool initialized = false;
0968     if ( !initialized )
0969     {
0970         initialized = true;
0971         factory.obj<model::Group>("ADBE Vector Group", [](ImportExport* io, model::Document* document, const PropertyPair& prop) {
0972             auto gp = std::make_unique<model::Group>(document);
0973             load_transform(io, gp->transform.get(), (*prop.value)["ADBE Vector Transform Group"], &gp->opacity, {1, 1}, true);
0974 
0975             load_shape_list(io, document, (*prop.value)["ADBE Vectors Group"], gp->shapes);
0976 
0977             return gp;
0978         });
0979         factory.obj<model::Rect>("ADBE Vector Shape - Rect")
0980             .prop(&model::Rect::reversed, "ADBE Vector Shape Direction", &convert_shape_reverse)
0981             .prop(&model::Rect::position, "ADBE Vector Rect Position")
0982             .prop(&model::Rect::size, "ADBE Vector Rect Size")
0983             .prop(&model::Rect::rounded, "ADBE Vector Rect Roundness")
0984         ;
0985         factory.obj<model::Ellipse>("ADBE Vector Shape - Ellipse")
0986             .prop(&model::Ellipse::reversed, "ADBE Vector Shape Direction", &convert_shape_reverse)
0987             .prop(&model::Ellipse::position, "ADBE Vector Ellipse Position")
0988             .prop(&model::Ellipse::size, "ADBE Vector Ellipse Size")
0989         ;
0990         factory.obj<model::PolyStar>("ADBE Vector Shape - Star")
0991             .prop(&model::PolyStar::reversed, "ADBE Vector Shape Direction", &convert_shape_reverse)
0992             .prop(&model::PolyStar::position, "ADBE Vector Star Position")
0993             .prop(&model::PolyStar::type, "ADBE Vector Star Type", &convert_enum<model::PolyStar::StarType>)
0994             .prop(&model::PolyStar::points, "ADBE Vector Star Points")
0995             .prop(&model::PolyStar::angle, "ADBE Vector Star Rotation")
0996             .prop(&model::PolyStar::inner_radius, "ADBE Vector Star Inner Radius")
0997             .prop(&model::PolyStar::outer_radius, "ADBE Vector Star Outer Radius")
0998             .prop(&model::PolyStar::inner_roundness, "ADBE Vector Star Inner Roundess", &convert_divide<100>)
0999             .prop(&model::PolyStar::outer_roundness, "ADBE Vector Star Outer Roundess", &convert_divide<100>)
1000         ;
1001         factory.obj<model::Path>("ADBE Vector Shape - Group")
1002             .prop(&model::Path::reversed, "ADBE Vector Shape Direction", &convert_shape_reverse)
1003             .prop(&model::Path::shape, "ADBE Vector Shape")
1004         ;
1005         const auto* fill = &factory.obj<model::Fill>("ADBE Vector Graphic - Fill")
1006             .ignore("ADBE Vector Blend Mode")
1007             .prop(&model::Fill::color, "ADBE Vector Fill Color", {}, QColor(255, 0, 0))
1008             .prop(&model::Fill::opacity, "ADBE Vector Fill Opacity", &convert_divide<100>)
1009             .prop(&model::Fill::fill_rule, "ADBE Vector Fill Rule", &convert_enum<model::Fill::Rule>)
1010             .ignore("ADBE Vector Composite Order") /// \todo could be parsed
1011         ;
1012         const auto* stroke = &factory.obj<model::Stroke>("ADBE Vector Graphic - Stroke")
1013             .ignore("ADBE Vector Blend Mode")
1014             .prop(&model::Stroke::color, "ADBE Vector Stroke Color", {}, QColor(255, 255, 255))
1015             .prop(&model::Stroke::opacity, "ADBE Vector Stroke Opacity", &convert_divide<100>)
1016             .prop(&model::Stroke::width, "ADBE Vector Stroke Width", {}, 2)
1017             .prop(&model::Stroke::cap, "ADBE Vector Stroke Line Cap", &convert_enum<model::Stroke::Cap>, model::Stroke::ButtCap)
1018             .prop(&model::Stroke::join, "ADBE Vector Stroke Line Join", &convert_enum<model::Stroke::Join>, model::Stroke::MiterJoin)
1019             .prop(&model::Stroke::miter_limit, "ADBE Vector Stroke Miter Limit", {}, 4)
1020             .ignore("ADBE Vector Stroke Dashes")
1021             .ignore("ADBE Vector Stroke Taper")
1022             .ignore("ADBE Vector Stroke Wave")
1023             .ignore("ADBE Vector Composite Order") /// \todo could be parsed
1024         ;
1025         factory.obj<model::RoundCorners>("ADBE Vector Filter - RC")
1026             .prop(&model::RoundCorners::radius, "ADBE Vector RoundCorner Radius", {}, 10)
1027         ;
1028         factory.obj<model::Trim>("ADBE Vector Filter - Trim")
1029             .prop(&model::Trim::start, "ADBE Vector Trim Start", &convert_divide<100>)
1030             .prop(&model::Trim::end, "ADBE Vector Trim End", &convert_divide<100>)
1031             .prop(&model::Trim::offset, "ADBE Vector Trim Offset", &convert_divide<360>)
1032             .prop(&model::Trim::multiple, "ADBE Vector Trim Type", &convert_enum<model::Trim::MultipleShapes>)
1033         ;
1034         factory.obj<model::OffsetPath>("ADBE Vector Filter - Offset")
1035             .prop(&model::OffsetPath::amount, "ADBE Vector Offset Amount")
1036             .prop(&model::OffsetPath::join, "ADBE Vector Offset Line Join", &convert_enum<model::Stroke::Join>, model::Stroke::MiterJoin)
1037             .prop(&model::OffsetPath::miter_limit, "ADBE Vector Offset Miter Limit", {}, 4)
1038         ;
1039         factory.obj<model::InflateDeflate>("ADBE Vector Filter - PB")
1040             .prop(&model::InflateDeflate::amount, "ADBE Vector PuckerBloat Amount", &convert_divide<100>)
1041         ;
1042         factory.obj<model::ZigZag>("ADBE Vector Filter - Zigzag")
1043             .prop(&model::ZigZag::amplitude, "ADBE Vector Zigzag Size", {}, 5)
1044             .prop(&model::ZigZag::frequency, "ADBE Vector Zigzag Detail", {}, 10)
1045             .prop(&model::ZigZag::style, "ADBE Vector Zigzag Points", &convert_enum<model::ZigZag::Style>)
1046         ;
1047         factory.obj<model::Fill>("ADBE Vector Graphic - G-Fill", [fill](ImportExport* io, model::Document* document, const PropertyPair& prop) {
1048             return load_gradient(fill, io, document, prop);
1049         });
1050         factory.obj<model::Stroke>("ADBE Vector Graphic - G-Stroke", [stroke](ImportExport* io, model::Document* document, const PropertyPair& prop) {
1051             return load_gradient(stroke, io, document, prop);
1052         });
1053         factory.obj<model::Repeater>("ADBE Vector Filter - Repeater", [](ImportExport* io, model::Document* document, const PropertyPair& prop) {
1054             auto shape = std::make_unique<model::Repeater>(document);
1055             if ( auto tf = prop.value->get("ADBE Vector Repeater Transform") )
1056             {
1057                 load_transform(io, shape->transform.get(), *tf, nullptr, {1, 1}, false);
1058                 const char* pmn = "ADBE Vector Repeater Start Opacity";
1059                 if ( auto o = tf->get(pmn) )
1060                     load_property_check(io, shape->start_opacity, *o, pmn, &convert_divide<100>);
1061                 pmn = "ADBE Vector Repeater End Opacity";
1062                 if ( auto o = tf->get(pmn) )
1063                     load_property_check(io, shape->end_opacity, *o, pmn, &convert_divide<100>);
1064             }
1065 
1066             if ( auto copies = prop.value->get("ADBE Vector Repeater Copies") )
1067             {
1068                 load_property_check(io, shape->copies, *copies, "ADBE Vector Repeater Copies");
1069             }
1070 
1071             return shape;
1072         });
1073     }
1074 
1075     return factory;
1076 };
1077 
1078 
1079 std::unique_ptr<model::ShapeElement> create_shape(ImportExport* io, model::Document* document, const PropertyPair& prop)
1080 {
1081     if ( auto shape = shape_factory().load(io, document, prop) )
1082         return shape;
1083 
1084     io->information(i18n("Unknown shape %1", prop.match_name));
1085     return nullptr;
1086 }
1087 
1088 } // namespace
1089 
1090 
1091 void glaxnimate::io::aep::AepLoader::load_layer(const glaxnimate::io::aep::Layer& ae_layer, CompData& data)
1092 {
1093 
1094     auto ulayer = std::make_unique<model::Layer>(document);
1095     auto layer = ulayer.get();
1096     data.comp->shapes.insert(std::move(ulayer), 0);
1097     data.layers[ae_layer.id] = layer;
1098 
1099     if ( ae_layer.parent_id || ae_layer.matte_id )
1100         data.pending.push_back({layer, ae_layer.parent_id, ae_layer.matte_id});
1101 
1102     layer->name.set(ae_layer.name);
1103     layer->render.set(!ae_layer.is_guide);
1104     layer->animation->first_frame.set(ae_layer.in_time);
1105     layer->animation->last_frame.set(ae_layer.out_time);
1106     layer->visible.set(ae_layer.properties.visible);
1107     /// \todo could be nice to toggle visibility based on solo/shy
1108     layer->group_color.set(label_colors[int(ae_layer.label_color)]);
1109 
1110     layer->transform->position.set({
1111         data.comp->width.get() / 2.,
1112         data.comp->height.get() / 2.,
1113     });
1114 
1115     QPointF anchor{1, 1};
1116     auto it = asset_size.find(ae_layer.asset_id);
1117     if ( it != asset_size.end() && !ae_layer.is_null )
1118     {
1119         anchor = it->second;
1120         layer->transform->anchor_point.set(anchor / 2);
1121     }
1122     load_transform(io, layer->transform.get(), ae_layer.properties["ADBE Transform Group"], &layer->opacity, anchor, false);
1123     layer->auto_orient.set(ae_layer.auto_orient);
1124     /// \todo masks "ADBE Mask Parade"
1125 
1126     if ( ae_layer.is_null )
1127         return;
1128     else if ( ae_layer.asset_id )
1129         asset_layer(layer, ae_layer, data);
1130     else if ( ae_layer.type == LayerType::ShapeLayer )
1131         shape_layer(layer, ae_layer, data);
1132     else if ( ae_layer.type == LayerType::TextLayer )
1133         text_layer(layer, ae_layer, data);
1134 
1135     auto mask_parade = ae_layer.properties.get("ADBE Mask Parade");
1136     if ( mask_parade )
1137     {
1138         layer->mask->mask.set(model::MaskSettings::Alpha);
1139 
1140         auto clip_p = std::make_unique<model::Group>(document);
1141         auto clip = clip_p.get();
1142         layer->shapes.insert(std::move(clip_p), 0);
1143         document->set_best_name(clip, i18n("Clip"));
1144 
1145         for ( const auto& mask : *mask_parade )
1146         {
1147             if ( mask.match_name != "ADBE Mask Atom" )
1148                 continue;
1149 
1150             auto group = std::make_unique<model::Group>(document);
1151 
1152             auto fill = std::make_unique<model::Fill>(document);
1153             fill->color.set(QColor(255, 255, 255));
1154             document->set_best_name(fill.get());
1155             auto mask_opacity = mask.value->get_pair("ADBE Mask Opacity");
1156             if ( mask_opacity )
1157                 load_property_check(io, fill->opacity, *mask_opacity->value, mask_opacity->match_name, &convert_divide<100>);
1158             group->shapes.insert(std::move(fill));
1159 
1160             if ( auto expansion = mask.value->get_pair("ADBE Mask Offset") )
1161             {
1162                 auto offset = std::make_unique<model::OffsetPath>(document);
1163                 document->set_best_name(offset.get());
1164                 load_property_check(io, offset->amount, *expansion->value, expansion->match_name);
1165                 group->shapes.insert(std::move(offset));
1166             }
1167 
1168             if ( auto shape = mask.value->get_pair("ADBE Mask Shape") )
1169             {
1170                 auto path = std::make_unique<model::Path>(document);
1171                 document->set_best_name(path.get());
1172                 load_property_check(io, path->shape, *shape->value, shape->match_name, {});
1173                 path->closed.set(true);
1174                 group->shapes.insert(std::move(path));
1175             }
1176 
1177             clip->shapes.insert(std::move(group));
1178         }
1179     }
1180 }
1181 
1182 void glaxnimate::io::aep::AepLoader::shape_layer(
1183     model::Layer* layer,
1184     const glaxnimate::io::aep::Layer& ae_layer,
1185     glaxnimate::io::aep::AepLoader::CompData&
1186 )
1187 {
1188     load_shape_list(io, document, ae_layer.properties["ADBE Root Vectors Group"], layer->shapes);
1189 }
1190 
1191 void glaxnimate::io::aep::AepLoader::asset_layer(
1192     model::Layer* layer, const Layer& ae_layer, CompData&
1193 )
1194 {
1195     auto img_it = images.find(ae_layer.asset_id);
1196     if ( img_it != images.end() )
1197     {
1198         auto image = std::make_unique<model::Image>(document);
1199         image->image.set(img_it->second);
1200         image->name.set(img_it->second->name.get());
1201         if ( layer->name.get().isEmpty() )
1202             layer->name.set(image->name.get());
1203         layer->shapes.insert(std::move(image));
1204         return;
1205     }
1206 
1207     auto comp_it = comps.find(ae_layer.asset_id);
1208     if ( comp_it != comps.end() )
1209     {
1210         /// \todo ADBE Time Remapping
1211         /// \todo Time stretch / start_time
1212         auto precomp = std::make_unique<model::PreCompLayer>(document);
1213         precomp->timing->start_time.set(ae_layer.start_time);
1214         precomp->timing->stretch.set(ae_layer.time_stretch);
1215         precomp->composition.set(comp_it->second);
1216         precomp->name.set(comp_it->second->name.get());
1217         precomp->size.set(comp_it->second->size());
1218         if ( layer->name.get().isEmpty() )
1219             layer->name.set(precomp->name.get());
1220         layer->shapes.insert(std::move(precomp));
1221         return;
1222     }
1223 
1224     auto solid_it = colors.find(ae_layer.asset_id);
1225     if ( solid_it != colors.end() )
1226     {
1227         auto fill = std::make_unique<model::Fill>(document);
1228         fill->color.set(solid_it->second.asset->color.get());
1229         fill->use.set(solid_it->second.asset);
1230         layer->shapes.insert(std::move(fill));
1231 
1232         auto rect = std::make_unique<model::Rect>(document);
1233         rect->size.set(QSizeF(
1234             solid_it->second.solid->width,
1235             solid_it->second.solid->height
1236         ));
1237         rect->position.set(QPointF(
1238             solid_it->second.solid->width / 2,
1239             solid_it->second.solid->height / 2
1240         ));
1241         layer->shapes.insert(std::move(rect));
1242 
1243         if ( layer->name.get().isEmpty() )
1244             layer->name.set(solid_it->second.asset->name.get());
1245         return;
1246     }
1247 
1248     warning(i18n("Unknown asset type for %1", ae_layer.name.isEmpty() ? "Layer" : ae_layer.name));
1249 }
1250 
1251 namespace {
1252 
1253 std::unique_ptr<model::ShapeElement> text_to_shapes(
1254     const TextDocument& doc, const std::vector<Font>& fonts, model::Document* document
1255 )
1256 {
1257     auto group = std::make_unique<model::Group>(document);
1258     auto pt = std::make_unique<model::TextShape>(document);
1259     auto text = pt.get();
1260     group->shapes.insert(std::move(pt));
1261     text->text.set(doc.text);
1262     if ( !doc.character_styles.empty() )
1263     {
1264         /// \todo Style text spans
1265         const auto& style = doc.character_styles[0];
1266         /// \todo figure out weight etc
1267         if ( style.font_index >= 0 && style.font_index < int(fonts.size()) )
1268             text->font->family.set(fonts[style.font_index].family);
1269         text->font->size.set(style.size);
1270 
1271         std::unique_ptr<model::Fill> fill;
1272         std::unique_ptr<model::Stroke> stroke;
1273 
1274         if ( style.stroke_enabled )
1275         {
1276             stroke = std::make_unique<model::Stroke>(document);
1277             stroke->color.set(style.stroke_color);
1278             stroke->width.set(style.stroke_width);
1279         }
1280 
1281         /// \todo fill enabled?
1282         fill = std::make_unique<model::Fill>(document);
1283         fill->color.set(style.fill_color);
1284 
1285         if ( style.stroke_over_fill )
1286         {
1287             if ( fill )
1288                 group->shapes.insert(std::move(fill));
1289             if ( stroke )
1290                 group->shapes.insert(std::move(stroke));
1291         }
1292         else
1293         {
1294             if ( stroke )
1295                 group->shapes.insert(std::move(stroke));
1296             if ( fill )
1297                 group->shapes.insert(std::move(fill));
1298         }
1299     }
1300 
1301     return group;
1302 }
1303 
1304 } // namespace
1305 
1306 void glaxnimate::io::aep::AepLoader::text_layer(model::Layer* layer, const Layer& ae_layer, CompData&)
1307 {
1308     auto prop = ae_layer.properties["ADBE Text Properties"]["ADBE Text Document"];
1309     if ( prop.class_type() != PropertyBase::TextProperty )
1310         return;
1311 
1312     const auto& tprop = static_cast<const TextProperty&>(prop);
1313 
1314     if ( tprop.documents.value.type() == PropertyValue::TextDocument )
1315     {
1316         layer->shapes.insert(text_to_shapes(
1317             std::get<TextDocument>(tprop.documents.value.value), tprop.fonts, document
1318         ));
1319         return;
1320     }
1321 
1322     if ( tprop.documents.keyframes.empty() )
1323         return;
1324 
1325     /// \todo animated text
1326     const auto& kf = tprop.documents.keyframes[0];
1327 
1328     if ( kf.value.type() == PropertyValue::TextDocument )
1329     {
1330         layer->shapes.insert(text_to_shapes(
1331             std::get<TextDocument>(kf.value.value), tprop.fonts, document
1332         ));
1333         return;
1334     }
1335 }
1336