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