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

0001 /*
0002  * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia <dev@dragon.best>
0003  *
0004  * SPDX-License-Identifier: GPL-3.0-or-later
0005  */
0006 #pragma once
0007 
0008 #include <QJsonDocument>
0009 #include <QJsonObject>
0010 
0011 #include "aep_riff.hpp"
0012 #include "ae_project.hpp"
0013 #include "cos.hpp"
0014 #include "gradient_xml.hpp"
0015 #include "aep_format.hpp"
0016 
0017 namespace glaxnimate::io::aep {
0018 
0019 class AepError : public std::runtime_error
0020 {
0021 public:
0022     AepError(QString message) : runtime_error(message.toStdString()), message(std::move(message)) {}
0023 
0024     QString message;
0025 };
0026 
0027 class AepParser
0028 {
0029 private:
0030     using Chunk = const RiffChunk*;
0031     using ChunkRange = RiffChunk::FindRange;
0032     struct PropertyContext
0033     {
0034         Composition* comp = nullptr;
0035         Layer* layer = nullptr;
0036 
0037         model::FrameTime time_to_frames(model::FrameTime time) const
0038         {
0039             return time / comp->time_scale + layer->start_time;
0040         }
0041     };
0042 
0043 public:
0044     AepParser(ImportExport* io) : io(io) {}
0045 
0046     Project parse(const RiffChunk& root)
0047     {
0048         if ( root.subheader != "Egg!" )
0049             throw AepError(i18n("Not an AEP file"));
0050 
0051         Project project;
0052         Chunk fold = nullptr, efdg = nullptr;
0053         root.find_multiple({&fold, &efdg}, {"Fold", "EfdG"});
0054 
0055         if ( load_unecessary && efdg )
0056             parse_effect_definitions(efdg->find_all("EfDf"), project);
0057 
0058         parse_folder(fold, project.folder, project);
0059 
0060         for ( auto& comp : project.compositions )
0061             parse_composition(comp_chunks[comp->id], *comp);
0062 
0063         return project;
0064     }
0065 
0066 private:
0067     void parse_folder(Chunk chunk, Folder& folder, Project& project)
0068     {
0069         FolderItem* current_item = nullptr;
0070 
0071         for ( const auto& child : chunk->children )
0072         {
0073             if ( *child == "fiac" )
0074             {
0075                 if ( current_item && child->data().read_uint8() )
0076                     project.current_item = current_item;
0077             }
0078             else if ( *child == "Item" )
0079             {
0080                 Chunk item = nullptr;
0081                 Chunk name_chunk = nullptr;
0082                 child->find_multiple({&item, &name_chunk}, {"idta", "Utf8"});
0083                 current_item = nullptr;
0084 
0085                 if ( !item )
0086                     continue;
0087 
0088                 auto name = to_string(name_chunk);
0089                 auto data = item->data();
0090                 auto type = data.read_uint16();
0091                 data.skip(14);
0092                 auto id = data.read_uint32();
0093                 data.skip(38);
0094                 auto color = LabelColors(data.read_uint8());
0095 
0096                 switch ( type )
0097                 {
0098                     case 1: // Folder
0099                     {
0100                         auto child_item = folder.add<Folder>();
0101                         child_item->id = id;
0102                         child_item->name = name;
0103                         current_item = child_item;
0104                         if ( auto contents = child->child("Sfdr") )
0105                             parse_folder(contents, *child_item, project);
0106                         break;
0107                     }
0108                     case 4: // Composition
0109                     {
0110                         auto comp = folder.add<Composition>();
0111                         comp->id = id;
0112                         comp->name = name;
0113                         current_item = comp;
0114                         project.compositions.push_back(comp);
0115                         project.assets[id] = comp;
0116                         comp_chunks[id] = child.get();
0117                         break;
0118                     }
0119                     case 7: // Asset
0120                         current_item = parse_asset(id, child->child("Pin "), folder, project);
0121                         break;
0122                     default:
0123                         warning(i18n("Unknown Item type %s", type));
0124                 }
0125 
0126                 if ( current_item )
0127                     current_item->label_color = color;
0128             }
0129         }
0130     }
0131 
0132     void parse_composition(Chunk chunk, Composition& comp)
0133     {
0134         auto cdta = chunk->child("cdta");
0135         if ( !cdta )
0136         {
0137             warning(i18n("Missing composition data"));
0138             return;
0139         }
0140 
0141         /// \todo label color?
0142         auto data = cdta->data();
0143         comp.resolution_x = data.read_uint16();
0144         comp.resolution_y = data.read_uint16();
0145         data.skip(1);
0146         // Time stuff
0147         comp.time_scale = data.read_uint16();
0148         data.skip(14);
0149         comp.playhead_time = comp.time_to_frames(data.read_uint16());
0150         data.skip(6);
0151         comp.in_time = comp.time_to_frames(data.read_uint16());
0152         data.skip(6);
0153         auto out_time = data.read_uint16();
0154         data.skip(6);
0155         comp.duration = comp.time_to_frames(data.read_uint16());
0156         if ( out_time == 0xffff )
0157             comp.out_time = comp.duration;
0158         else
0159             comp.out_time = comp.time_to_frames(out_time);
0160         data.skip(5);
0161 
0162         // Background
0163         comp.color.setRed(data.read_uint8());
0164         comp.color.setGreen(data.read_uint8());
0165         comp.color.setBlue(data.read_uint8());
0166 
0167         // Flags
0168         data.skip(84);
0169         Flags attr(data.read_uint8());
0170         comp.shy = attr.get(0, 0);
0171         comp.motion_blur = attr.get(0, 3);
0172         comp.frame_blending = attr.get(0, 4);
0173         comp.preserve_framerate = attr.get(0, 5);
0174         comp.preserve_resolution = attr.get(0, 7);
0175 
0176         // Lottie
0177         comp.width = data.read_uint16();
0178         comp.height = data.read_uint16();
0179         comp.pixel_ratio_width = data.read_uint32();
0180         comp.pixel_ratio_height = data.read_uint32();
0181         data.skip(4);
0182         comp.framerate = data.read_uint16();
0183 
0184         // Misc
0185         data.skip(16);
0186         comp.shutter_angle = data.read_uint16();
0187         comp.shutter_phase = data.read_sint32();
0188         data.skip(16);
0189         comp.samples_limit = data.read_uint32();
0190         comp.samples_per_frame = data.read_uint32();
0191 
0192         for ( const auto& child : chunk->children )
0193         {
0194             if ( *child == "Layr" )
0195                 comp.layers.push_back(parse_layer(child.get(), comp));
0196             else if ( load_unecessary && *child == "SecL" )
0197                 comp.markers = parse_layer(child.get(), comp);
0198             else if ( load_unecessary && (*child == "CLay" || *child == "DLay" || *child == "SLay") )
0199                 comp.views.push_back(parse_layer(child.get(), comp));
0200         }
0201     }
0202 
0203     QString to_string(Chunk chunk)
0204     {
0205         if ( !chunk )
0206             return "";
0207         auto data = chunk->data().read();
0208         if ( data == placeholder )
0209             return "";
0210 
0211         if ( chunk->header == "Utf8" )
0212             return QString::fromUtf8(data);
0213 
0214         warning(i18n("Unknown encoding for %1", chunk->header.to_string()));
0215         return "";
0216     }
0217 
0218     void warning(const QString& msg) const
0219     {
0220         io->warning(msg);
0221     }
0222 
0223     FolderItem* parse_asset(Id id, Chunk chunk, Folder& folder, Project& project)
0224     {
0225         Chunk sspc, utf8, als2, opti;
0226         sspc = utf8 = als2 = opti = nullptr;
0227         chunk->find_multiple({&sspc, &utf8, &als2, &opti}, {"sspc", "Utf8", "Als2", "opti"});
0228         if ( !sspc || !opti )
0229         {
0230             warning(i18n("Missing asset data"));
0231             return nullptr;
0232         }
0233 
0234         auto name = to_string(utf8);
0235         auto asset_reader = sspc->data();
0236         asset_reader.skip(32);
0237         auto width = asset_reader.read_uint16();
0238         asset_reader.skip(2);
0239         auto height = asset_reader.read_uint16();
0240         Asset* asset;
0241 
0242         auto data = opti->data();
0243 
0244         if ( data.read(4) == "Soli" )
0245         {
0246             data.skip(6);
0247             auto solid = folder.add<Solid>();
0248             solid->color.setAlphaF(data.read_float32());
0249             solid->color.setRedF(data.read_float32());
0250             solid->color.setGreenF(data.read_float32());
0251             solid->color.setBlueF(data.read_float32());
0252             solid->name = data.read_utf8_nul(256);
0253             asset = solid;
0254         }
0255         else
0256         {
0257             auto doc = QJsonDocument::fromJson(als2->child("alas")->data().read());
0258             QString path = doc.object()["fullpath"].toString();
0259             // Handle weird windows paths
0260             if ( path.contains('\\') && QDir::separator() == '/' )
0261             {
0262                 path = path.replace('\\', '/');
0263                 if ( path.size() > 1 && path[1] == ':' )
0264                     path = '/' + path;
0265             }
0266             auto file = folder.add<FileAsset>();
0267             file->path = QFileInfo(path);
0268             file->name = name.isEmpty() ? file->path.fileName() : name;
0269             asset = file;
0270         }
0271 
0272         asset->width = width;
0273         asset->height = height;
0274         asset->id = id;
0275         project.assets[id] = asset;
0276         return asset;
0277     }
0278 
0279     std::unique_ptr<Layer> parse_layer(Chunk chunk, Composition& comp)
0280     {
0281         auto layer = std::make_unique<Layer>();
0282 
0283         Chunk ldta, utf8, tdgp;
0284         ldta = utf8 = tdgp = nullptr;
0285         chunk->find_multiple({&ldta, &utf8, &tdgp}, {"ldta", "Utf8", "tdgp"});
0286         if ( !ldta )
0287         {
0288             warning(i18n("Missing layer data"));
0289             return {};
0290         }
0291 
0292         PropertyContext context{&comp, layer.get()};
0293         layer->name = to_string(utf8);
0294 
0295         auto data = ldta->data();
0296         layer->id = data.read_uint32();
0297         layer->quality = LayerQuality(data.read_uint16());
0298         data.skip(4);
0299         layer->time_stretch = data.read_uint16();
0300         data.skip(1);
0301         layer->start_time = comp.time_to_frames(data.read_sint16());
0302         data.skip(6);
0303         layer->in_time = context.time_to_frames(data.read_uint16());
0304         data.skip(6);
0305         layer->out_time = context.time_to_frames(data.read_uint16());
0306         data.skip(6);
0307         Flags flags = data.read_uint<3>();
0308         layer->is_guide = flags.get(2, 1);
0309         layer->bicubic_sampling = flags.get(2, 6);
0310         layer->auto_orient = flags.get(1, 0);
0311         layer->is_adjustment = flags.get(1, 1);
0312         layer->threedimensional = flags.get(1, 2);
0313         layer->solo = flags.get(1, 3);
0314         layer->is_null = flags.get(1, 7);
0315         layer->visible = flags.get(0, 0);
0316         layer->effects_enabled = flags.get(0, 2);
0317         layer->motion_blur = flags.get(0, 3);
0318         layer->locked = flags.get(0, 5);
0319         layer->shy = flags.get(0, 6);
0320         layer->continuously_rasterize = flags.get(0, 7);
0321         layer->asset_id = data.read_uint32();
0322         data.skip(17);
0323         layer->label_color = LabelColors(data.read_uint8());
0324         data.skip(2);
0325         data.skip(32); // Name, we get it from Utf8 instead
0326         data.skip(11);
0327         layer->matte_mode = TrackMatteType(data.read_uint8());
0328         data.skip(2);
0329         layer->time_stretch /= data.read_uint16();
0330         data.skip(19);
0331         layer->type = LayerType(data.read_uint8());
0332         layer->parent_id = data.read_uint32();
0333         data.skip(24);
0334         layer->matte_id = data.read_uint32();
0335 
0336         parse_property_group(tdgp, layer->properties, context);
0337 
0338         return layer;
0339     }
0340 
0341     void parse_property_group(Chunk chunk, PropertyGroup& group, const PropertyContext& context)
0342     {
0343         QString match_name;
0344         for ( auto it = chunk->children.begin(); it != chunk->children.end(); ++it )
0345         {
0346             auto child = it->get();
0347 
0348             if ( *child == "tdmn" )
0349             {
0350                 match_name = child->data().read_utf8_nul();
0351             }
0352             else if ( *child == "tdsb" )
0353             {
0354                 Flags flags = child->data().read_uint32();
0355                 group.visible = flags.get(0, 0);
0356             }
0357             else if ( *child == "tdsn" )
0358             {
0359                 group.name = to_string(child->child("Utf8"));
0360             }
0361             else if ( *child == "mkif" )
0362             {
0363                 auto mask = std::make_unique<Mask>();
0364                 auto data = child->data();
0365                 mask->inverted = data.read_uint8();
0366                 mask->locked = data.read_uint8();
0367                 data.skip(4);
0368                 mask->mode = MaskMode(data.read_uint16());
0369                 ++it;
0370                 if ( it == chunk->children.end() )
0371                 {
0372                     warning(i18n("Missing mask properties"));
0373                     return;
0374                 }
0375                 if ( **it != "tdgp" )
0376                 {
0377                     warning(i18n("Missing mask properties"));
0378                     continue;
0379                 }
0380 
0381                 parse_property_group(it->get(), mask->properties, context);
0382                 group.properties.push_back({match_name, std::move(mask)});
0383                 match_name.clear();
0384             }
0385             else if ( !match_name.isEmpty() )
0386             {
0387                 auto prop = parse_property(child, context);
0388                 if ( prop )
0389                     group.properties.push_back({match_name, std::move(prop)});
0390                 match_name.clear();
0391             }
0392         }
0393     }
0394 
0395     std::unique_ptr<PropertyGroup> parse_property_group(Chunk chunk, const PropertyContext& context)
0396     {
0397         auto group = std::make_unique<PropertyGroup>();
0398         parse_property_group(chunk, *group, context);
0399         return group;
0400     }
0401 
0402     std::unique_ptr<PropertyBase> parse_property(Chunk chunk, const PropertyContext& context)
0403     {
0404         if ( *chunk == "tdgp" )
0405             return parse_property_group(chunk, context);
0406         else if ( *chunk == "tdbs" )
0407             return parse_animated_property(chunk, context, {});
0408         else if ( *chunk == "om-s" )
0409             return parse_animated_with_values(chunk, context, "omks", "shap", &AepParser::parse_bezier);
0410         else if ( *chunk == "GCst" )
0411             return parse_animated_with_values(chunk, context, "GCky", "Utf8", &AepParser::parse_gradient);
0412         else if ( *chunk == "btds" )
0413             return parse_animated_text(chunk, context);
0414         else if ( *chunk == "sspc" )
0415             return parse_effect_instance(chunk, context);
0416         else if ( *chunk == "otst" )
0417             return load_unecessary ? parse_animated_with_values(chunk, context, "otky", "otda", &AepParser::parse_orientation) : nullptr;
0418         else if ( *chunk == "mrst" )
0419             return load_unecessary ? parse_animated_with_values(chunk, context, "mrky", "Nmrd", &AepParser::parse_marker) : nullptr;
0420         // I've seen these in files but I'm not sure how to parse them
0421         else if ( *chunk == "OvG2" || *chunk == "blsi" || *chunk == "blsv" )
0422             return {};
0423 
0424         warning(i18n("Unknown property type: %1", chunk->name().to_string()));
0425         return {};
0426     }
0427 
0428     std::unique_ptr<Property> parse_animated_property(
0429         Chunk chunk, const PropertyContext& context, std::vector<PropertyValue>&& values
0430     )
0431     {
0432         auto prop = std::make_unique<Property>();
0433         parse_animated_property(prop.get(), chunk, context, std::move(values));
0434         return prop;
0435     }
0436 
0437     void parse_animated_property(
0438         Property* prop, Chunk chunk, const PropertyContext& context, std::vector<PropertyValue> values
0439     )
0440     {
0441         Chunk tdsb, header, value, keyframes, expression, tdpi, tdps, tdli;
0442         tdsb = header = value = keyframes = expression = tdpi = tdps = tdli = nullptr;
0443         chunk->find_multiple(
0444             {&tdsb, &header, &value, &keyframes, &expression, &tdpi, &tdps, &tdli},
0445             {"tdsb", "tdb4", "cdat", "list", "Utf8", "tdpi", "tdps", "tdli"}
0446         );
0447 
0448         if ( tdsb )
0449         {
0450             Flags flags(tdsb->data().read_uint32());
0451             prop->split = flags.get(1, 3);
0452         }
0453 
0454         auto data = header->data();
0455         data.skip(2);
0456         prop->components = data.read_uint16();
0457 
0458         bool position = Flags(data.read_uint16()).get(0, 3);
0459         data.skip(10+8*5);
0460         Flags type = data.read_uint32();
0461         bool no_value = type.get(2, 0);
0462         bool color = type.get(0, 0);
0463         bool integer = type.get(0, 2);
0464         data.skip(8);
0465 
0466         if ( position )
0467             prop->type = PropertyType::Position;
0468         else if ( color )
0469             prop->type = PropertyType::Color;
0470         else if ( no_value )
0471             prop->type = PropertyType::NoValue;
0472         else if ( integer )
0473             prop->type = PropertyType::Integer;
0474         else
0475             prop->type = PropertyType::MultiDimensional;
0476 
0477         prop->animated = data.read_uint8() == 1;
0478 
0479         data.skip(6);
0480 
0481         prop->is_component = data.read_uint8() == 1;
0482 
0483         if ( integer && tdpi )
0484         {
0485             prop->type = PropertyType::LayerSelection;
0486             LayerSelection val;
0487             val.layer_id = tdpi->data().read_uint32();
0488             if ( tdps )
0489                 val.layer_source = LayerSource(tdps->data().read_sint32());
0490             prop->value = val;
0491         }
0492         else if ( integer && tdli )
0493         {
0494             prop->type = PropertyType::MaskIndex;
0495             prop->value = tdli->data().read_uint32();
0496         }
0497         else if ( keyframes )
0498         {
0499             auto raw_keys = list_values(keyframes);
0500             for ( std::size_t i = 0; i < raw_keys.size(); i++ ) {
0501                 prop->keyframes.push_back(load_keyframe(i, raw_keys[i], *prop, context, values));
0502             }
0503         }
0504         else if ( value )
0505         {
0506             auto vdat = value->data();
0507             auto raw_value = vdat.read_array(&BinaryReader::read_float64, prop->components);
0508             prop->value = property_value(0, raw_value, values, prop->type);
0509         }
0510 
0511         if ( expression )
0512             prop->expression = to_string(expression);
0513     }
0514 
0515     PropertyValue property_value(
0516         int index,
0517         const std::vector<qreal>& raw_value,
0518         std::vector<PropertyValue>& values,
0519         PropertyType type
0520     )
0521     {
0522         switch ( type )
0523         {
0524             case PropertyType::NoValue:
0525                 if ( index < int(values.size()) )
0526                     return std::move(values[index]);
0527                 return nullptr;
0528             case PropertyType::Color:
0529                 if ( raw_value.size() < 4 )
0530                     return QColor();
0531                 return QColor(raw_value[1], raw_value[2], raw_value[3], raw_value[0]);
0532             default:
0533                 return vector_value(raw_value);
0534         }
0535     }
0536 
0537     PropertyValue vector_value(const std::vector<qreal>& raw_value)
0538     {
0539         switch ( raw_value.size() )
0540         {
0541             case 0:
0542                 return nullptr;
0543             case 1:
0544                 return raw_value[0];
0545             case 2:
0546                 return QPointF(raw_value[0], raw_value[1]);
0547             case 3:
0548             default:
0549                 return QVector3D(raw_value[0], raw_value[1], raw_value[2]);
0550         }
0551     }
0552 
0553     Keyframe load_keyframe(int index, BinaryReader& reader, Property& prop, const PropertyContext& context, std::vector<PropertyValue>& values)
0554     {
0555         reader.prepare();
0556         Keyframe kf;
0557 
0558         reader.skip(1);
0559         kf.time = context.time_to_frames(reader.read_uint16());
0560         reader.skip(2);
0561 
0562         kf.transition_type = KeyframeTransitionType(reader.read_uint8());
0563 
0564         kf.label_color = LabelColors(reader.read_uint8());
0565 
0566         Flags flags  = reader.read_uint8();
0567         kf.roving = flags.get(0, 5);
0568         if ( flags.get(0, 3) )
0569             kf.bezier_mode = KeyframeBezierMode::Continuous;
0570         else if ( flags.get(0, 4) )
0571             kf.bezier_mode = KeyframeBezierMode::Auto;
0572         else
0573             kf.bezier_mode = KeyframeBezierMode::Normal;
0574 
0575         if ( prop.type == PropertyType::NoValue )
0576         {
0577             reader.skip(16);
0578             kf.in_speed.push_back(reader.read_float64());
0579             kf.in_influence.push_back(reader.read_float64());
0580             kf.out_speed.push_back(reader.read_float64());
0581             kf.out_influence.push_back(reader.read_float64());
0582             kf.value = std::move(values[index]);
0583         }
0584         else if ( prop.type == PropertyType::MultiDimensional || prop.type == PropertyType::Integer )
0585         {
0586             kf.value = vector_value(reader.read_array(&BinaryReader::read_float64, prop.components));
0587             kf.in_speed = reader.read_array(&BinaryReader::read_float64, prop.components);
0588             kf.in_influence = reader.read_array(&BinaryReader::read_float64, prop.components);
0589             kf.out_speed = reader.read_array(&BinaryReader::read_float64, prop.components);
0590             kf.out_influence = reader.read_array(&BinaryReader::read_float64, prop.components);
0591         }
0592         else if ( prop.type == PropertyType::Position )
0593         {
0594             reader.skip(16);
0595             kf.in_speed.push_back(reader.read_float64());
0596             kf.in_influence.push_back(reader.read_float64());
0597             kf.out_speed.push_back(reader.read_float64());
0598             kf.out_influence.push_back(reader.read_float64());
0599             kf.value = vector_value(reader.read_array(&BinaryReader::read_float64, prop.components));
0600             auto it = reader.read_array(&BinaryReader::read_float64, prop.components);
0601             auto ot = reader.read_array(&BinaryReader::read_float64, prop.components);
0602             if ( prop.components >= 2 )
0603             {
0604                 kf.in_tangent = {it[0], it[1]};
0605                 kf.out_tangent = {ot[0], ot[1]};
0606             }
0607         }
0608         else if ( prop.type == PropertyType::Color )
0609         {
0610             reader.skip(16);
0611             kf.in_speed.push_back(reader.read_float64());
0612             kf.in_influence.push_back(reader.read_float64());
0613             kf.out_speed.push_back(reader.read_float64());
0614             kf.out_influence.push_back(reader.read_float64());
0615             auto value = reader.read_array(&BinaryReader::read_float64, prop.components);
0616             kf.value = QColor(value[1], value[2], value[3], value[0]);
0617         }
0618 
0619         return kf;
0620     }
0621 
0622     template<class T>
0623     std::unique_ptr<Property> parse_animated_with_values(
0624         Chunk chunk, const PropertyContext& context,
0625         const char* container, const char* value_name,
0626         T (AepParser::*parse)(Chunk chunk)
0627     )
0628     {
0629         Chunk value_container, tdbs;
0630         value_container = tdbs = nullptr;
0631         chunk->find_multiple({&value_container, &tdbs}, {container, "tdbs"});
0632         std::vector<PropertyValue> values;
0633         for ( const RiffChunk& value_chunk : value_container->find_all(value_name) )
0634             values.emplace_back((this->*parse)(&value_chunk));
0635         return parse_animated_property(tdbs, context, std::move(values));
0636     }
0637 
0638     std::vector<BinaryReader> list_values(Chunk list)
0639     {
0640         Chunk head, vals;
0641         head = vals = nullptr;
0642         list->find_multiple({&head, &vals}, {"lhd3", "ldat"});
0643         if ( !head || !vals )
0644         {
0645             warning(i18n("Missing list data"));
0646             return {};
0647         }
0648 
0649         auto data = head->data();
0650         data.skip(10);
0651         std::uint32_t count = data.read_uint16();
0652         data.skip(6);
0653         std::uint32_t size = data.read_uint16();
0654         std::uint32_t total_size = count * size;
0655         if ( vals->reader.size() < total_size )
0656         {
0657             warning(i18n("Not enough data in list"));
0658             return {};
0659         }
0660 
0661         std::vector<BinaryReader> values;
0662         values.reserve(count);
0663         for ( std::uint32_t i = 0; i < count; i++ )
0664             values.push_back(vals->reader.sub_reader(size, i * size));
0665         vals->reader.prepare();
0666         return values;
0667     }
0668 
0669     BezierData parse_bezier(Chunk chunk)
0670     {
0671         BezierData data;
0672         auto bounds = chunk->child("shph")->data();
0673         bounds.skip(3);
0674         data.closed = !Flags(bounds.read_uint8()).get(0, 3);
0675         data.minimum.setX(bounds.read_float32());
0676         data.minimum.setY(bounds.read_float32());
0677         data.maximum.setX(bounds.read_float32());
0678         data.maximum.setY(bounds.read_float32());
0679 
0680         for ( auto& pt : list_values(chunk->child("list")) )
0681         {
0682             float x = pt.read_float32();
0683             float y = pt.read_float32();
0684             data.points.push_back({x, y});
0685         }
0686 
0687         return data;
0688     }
0689 
0690     Gradient parse_gradient(Chunk chunk)
0691     {
0692         return parse_gradient_xml(to_string(chunk));
0693     }
0694 
0695     QVector3D parse_orientation(Chunk chunk)
0696     {
0697         auto data = chunk->data();
0698         QVector3D v;
0699         v.setX(data.read_float64());
0700         v.setY(data.read_float64());
0701         v.setZ(data.read_float64());
0702         return v;
0703     }
0704 
0705     Marker parse_marker(Chunk chunk)
0706     {
0707         Marker marker;
0708         marker.name = to_string(chunk->child("Utf8"));
0709         auto data = chunk->child("NmHd")->data();
0710         data.skip(4);
0711         marker.is_protected = data.read_uint8() & 2;
0712         data.skip(4);
0713         marker.duration = data.read_uint32();
0714         data.skip(4);
0715         marker.label_color = LabelColors(data.read_uint8());
0716         return marker;
0717     }
0718 
0719     QColor cos_color(const CosValue& cos)
0720     {
0721         const auto& arr = *cos.get<CosValue::Index::Array>();
0722         if ( arr.size() < 4 )
0723             throw CosError("Not enough components for color");
0724 
0725         return QColor::fromRgbF(
0726             arr[1].get<CosValue::Index::Number>(),
0727             arr[2].get<CosValue::Index::Number>(),
0728             arr[3].get<CosValue::Index::Number>(),
0729             arr[0].get<CosValue::Index::Number>()
0730         );
0731     }
0732 
0733     TextDocument parse_text_document(const CosValue& cos)
0734     {
0735         TextDocument doc;
0736         doc.text = get_as<CosValue::Index::String>(cos, 0, 0);
0737 
0738         for ( const auto& cs : *get_as<CosValue::Index::Array>(cos, 0, 5, 0) )
0739         {
0740             LineStyle style;
0741             style.character_count = get_as<CosValue::Index::Number>(cs, 1);
0742             const auto& data = get(cs, 0, 0, 5);
0743             style.text_justify = TextJustify(get_as<CosValue::Index::Number>(data, 0));
0744             doc.line_styles.emplace_back(std::move(style));
0745         }
0746 
0747         for ( const auto& cs : *get_as<CosValue::Index::Array>(cos, 0, 6, 0) )
0748         {
0749             CharacterStyle style;
0750             style.character_count = get_as<CosValue::Index::Number>(cs, 1);
0751             const auto& data = get(cs, 0, 0, 6);
0752             style.font_index = get_as<CosValue::Index::Number>(data, 0);
0753             style.size = get_as<CosValue::Index::Number>(data, 1);
0754             style.faux_bold = get_as<CosValue::Index::Boolean>(data, 2);
0755             style.faux_italic = get_as<CosValue::Index::Boolean>(data, 3);
0756             style.text_transform = TextTransform(get_as<CosValue::Index::Number>(data, 12));
0757             style.vertical_align = TextVerticalAlign(get_as<CosValue::Index::Number>(data, 13));
0758             style.fill_color = cos_color(get(data, 53, 0, 1));
0759             style.stroke_color = cos_color(get(data, 54, 0, 1));
0760             style.stroke_enabled = get_as<CosValue::Index::Boolean>(data, 57);
0761             style.stroke_over_fill = get_as<CosValue::Index::Boolean>(data, 58);
0762             style.stroke_width = get_as<CosValue::Index::Number>(data, 63);
0763             doc.character_styles.emplace_back(std::move(style));
0764         }
0765 
0766         return doc;
0767     }
0768 
0769     std::unique_ptr<PropertyBase> parse_animated_text(Chunk chunk, const PropertyContext& context)
0770     {
0771         Chunk text_data, tdbs;
0772         text_data = tdbs = nullptr;
0773         chunk->find_multiple({&text_data, &tdbs}, {"btdk", "tdbs"});
0774         try {
0775             auto val = CosParser(text_data->data().read()).parse();
0776             if ( val.type() != CosValue::Index::Object )
0777                 throw CosError("Expected Object");
0778 
0779             auto property = std::make_unique<TextProperty>();
0780             for ( const auto& font : *get_as<CosValue::Index::Array>(val, 0, 1, 0) )
0781                 property->fonts.push_back({get_as<CosValue::Index::String>(font, 0, 0, 0)});
0782 
0783             std::vector<PropertyValue> values;
0784             for ( const auto& doc : *get_as<CosValue::Index::Array>(val, 1, 1) )
0785                 values.push_back(parse_text_document(doc));
0786 
0787             parse_animated_property(&property->documents, tdbs, context, std::move(values));
0788             return property;
0789 
0790         } catch ( const CosError& err ) {
0791             warning(i18n("Invalid text document: %1", err.message));
0792             return {};
0793         }
0794     }
0795 
0796     void parse_effect_definitions(const ChunkRange& range, Project& project)
0797     {
0798         for ( const auto& chunk : range )
0799         {
0800             Chunk tdmn, sspc;
0801             tdmn = sspc = nullptr;
0802             chunk.find_multiple({&tdmn, &sspc}, {"tdmn", "sspc"});
0803             if ( !tdmn || !sspc )
0804                 continue;
0805 
0806             auto mn = tdmn->data().read_utf8_nul();
0807             EffectDefinition& effect = project.effects[mn];
0808             effect.match_name = mn;
0809 
0810             Chunk fnam, part;
0811             fnam = part = nullptr;
0812             chunk.find_multiple({&fnam, &part}, {"fnam", "parT"});
0813             if ( fnam )
0814                 effect.name = to_string(fnam->child("Utf8"));
0815 
0816             QString param_mn;
0817             for ( const auto& param_chunk : part->children )
0818             {
0819                 if ( *param_chunk == "tdmn" )
0820                 {
0821                     param_mn = param_chunk->data().read_utf8_nul();
0822                 }
0823                 else
0824                 {
0825                     auto& param = effect.parameter_map[param_mn];
0826                     param.match_name = param_mn;
0827                     effect.parameters.push_back(&param);
0828                     parse_effect_parameter(param, param_chunk->data());
0829                 }
0830             }
0831         }
0832     }
0833 
0834     void parse_effect_parameter(EffectParameter& param, BinaryReader data)
0835     {
0836         data.skip(15);
0837         param.type = EffectParameterType(data.read_uint8());
0838         param.name = data.read_utf8_nul(32);
0839         data.skip(8);
0840 
0841         switch ( param.type )
0842         {
0843             case EffectParameterType::Layer:
0844                 param.last_value = LayerSelection();
0845                 param.default_value = LayerSelection();
0846                 break;
0847             case EffectParameterType::Scalar:
0848             case EffectParameterType::Angle:
0849                 param.last_value = data.read_sint32() / 0x10000;
0850                 param.default_value = 0;
0851                 break;
0852             case EffectParameterType::Boolean:
0853                 param.last_value = data.read_uint32();
0854                 param.default_value = data.read_uint8();
0855                 break;
0856             case EffectParameterType::Color:
0857             {
0858                 auto a = data.read_uint8();
0859                 auto r = data.read_uint8();
0860                 auto g = data.read_uint8();
0861                 auto b = data.read_uint8();
0862                 param.last_value = QColor(r, g, b, a);
0863                 data.skip(1);
0864                 a = 255;
0865                 r = data.read_uint8();
0866                 g = data.read_uint8();
0867                 b = data.read_uint8();
0868                 param.default_value = QColor(r, g, b, a);
0869                 break;
0870             }
0871             case EffectParameterType::Vector2D:
0872             {
0873                 qreal x = data.read_sint32();
0874                 qreal y = data.read_sint32();
0875                 param.last_value = QPointF(x / 0x80, y / 0x80);
0876                 param.default_value = QPointF();
0877                 break;
0878             }
0879             case EffectParameterType::Enum:
0880                 param.last_value = data.read_uint32();
0881                 data.skip(2); // Number of enum values
0882                 param.default_value = data.read_uint16();
0883                 break;
0884             case EffectParameterType::Slider:
0885                 param.last_value = data.read_float64();
0886                 param.default_value = 0;
0887                 break;
0888             case EffectParameterType::Vector3D:
0889             {
0890                 auto x3 = data.read_float64() * 512;
0891                 auto y3 = data.read_float64() * 512;
0892                 auto z3 = data.read_float64() * 512;
0893                 param.last_value = QVector3D(x3, y3, z3);
0894                 param.default_value = QVector3D(0, 0, 0);
0895                 break;
0896             }
0897             default:
0898                 param.last_value = 0;
0899                 param.default_value = 0;
0900                 break;
0901         }
0902 
0903     }
0904 
0905     std::unique_ptr<EffectInstance> parse_effect_instance(Chunk chunk, const PropertyContext& context)
0906     {
0907         if ( !load_unecessary )
0908             return {};
0909         auto effect = std::make_unique<EffectInstance>();
0910         Chunk fnam, tdgp;
0911         fnam = tdgp = nullptr;
0912         chunk->find_multiple({&fnam, &tdgp}, {"fnam", "tdgp"});
0913         if ( fnam )
0914            effect->name = to_string(fnam->child("Utf8"));
0915         parse_property_group(tdgp, effect->parameters, context);
0916         return effect;
0917     }
0918 
0919     static constexpr const char* const placeholder = "-_0_/-";
0920     std::unordered_map<Id, Chunk> comp_chunks;
0921     // For loading into the object model stuff Glaxnimate doesn't support
0922     // I'm adding the code to load them just in case someone wants to do
0923     // something else with them and so that if Glaxnimate ever supports given
0924     // features, it's easier to add
0925     const bool load_unecessary = false;
0926     ImportExport* io;
0927 };
0928 
0929 } // namespace glaxnimate::io::aep
0930 
0931