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 };