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 #include "svg_parser.hpp" 0008 #include "svg_parser_private.hpp" 0009 0010 using namespace glaxnimate::io::svg::detail; 0011 0012 class glaxnimate::io::svg::SvgParser::Private : public SvgParserPrivate 0013 { 0014 public: 0015 Private( 0016 model::Document* document, 0017 const std::function<void(const QString&)>& on_warning, 0018 ImportExport* io, 0019 QSize forced_size, 0020 model::FrameTime default_time, 0021 GroupMode group_mode, 0022 QDir default_asset_path 0023 ) : SvgParserPrivate(document, on_warning, io, forced_size, default_time), 0024 group_mode(group_mode), 0025 default_asset_path(default_asset_path) 0026 {} 0027 0028 protected: 0029 void on_parse_prepare(const QDomElement&) override 0030 { 0031 for ( const auto& p : shape_parsers ) 0032 to_process += dom.elementsByTagName(p.first).count(); 0033 } 0034 0035 QSizeF get_size(const QDomElement& svg) override 0036 { 0037 return { 0038 len_attr(svg, "width", size.width()), 0039 len_attr(svg, "height", size.height()) 0040 }; 0041 } 0042 0043 void on_parse(const QDomElement& svg) override 0044 { 0045 dpi = attr(svg, "inkscape", "export-xdpi", "96").toDouble(); 0046 0047 QPointF pos; 0048 QVector2D scale{1, 1}; 0049 if ( svg.hasAttribute("viewBox") ) 0050 { 0051 auto vb = split_attr(svg, "viewBox"); 0052 if ( vb.size() == 4 ) 0053 { 0054 qreal vbx = vb[0].toDouble(); 0055 qreal vby = vb[1].toDouble(); 0056 qreal vbw = vb[2].toDouble(); 0057 qreal vbh = vb[3].toDouble(); 0058 0059 if ( !forced_size.isValid() ) 0060 { 0061 if ( !svg.hasAttribute("width") ) 0062 size.setWidth(vbw); 0063 if ( !svg.hasAttribute("height") ) 0064 size.setHeight(vbh); 0065 } 0066 0067 pos = -QPointF(vbx, vby); 0068 if ( vbw != 0 && vbh != 0 ) 0069 { 0070 scale = QVector2D(size.width() / vbw, size.height() / vbh); 0071 0072 if ( forced_size.isValid() ) 0073 { 0074 auto single = qMin(scale.x(), scale.y()); 0075 scale = QVector2D(single, single); 0076 } 0077 } 0078 } 0079 } 0080 0081 for ( const auto& link_node : ItemCountRange(dom.elementsByTagName("link")) ) 0082 { 0083 auto link = link_node.toElement(); 0084 if ( link.attribute("rel") == "stylesheet" ) 0085 { 0086 QString url = link.attribute("href"); 0087 if ( !url.isEmpty() ) 0088 document->add_pending_asset("", QUrl(url)); 0089 } 0090 } 0091 0092 parse_css(); 0093 parse_assets(); 0094 parse_metadata(); 0095 0096 model::Layer* parent_layer = add_layer(&main->shapes); 0097 parent_layer->transform.get()->position.set(-pos); 0098 parent_layer->transform.get()->scale.set(scale); 0099 parent_layer->name.set( 0100 attr(svg, "sodipodi", "docname", svg.attribute("id", parent_layer->type_name_human())) 0101 ); 0102 0103 Style default_style(Style::Map{ 0104 {"fill", "black"}, 0105 }); 0106 parse_children({svg, &parent_layer->shapes, parse_style(svg, default_style), false}); 0107 0108 main->name.set( 0109 attr(svg, "sodipodi", "docname", "") 0110 ); 0111 } 0112 0113 void parse_shape(const ParseFuncArgs& args) override 0114 { 0115 if ( handle_mask(args) ) 0116 return; 0117 0118 parse_shape_impl(args); 0119 } 0120 0121 private: 0122 void parse_css() 0123 { 0124 CssParser parser(css_blocks); 0125 0126 for ( const auto& style : ItemCountRange(dom.elementsByTagName("style")) ) 0127 { 0128 QString data; 0129 for ( const auto & child : ItemCountRange(style.childNodes()) ) 0130 { 0131 if ( child.isText() || child.isCDATASection() ) 0132 data += child.toCharacterData().data(); 0133 } 0134 0135 if ( data.contains("@font-face") ) 0136 document->add_pending_asset("", data.toUtf8()); 0137 0138 parser.parse(data); 0139 } 0140 0141 std::stable_sort(css_blocks.begin(), css_blocks.end()); 0142 } 0143 0144 void parse_defs(const QDomNode& node) 0145 { 0146 if ( !node.isElement() ) 0147 return; 0148 0149 auto defs = node.toElement(); 0150 for ( const auto& def : ElementRange(defs) ) 0151 { 0152 if ( def.tagName().startsWith("animate") ) 0153 { 0154 QString link = attr(def, "xlink", "href"); 0155 if ( link.isEmpty() || link[0] != '#' ) 0156 continue; 0157 animate_parser.store_animate(link.mid(1), def); 0158 } 0159 } 0160 } 0161 0162 void parse_assets() 0163 { 0164 std::vector<QDomElement> later; 0165 0166 for ( const auto& domnode : ItemCountRange(dom.elementsByTagName("linearGradient")) ) 0167 parse_gradient_node(domnode, later); 0168 0169 for ( const auto& domnode : ItemCountRange(dom.elementsByTagName("radialGradient")) ) 0170 parse_gradient_node(domnode, later); 0171 0172 std::vector<QDomElement> unprocessed; 0173 while ( !later.empty() && unprocessed.size() != later.size() ) 0174 { 0175 unprocessed.clear(); 0176 0177 for ( const auto& element : later ) 0178 parse_brush_style_check(element, unprocessed); 0179 0180 std::swap(later, unprocessed); 0181 } 0182 0183 0184 for ( const auto& defs : ItemCountRange(dom.elementsByTagName("defs")) ) 0185 parse_defs(defs); 0186 } 0187 0188 void parse_gradient_node(const QDomNode& domnode, std::vector<QDomElement>& later) 0189 { 0190 if ( !domnode.isElement() ) 0191 return; 0192 0193 auto gradient = domnode.toElement(); 0194 QString id = gradient.attribute("id"); 0195 if ( id.isEmpty() ) 0196 return; 0197 0198 if ( parse_brush_style_check(gradient, later) ) 0199 parse_gradient_nolink(gradient, id); 0200 } 0201 0202 bool parse_brush_style_check(const QDomElement& element, std::vector<QDomElement>& later) 0203 { 0204 QString link = attr(element, "xlink", "href"); 0205 if ( link.isEmpty() ) 0206 return true; 0207 0208 if ( !link.startsWith("#") ) 0209 return false; 0210 0211 auto it = brush_styles.find(link); 0212 if ( it != brush_styles.end() ) 0213 { 0214 brush_styles["#" + element.attribute("id")] = it->second; 0215 return false; 0216 } 0217 0218 0219 auto it1 = gradients.find(link); 0220 if ( it1 != gradients.end() ) 0221 { 0222 parse_gradient(element, element.attribute("id"), it1->second); 0223 return false; 0224 } 0225 0226 later.push_back(element); 0227 return false; 0228 } 0229 0230 QGradientStops parse_gradient_stops(const QDomElement& gradient) 0231 { 0232 QGradientStops stops; 0233 0234 for ( const auto& domnode : ItemCountRange(gradient.childNodes()) ) 0235 { 0236 if ( !domnode.isElement() ) 0237 continue; 0238 0239 auto stop = domnode.toElement(); 0240 0241 if ( stop.tagName() != "stop" ) 0242 continue; 0243 0244 Style style = parse_style(stop, {}); 0245 if ( !style.contains("stop-color") ) 0246 continue; 0247 QColor color = parse_color(style["stop-color"], QColor()); 0248 color.setAlphaF(color.alphaF() * style.get("stop-opacity", "1").toDouble()); 0249 0250 stops.push_back({stop.attribute("offset", "0").toDouble(), color}); 0251 } 0252 0253 utils::sort_gradient(stops); 0254 0255 return stops; 0256 } 0257 0258 void parse_gradient_nolink(const QDomElement& gradient, const QString& id) 0259 { 0260 QGradientStops stops = parse_gradient_stops(gradient); 0261 0262 if ( stops.empty() ) 0263 return; 0264 0265 if ( stops.size() == 1 ) 0266 { 0267 auto col = std::make_unique<model::NamedColor>(document); 0268 col->name.set(id); 0269 col->color.set(stops[0].second); 0270 brush_styles["#"+id] = col.get(); 0271 auto anim = parse_animated(gradient.firstChildElement("stop")); 0272 0273 for ( const auto& kf : anim.single("stop-color") ) 0274 col->color.set_keyframe(kf.time, kf.values.color())->set_transition(kf.transition); 0275 0276 document->assets()->colors->values.insert(std::move(col)); 0277 return; 0278 } 0279 0280 auto colors = std::make_unique<model::GradientColors>(document); 0281 colors->name.set(id); 0282 colors->colors.set(stops); 0283 gradients["#"+id] = colors.get(); 0284 auto ptr = colors.get(); 0285 document->assets()->gradient_colors->values.insert(std::move(colors)); 0286 parse_gradient(gradient, id, ptr); 0287 } 0288 0289 void parse_gradient(const QDomElement& element, const QString& id, model::GradientColors* colors) 0290 { 0291 auto gradient = std::make_unique<model::Gradient>(document); 0292 QTransform gradient_transform; 0293 0294 if ( element.hasAttribute("gradientTransform") ) 0295 gradient_transform = svg_transform(element.attribute("gradientTransform"), {}).transform; 0296 0297 if ( element.tagName() == "linearGradient" ) 0298 { 0299 if ( !element.hasAttribute("x1") || !element.hasAttribute("x2") || 0300 !element.hasAttribute("y1") || !element.hasAttribute("y2") ) 0301 return; 0302 0303 gradient->type.set(model::Gradient::Linear); 0304 0305 gradient->start_point.set(gradient_transform.map(QPointF( 0306 len_attr(element, "x1"), 0307 len_attr(element, "y1") 0308 ))); 0309 gradient->end_point.set(gradient_transform.map(QPointF( 0310 len_attr(element, "x2"), 0311 len_attr(element, "y2") 0312 ))); 0313 0314 auto anim = parse_animated(element); 0315 for ( const auto& kf : anim.joined({"x1", "y1"}) ) 0316 gradient->start_point.set_keyframe(kf.time, {kf.values[0].vector()[0], kf.values[1].vector()[0]})->set_transition(kf.transition); 0317 for ( const auto& kf : anim.joined({"x2", "y2"}) ) 0318 gradient->end_point.set_keyframe(kf.time, {kf.values[0].vector()[0], kf.values[1].vector()[0]})->set_transition(kf.transition); 0319 } 0320 else if ( element.tagName() == "radialGradient" ) 0321 { 0322 if ( !element.hasAttribute("cx") || !element.hasAttribute("cy") || !element.hasAttribute("r") ) 0323 return; 0324 0325 gradient->type.set(model::Gradient::Radial); 0326 0327 QPointF c = QPointF( 0328 len_attr(element, "cx"), 0329 len_attr(element, "cy") 0330 ); 0331 gradient->start_point.set(gradient_transform.map(c)); 0332 0333 if ( element.hasAttribute("fx") ) 0334 gradient->highlight.set(gradient_transform.map(QPointF( 0335 len_attr(element, "fx"), 0336 len_attr(element, "fy") 0337 ))); 0338 else 0339 gradient->highlight.set(gradient_transform.map(c)); 0340 0341 gradient->end_point.set(gradient_transform.map(QPointF( 0342 c.x() + len_attr(element, "r"), c.y() 0343 ))); 0344 0345 0346 auto anim = parse_animated(element); 0347 for ( const auto& kf : anim.joined({"cx", "cy"}) ) 0348 gradient->start_point.set_keyframe(kf.time, 0349 gradient_transform.map(QPointF{kf.values[0].vector()[0], kf.values[1].vector()[0]}) 0350 )->set_transition(kf.transition); 0351 0352 for ( const auto& kf : anim.joined({"fx", "fy"}) ) 0353 gradient->highlight.set_keyframe(kf.time, 0354 gradient_transform.map(QPointF{kf.values[0].vector()[0], kf.values[1].vector()[0]}) 0355 )->set_transition(kf.transition); 0356 0357 for ( const auto& kf : anim.joined({"cx", "cy", "r"}) ) 0358 gradient->end_point.set_keyframe(kf.time, 0359 gradient_transform.map(QPointF{kf.values[0].vector()[0] + kf.values[2].vector()[0], kf.values[1].vector()[0]}) 0360 )->set_transition(kf.transition); 0361 0362 } 0363 else 0364 { 0365 return; 0366 } 0367 0368 gradient->name.set(id); 0369 gradient->colors.set(colors); 0370 brush_styles["#"+id] = gradient.get(); 0371 document->assets()->gradients->values.insert(std::move(gradient)); 0372 } 0373 0374 Style parse_style(const QDomElement& element, const Style& parent_style) 0375 { 0376 Style style = parent_style; 0377 0378 auto class_names_list = element.attribute("class").split(" ", 0379 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) 0380 Qt::SkipEmptyParts 0381 #else 0382 QString::SkipEmptyParts 0383 #endif 0384 ); 0385 std::unordered_set<QString> class_names(class_names_list.begin(), class_names_list.end()); 0386 for ( const auto& rule : css_blocks ) 0387 { 0388 if ( rule.selector.match(element, class_names) ) 0389 rule.merge_into(style); 0390 } 0391 0392 if ( element.hasAttribute("style") ) 0393 { 0394 for ( const auto& item : element.attribute("style").split(';') ) 0395 { 0396 auto split = ::utils::split_ref(item, ':'); 0397 if ( split.size() == 2 ) 0398 { 0399 QString name = split[0].trimmed().toString(); 0400 if ( !name.isEmpty() && css_atrrs.count(name) ) 0401 style[name] = split[1].trimmed().toString(); 0402 } 0403 } 0404 } 0405 0406 for ( const auto& domnode : ItemCountRange(element.attributes()) ) 0407 { 0408 auto attr = domnode.toAttr(); 0409 if ( css_atrrs.count(attr.name()) ) 0410 style[attr.name()] = attr.value(); 0411 } 0412 0413 for ( auto it = style.map.begin(); it != style.map.end(); ) 0414 { 0415 if ( it->second == "inherit" ) 0416 { 0417 QString parent = parent_style.get(it->first, ""); 0418 if ( parent.isEmpty() || parent == "inherit" ) 0419 { 0420 it = style.map.erase(it); 0421 continue; 0422 } 0423 it->second = parent; 0424 } 0425 0426 ++it; 0427 } 0428 0429 if ( !style.contains("fill") ) 0430 style.set("fill", parent_style.get("fill")); 0431 0432 style.color = parse_color(style.get("color", ""), parent_style.color); 0433 return style; 0434 } 0435 0436 bool handle_mask(const ParseFuncArgs& args) 0437 { 0438 QString mask_ref; 0439 if ( args.element.hasAttribute("clip-path") ) 0440 mask_ref = args.element.attribute("clip-path"); 0441 else if ( args.element.hasAttribute("mask") ) 0442 mask_ref = args.element.attribute("mask"); 0443 0444 if ( mask_ref.isEmpty() ) 0445 return false; 0446 0447 auto match = url_re.match(mask_ref); 0448 if ( !match.hasMatch() ) 0449 return false; 0450 0451 QString id = match.captured(1).mid(1); 0452 QDomElement mask_element = element_by_id(id); 0453 if ( mask_element.isNull() ) 0454 return false; 0455 0456 0457 Style style = parse_style(args.element, args.parent_style); 0458 auto layer = add_layer(args.shape_parent); 0459 apply_common_style(layer, args.element, style); 0460 set_name(layer, args.element); 0461 layer->mask->mask.set(model::MaskSettings::Alpha); 0462 0463 QDomElement element = args.element; 0464 0465 QDomElement trans_copy = dom.createElement("g"); 0466 trans_copy.setAttribute("style", element.attribute("style")); 0467 element.removeAttribute("style"); 0468 trans_copy.setAttribute("transform", element.attribute("transform")); 0469 element.removeAttribute("transform"); 0470 0471 for ( const auto& attr : detail::css_atrrs ) 0472 element.removeAttribute(attr); 0473 0474 Style mask_style; 0475 mask_style["stroke"] = "none"; 0476 parse_g_to_layer({ 0477 mask_element, 0478 &layer->shapes, 0479 mask_style, 0480 false 0481 }); 0482 0483 parse_shape_impl({ 0484 element, 0485 &layer->shapes, 0486 style, 0487 false 0488 }); 0489 0490 parse_transform(trans_copy, layer, layer->transform.get()); 0491 0492 return true; 0493 } 0494 0495 void parse_shape_impl(const ParseFuncArgs& args) 0496 { 0497 auto it = shape_parsers.find(args.element.tagName()); 0498 if ( it != shape_parsers.end() ) 0499 { 0500 mark_progress(); 0501 (this->*it->second)(args); 0502 } 0503 } 0504 0505 void parse_transform( 0506 const QDomElement& element, 0507 model::Group* node, 0508 model::Transform* transform 0509 ) 0510 { 0511 auto bb = node->local_bounding_rect(0); 0512 bool anchor_from_inkscape = false; 0513 QPointF center = bb.center(); 0514 if ( element.hasAttributeNS(detail::xmlns.at("inkscape"), "transform-center-x") ) 0515 { 0516 anchor_from_inkscape = true; 0517 qreal ix = element.attributeNS(detail::xmlns.at("inkscape"), "transform-center-x").toDouble(); 0518 qreal iy = -element.attributeNS(detail::xmlns.at("inkscape"), "transform-center-y").toDouble(); 0519 center += QPointF(ix, iy); 0520 } 0521 0522 bool anchor_from_rotate = false; 0523 0524 if ( element.hasAttribute("transform") ) 0525 { 0526 auto trans = svg_transform( 0527 element.attribute("transform"), 0528 transform->transform_matrix(transform->time()) 0529 ); 0530 transform->set_transform_matrix(trans.transform); 0531 anchor_from_rotate = trans.anchor_set; 0532 if ( trans.anchor_set ) 0533 center = trans.anchor; 0534 0535 } 0536 0537 /// Adjust anchor point 0538 QPointF delta_pos; 0539 if ( anchor_from_rotate ) 0540 { 0541 transform->anchor_point.set(center); 0542 delta_pos = center; 0543 } 0544 else if ( anchor_from_inkscape ) 0545 { 0546 auto matrix = transform->transform_matrix(transform->time()); 0547 QPointF p1 = matrix.map(QPointF(0, 0)); 0548 transform->anchor_point.set(center); 0549 matrix = transform->transform_matrix(transform->time()); 0550 QPointF p2 = matrix.map(QPointF(0, 0)); 0551 delta_pos = p1 - p2; 0552 } 0553 transform->position.set(transform->position.get() + delta_pos); 0554 0555 auto anim = animate_parser.parse_animated_transform(element); 0556 0557 if ( !anim.apply_motion(transform->position, delta_pos, &node->auto_orient) ) 0558 { 0559 for ( const auto& kf : anim.single("translate") ) 0560 transform->position.set_keyframe(kf.time, QPointF{kf.values.vector()[0], kf.values.vector()[1]} + delta_pos)->set_transition(kf.transition); 0561 } 0562 0563 for ( const auto& kf : anim.single("scale") ) 0564 transform->scale.set_keyframe(kf.time, QVector2D(kf.values.vector()[0], kf.values.vector()[1]))->set_transition(kf.transition); 0565 0566 for ( const auto& kf : anim.single("rotate") ) 0567 { 0568 transform->rotation.set_keyframe(kf.time, kf.values.vector()[0])->set_transition(kf.transition); 0569 if ( kf.values.vector().size() == 3 ) 0570 { 0571 QPointF p = {kf.values.vector()[1], kf.values.vector()[2]}; 0572 transform->anchor_point.set_keyframe(kf.time, p)->set_transition(kf.transition); 0573 transform->position.set_keyframe(kf.time, p)->set_transition(kf.transition); 0574 } 0575 } 0576 } 0577 0578 struct ParsedTransformInfo 0579 { 0580 QTransform transform; 0581 QPointF anchor = {}; 0582 bool anchor_set = false; 0583 }; 0584 0585 ParsedTransformInfo svg_transform(const QString& attr, const QTransform& trans) 0586 { 0587 ParsedTransformInfo info{trans}; 0588 for ( const QRegularExpressionMatch& match : utils::regexp::find_all(transform_re, attr) ) 0589 { 0590 auto args = double_args(match.captured(2)); 0591 if ( args.empty() ) 0592 { 0593 warning("Missing transformation parameters"); 0594 continue; 0595 } 0596 0597 QString name = match.captured(1); 0598 0599 if ( name == "translate" ) 0600 { 0601 info.transform.translate(args[0], args.size() > 1 ? args[1] : 0); 0602 } 0603 else if ( name == "scale" ) 0604 { 0605 info.transform.scale(args[0], (args.size() > 1 ? args[1] : args[0])); 0606 } 0607 else if ( name == "rotate" ) 0608 { 0609 qreal ang = args[0]; 0610 if ( args.size() > 2 ) 0611 { 0612 qreal x = args[1]; 0613 qreal y = args[2]; 0614 info.anchor = {x, y}; 0615 info.anchor_set = true; 0616 // info.transform.translate(-x, -y); 0617 info.transform.rotate(ang); 0618 // info.transform.translate(x, y); 0619 } 0620 else 0621 { 0622 info.transform.rotate(ang); 0623 } 0624 } 0625 else if ( name == "skewX" ) 0626 { 0627 info.transform *= QTransform( 0628 1, 0, 0, 0629 qTan(args[0]), 1, 0, 0630 0, 0, 1 0631 ); 0632 } 0633 else if ( name == "skewY" ) 0634 { 0635 info.transform *= QTransform( 0636 1, qTan(args[0]), 0, 0637 0, 1, 0, 0638 0, 0, 1 0639 ); 0640 } 0641 else if ( name == "matrix" ) 0642 { 0643 if ( args.size() == 6 ) 0644 { 0645 info.transform *= QTransform( 0646 args[0], args[1], 0, 0647 args[2], args[3], 0, 0648 args[4], args[5], 1 0649 ); 0650 } 0651 else 0652 { 0653 warning("Wrong translation matrix"); 0654 } 0655 } 0656 else 0657 { 0658 warning(QString("Unknown transformation %1").arg(name)); 0659 } 0660 0661 } 0662 return info; 0663 } 0664 0665 void add_shapes(const ParseFuncArgs& args, ShapeCollection&& shapes) 0666 { 0667 Style style = parse_style(args.element, args.parent_style); 0668 auto group = std::make_unique<model::Group>(document); 0669 apply_common_style(group.get(), args.element, style); 0670 set_name(group.get(), args.element); 0671 0672 add_style_shapes(args, &group->shapes, style); 0673 0674 for ( auto& shape : shapes ) 0675 group->shapes.insert(std::move(shape)); 0676 0677 // parse_transform at the end so the bounding box isn't empty 0678 parse_transform(args.element, group.get(), group->transform.get()); 0679 args.shape_parent->insert(std::move(group)); 0680 } 0681 0682 void apply_common_style(model::VisualNode* node, const QDomElement& element, const Style& style) 0683 { 0684 if ( style.get("display") == "none" || style.get("visibility") == "hidden" ) 0685 node->visible.set(false); 0686 node->locked.set(attr(element, "sodipodi", "insensitive") == "true"); 0687 node->set("opacity", percent_1(style.get("opacity", "1"))); 0688 node->get("transform").value<model::Transform*>(); 0689 } 0690 0691 void set_name(model::DocumentNode* node, const QDomElement& element) 0692 { 0693 QString name = attr(element, "inkscape", "label"); 0694 if ( name.isEmpty() ) 0695 { 0696 name = attr(element, "android", "name"); 0697 if ( name.isEmpty() ) 0698 name = element.attribute("id"); 0699 } 0700 node->name.set(name); 0701 } 0702 0703 void add_style_shapes(const ParseFuncArgs& args, model::ShapeListProperty* shapes, const Style& style) 0704 { 0705 QString paint_order = style.get("paint-order", "normal"); 0706 if ( paint_order == "normal" ) 0707 paint_order = "fill stroke"; 0708 0709 for ( const auto& sr : paint_order.split(' ', 0710 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) 0711 Qt::SkipEmptyParts 0712 #else 0713 QString::SkipEmptyParts 0714 #endif 0715 ) ) 0716 { 0717 if ( sr == "fill" ) 0718 add_fill(args, shapes, style); 0719 else if ( sr == "stroke" ) 0720 add_stroke(args, shapes, style); 0721 } 0722 } 0723 0724 void display_to_opacity(model::VisualNode* node, 0725 const detail::AnimateParser::AnimatedProperties& anim, 0726 model::AnimatedProperty<float>& opacity, 0727 Style* style) 0728 { 0729 if ( !anim.has("display") ) 0730 return; 0731 0732 if ( opacity.keyframe_count() > 2 ) 0733 { 0734 warning("Either animate `opacity` or `display`, not both"); 0735 return; 0736 } 0737 0738 if ( style ) 0739 style->map.erase("display"); 0740 0741 model::KeyframeTransition hold; 0742 hold.set_hold(true); 0743 0744 for ( const auto& kf : anim.single("display") ) 0745 { 0746 opacity.set_keyframe(kf.time, kf.values.string() == "none" ? 0 : 1)->set_transition(hold); 0747 } 0748 0749 node->visible.set(true); 0750 } 0751 0752 void add_stroke(const ParseFuncArgs& args, model::ShapeListProperty* shapes, const Style& style) 0753 { 0754 QString stroke_color = style.get("stroke", "transparent"); 0755 if ( stroke_color == "none" ) 0756 return; 0757 0758 auto stroke = std::make_unique<model::Stroke>(document); 0759 set_styler_style(stroke.get(), stroke_color, style.color); 0760 0761 stroke->opacity.set(percent_1(style.get("stroke-opacity", "1"))); 0762 stroke->width.set(parse_unit(style.get("stroke-width", "1"))); 0763 0764 stroke->cap.set(line_cap(style.get("stroke-linecap", "butt"))); 0765 stroke->join.set(line_join(style.get("stroke-linejoin", "miter"))); 0766 stroke->miter_limit.set(parse_unit(style.get("stroke-miterlimit", "4"))); 0767 0768 auto anim = parse_animated(args.element); 0769 for ( const auto& kf : anim.single("stroke") ) 0770 stroke->color.set_keyframe(kf.time, kf.values.color())->set_transition(kf.transition); 0771 0772 for ( const auto& kf : anim.single("stroke-opacity") ) 0773 stroke->opacity.set_keyframe(kf.time, kf.values.vector()[0])->set_transition(kf.transition); 0774 0775 for ( const auto& kf : anim.single("stroke-width") ) 0776 stroke->width.set_keyframe(kf.time, kf.values.vector()[0])->set_transition(kf.transition); 0777 0778 display_to_opacity(stroke.get(), anim, stroke->opacity, nullptr); 0779 0780 shapes->insert(std::move(stroke)); 0781 } 0782 0783 void set_styler_style(model::Styler* styler, const QString& color_str, const QColor& current_color) 0784 { 0785 if ( !color_str.startsWith("url") ) 0786 { 0787 styler->color.set(parse_color(color_str, current_color)); 0788 return; 0789 } 0790 0791 auto match = url_re.match(color_str); 0792 if ( match.hasMatch() ) 0793 { 0794 QString id = match.captured(1); 0795 auto it = brush_styles.find(id); 0796 if ( it != brush_styles.end() ) 0797 { 0798 styler->use.set(it->second); 0799 return; 0800 } 0801 } 0802 0803 styler->color.set(current_color); 0804 } 0805 0806 void add_fill(const ParseFuncArgs& args, model::ShapeListProperty* shapes, const Style& style) 0807 { 0808 QString fill_color = style.get("fill", ""); 0809 0810 auto fill = std::make_unique<model::Fill>(document); 0811 set_styler_style(fill.get(), fill_color, style.color); 0812 fill->opacity.set(percent_1(style.get("fill-opacity", "1"))); 0813 0814 if ( style.get("fill-rule", "") == "evenodd" ) 0815 fill->fill_rule.set(model::Fill::EvenOdd); 0816 0817 auto anim = parse_animated(args.element); 0818 for ( const auto& kf : anim.single("fill") ) 0819 fill->color.set_keyframe(kf.time, kf.values.color())->set_transition(kf.transition); 0820 0821 for ( const auto& kf : anim.single("fill-opacity") ) 0822 fill->opacity.set_keyframe(kf.time, kf.values.vector()[0])->set_transition(kf.transition); 0823 0824 if ( fill_color == "none" ) 0825 fill->visible.set(false); 0826 0827 display_to_opacity(fill.get(), anim, fill->opacity, nullptr); 0828 0829 shapes->insert(std::move(fill)); 0830 } 0831 0832 QColor parse_color(const QString& color_str, const QColor& current_color) 0833 { 0834 if ( color_str.isEmpty() || color_str == "currentColor" ) 0835 return current_color; 0836 0837 return glaxnimate::io::svg::parse_color(color_str); 0838 } 0839 0840 void parseshape_rect(const ParseFuncArgs& args) 0841 { 0842 ShapeCollection shapes; 0843 auto rect = push<model::Rect>(shapes); 0844 qreal w = len_attr(args.element, "width", 0); 0845 qreal h = len_attr(args.element, "height", 0); 0846 rect->position.set(QPointF( 0847 len_attr(args.element, "x", 0) + w / 2, 0848 len_attr(args.element, "y", 0) + h / 2 0849 )); 0850 rect->size.set(QSizeF(w, h)); 0851 qreal rx = len_attr(args.element, "rx", 0); 0852 qreal ry = len_attr(args.element, "ry", 0); 0853 rect->rounded.set(qMax(rx, ry)); 0854 0855 0856 auto anim = parse_animated(args.element); 0857 0858 /// \todo handle offset 0859 anim.apply_motion(rect->position); 0860 0861 for ( const auto& kf : anim.joined({"x", "y", "width", "height"}) ) 0862 rect->position.set_keyframe(kf.time, { 0863 kf.values[0].vector()[0] + kf.values[2].vector()[0] / 2, 0864 kf.values[1].vector()[0] + kf.values[3].vector()[0] / 2 0865 })->set_transition(kf.transition); 0866 0867 for ( const auto& kf : anim.joined({"width", "height"}) ) 0868 rect->size.set_keyframe(kf.time, {kf.values[0].vector()[0], kf.values[1].vector()[0]})->set_transition(kf.transition); 0869 0870 for ( const auto& kf : anim.joined({"rx", "ry"}) ) 0871 rect->rounded.set_keyframe(kf.time, qMax(kf.values[0].vector()[0], kf.values[1].vector()[0]))->set_transition(kf.transition); 0872 0873 add_shapes(args, std::move(shapes)); 0874 } 0875 0876 void parseshape_ellipse(const ParseFuncArgs& args) 0877 { 0878 ShapeCollection shapes; 0879 auto ellipse = push<model::Ellipse>(shapes); 0880 ellipse->position.set(QPointF( 0881 len_attr(args.element, "cx", 0), 0882 len_attr(args.element, "cy", 0) 0883 )); 0884 qreal rx = len_attr(args.element, "rx", 0); 0885 qreal ry = len_attr(args.element, "ry", 0); 0886 ellipse->size.set(QSizeF(rx * 2, ry * 2)); 0887 0888 auto anim = parse_animated(args.element); 0889 anim.apply_motion(ellipse->position); 0890 for ( const auto& kf : anim.joined({"cx", "cy"}) ) 0891 ellipse->position.set_keyframe(kf.time, {kf.values[0].vector()[0], kf.values[1].vector()[0]})->set_transition(kf.transition); 0892 for ( const auto& kf : anim.joined({"rx", "ry"}) ) 0893 ellipse->size.set_keyframe(kf.time, {kf.values[0].vector()[0]*2, kf.values[1].vector()[0]*2})->set_transition(kf.transition); 0894 0895 add_shapes(args, std::move(shapes)); 0896 } 0897 0898 void parseshape_circle(const ParseFuncArgs& args) 0899 { 0900 ShapeCollection shapes; 0901 auto ellipse = push<model::Ellipse>(shapes); 0902 ellipse->position.set(QPointF( 0903 len_attr(args.element, "cx", 0), 0904 len_attr(args.element, "cy", 0) 0905 )); 0906 qreal d = len_attr(args.element, "r", 0) * 2; 0907 ellipse->size.set(QSizeF(d, d)); 0908 0909 auto anim = parse_animated(args.element); 0910 anim.apply_motion(ellipse->position); 0911 for ( const auto& kf : anim.joined({"cx", "cy"}) ) 0912 ellipse->position.set_keyframe(kf.time, {kf.values[0].vector()[0], kf.values[1].vector()[0]})->set_transition(kf.transition); 0913 for ( const auto& kf : anim.single({"r"}) ) 0914 ellipse->size.set_keyframe(kf.time, {kf.values.vector()[0]*2, kf.values.vector()[0]*2})->set_transition(kf.transition); 0915 0916 add_shapes(args, std::move(shapes)); 0917 } 0918 0919 void parseshape_g(const ParseFuncArgs& args) 0920 { 0921 switch ( group_mode ) 0922 { 0923 case Groups: 0924 parse_g_to_shape(args); 0925 break; 0926 case Layers: 0927 parse_g_to_layer(args); 0928 break; 0929 case Inkscape: 0930 if ( args.in_group ) 0931 parse_g_to_shape(args); 0932 else if ( attr(args.element, "inkscape", "groupmode") == "layer" ) 0933 parse_g_to_layer(args); 0934 else 0935 parse_g_to_shape(args); 0936 break; 0937 } 0938 } 0939 0940 void parse_g_to_layer(const ParseFuncArgs& args) 0941 { 0942 Style style = parse_style(args.element, args.parent_style); 0943 auto layer = add_layer(args.shape_parent); 0944 parse_g_common( 0945 {args.element, &layer->shapes, style, false}, 0946 layer, 0947 layer->transform.get(), 0948 style 0949 ); 0950 } 0951 0952 void parse_g_to_shape(const ParseFuncArgs& args) 0953 { 0954 Style style = parse_style(args.element, args.parent_style); 0955 auto ugroup = std::make_unique<model::Group>(document); 0956 auto group = ugroup.get(); 0957 args.shape_parent->insert(std::move(ugroup)); 0958 parse_g_common( 0959 {args.element, &group->shapes, style, true}, 0960 group, 0961 group->transform.get(), 0962 style 0963 ); 0964 } 0965 0966 void parse_g_common( 0967 const ParseFuncArgs& args, 0968 model::Group* g_node, 0969 model::Transform* transform, 0970 Style& style 0971 ) 0972 { 0973 apply_common_style(g_node, args.element, args.parent_style); 0974 0975 auto anim = parse_animated(args.element); 0976 0977 for ( const auto& kf : anim.single("opacity") ) 0978 g_node->opacity.set_keyframe(kf.time, kf.values.vector()[0])->set_transition(kf.transition); 0979 0980 display_to_opacity(g_node, anim, g_node->opacity, &style); 0981 0982 set_name(g_node, args.element); 0983 // Avoid doubling opacity values 0984 style.map.erase("opacity"); 0985 parse_children(args); 0986 parse_transform(args.element, g_node, transform); 0987 } 0988 0989 std::vector<model::Path*> parse_bezier_impl(const ParseFuncArgs& args, const math::bezier::MultiBezier& bez) 0990 { 0991 if ( bez.beziers().empty() ) 0992 return {}; 0993 0994 ShapeCollection shapes; 0995 std::vector<model::Path*> paths; 0996 for ( const auto& bezier : bez.beziers() ) 0997 { 0998 model::Path* shape = push<model::Path>(shapes); 0999 paths.push_back(shape); 1000 shape->shape.set(bezier); 1001 shape->closed.set(bezier.closed()); 1002 } 1003 add_shapes(args, std::move(shapes)); 1004 return paths; 1005 } 1006 1007 1008 model::Path* parse_bezier_impl_single(const ParseFuncArgs& args, const math::bezier::Bezier& bez) 1009 { 1010 ShapeCollection shapes; 1011 auto path = push<model::Path>(shapes); 1012 path->shape.set(bez); 1013 add_shapes(args, {std::move(shapes)}); 1014 return path; 1015 } 1016 1017 detail::AnimateParser::AnimatedProperties parse_animated(const QDomElement& element) 1018 { 1019 return animate_parser.parse_animated_properties(element); 1020 } 1021 1022 void parseshape_line(const ParseFuncArgs& args) 1023 { 1024 math::bezier::Bezier bez; 1025 bez.add_point(QPointF( 1026 len_attr(args.element, "x1", 0), 1027 len_attr(args.element, "y1", 0) 1028 )); 1029 bez.line_to(QPointF( 1030 len_attr(args.element, "x2", 0), 1031 len_attr(args.element, "y2", 0) 1032 )); 1033 auto path = parse_bezier_impl_single(args, bez); 1034 for ( const auto& kf : parse_animated(args.element).joined({"x1", "y1", "x2", "y2"}) ) 1035 { 1036 math::bezier::Bezier bez; 1037 bez.add_point({kf.values[0].vector()[0], kf.values[1].vector()[0]}); 1038 bez.add_point({kf.values[2].vector()[0], kf.values[3].vector()[0]}); 1039 path->shape.set_keyframe(kf.time, bez)->set_transition(kf.transition); 1040 } 1041 } 1042 1043 math::bezier::Bezier build_poly(const std::vector<qreal>& coords, bool close) 1044 { 1045 math::bezier::Bezier bez; 1046 1047 if ( coords.size() < 4 ) 1048 { 1049 if ( !coords.empty() ) 1050 warning("Not enough `points` for `polygon` / `polyline`"); 1051 return bez; 1052 } 1053 1054 bez.add_point(QPointF(coords[0], coords[1])); 1055 1056 for ( int i = 2; i < int(coords.size()); i+= 2 ) 1057 bez.line_to(QPointF(coords[i], coords[i+1])); 1058 1059 if ( close ) 1060 bez.close(); 1061 1062 return bez; 1063 } 1064 1065 void handle_poly(const ParseFuncArgs& args, bool close) 1066 { 1067 auto path = parse_bezier_impl_single(args, build_poly(double_args(args.element.attribute("points", "")), close)); 1068 if ( !path ) 1069 return; 1070 1071 for ( const auto& kf : parse_animated(args.element).single("points") ) 1072 path->shape.set_keyframe(kf.time, build_poly(kf.values.vector(), close))->set_transition(kf.transition); 1073 1074 } 1075 1076 void parseshape_polyline(const ParseFuncArgs& args) 1077 { 1078 handle_poly(args, false); 1079 } 1080 1081 void parseshape_polygon(const ParseFuncArgs& args) 1082 { 1083 handle_poly(args, true); 1084 } 1085 1086 void parseshape_path(const ParseFuncArgs& args) 1087 { 1088 if ( parse_star(args) ) 1089 return; 1090 QString d = args.element.attribute("d"); 1091 math::bezier::MultiBezier bez = PathDParser(d).parse(); 1092 /// \todo sodipodi:nodetypes 1093 auto paths = parse_bezier_impl(args, bez); 1094 1095 path_animation(paths, parse_animated(args.element), "d" ); 1096 } 1097 1098 bool parse_star(const ParseFuncArgs& args) 1099 { 1100 if ( attr(args.element, "sodipodi", "type") != "star" ) 1101 return false; 1102 1103 qreal randomized = attr(args.element, "inkscape", "randomized", "0").toDouble(); 1104 if ( !qFuzzyCompare(randomized, 0.0) ) 1105 return false; 1106 1107 qreal rounded = attr(args.element, "inkscape", "rounded", "0").toDouble(); 1108 if ( !qFuzzyCompare(rounded, 0.0) ) 1109 return false; 1110 1111 1112 ShapeCollection shapes; 1113 auto shape = push<model::PolyStar>(shapes); 1114 shape->points.set( 1115 attr(args.element, "sodipodi", "sides").toInt() 1116 ); 1117 auto flat = attr(args.element, "inkscape", "flatsided"); 1118 shape->type.set( 1119 flat == "true" ? 1120 model::PolyStar::Polygon : 1121 model::PolyStar::Star 1122 ); 1123 shape->position.set(QPointF( 1124 attr(args.element, "sodipodi", "cx").toDouble(), 1125 attr(args.element, "sodipodi", "cy").toDouble() 1126 )); 1127 shape->outer_radius.set(attr(args.element, "sodipodi", "r1").toDouble()); 1128 shape->inner_radius.set(attr(args.element, "sodipodi", "r2").toDouble()); 1129 shape->angle.set( 1130 math::rad2deg(attr(args.element, "sodipodi", "arg1").toDouble()) 1131 +90 1132 ); 1133 1134 add_shapes(args, std::move(shapes)); 1135 return true; 1136 } 1137 1138 void parseshape_use(const ParseFuncArgs& args) 1139 { 1140 QString id = attr(args.element, "xlink", "href"); 1141 if ( !id.startsWith('#') ) 1142 return; 1143 id.remove(0, 1); 1144 QDomElement element = element_by_id(id); 1145 if ( element.isNull() ) 1146 return; 1147 1148 Style style = parse_style(args.element, args.parent_style); 1149 auto group = std::make_unique<model::Group>(document); 1150 apply_common_style(group.get(), args.element, style); 1151 set_name(group.get(), args.element); 1152 1153 parse_shape({element, &group->shapes, style, true}); 1154 1155 group->transform.get()->position.set( 1156 QPointF(len_attr(args.element, "x", 0), len_attr(args.element, "y", 0)) 1157 ); 1158 parse_transform(args.element, group.get(), group->transform.get()); 1159 args.shape_parent->insert(std::move(group)); 1160 } 1161 1162 QString find_asset_file(const QString& path) 1163 { 1164 QFileInfo finfo(path); 1165 if ( finfo.exists() ) 1166 return path; 1167 else if ( default_asset_path.exists(path) ) 1168 return default_asset_path.filePath(path); 1169 else if ( default_asset_path.exists(finfo.fileName()) ) 1170 return default_asset_path.filePath(finfo.fileName()); 1171 1172 return {}; 1173 } 1174 1175 bool open_asset_file(model::Bitmap* image, const QString& path) 1176 { 1177 if ( path.isEmpty() ) 1178 return false; 1179 1180 auto file = find_asset_file(path); 1181 if ( file.isEmpty() ) 1182 return false; 1183 1184 return image->from_file(file); 1185 } 1186 1187 void parseshape_image(const ParseFuncArgs& args) 1188 { 1189 auto bitmap = std::make_unique<model::Bitmap>(document); 1190 1191 bool open = false; 1192 QString href = attr(args.element, "xlink", "href"); 1193 QUrl url = QUrl(href); 1194 1195 if ( url.isRelative() ) 1196 open = open_asset_file(bitmap.get(), href); 1197 if ( !open ) 1198 { 1199 if ( url.isLocalFile() ) 1200 open = open_asset_file(bitmap.get(), url.toLocalFile()); 1201 else 1202 open = bitmap->from_url(url); 1203 } 1204 1205 if ( !open ) 1206 { 1207 QString path = attr(args.element, "sodipodi", "absref"); 1208 open = open_asset_file(bitmap.get(), path); 1209 } 1210 if ( !open ) 1211 warning(QString("Could not load image %1").arg(href)); 1212 1213 auto image = std::make_unique<model::Image>(document); 1214 image->image.set(document->assets()->images->values.insert(std::move(bitmap))); 1215 1216 QTransform trans; 1217 if ( args.element.hasAttribute("transform") ) 1218 trans = svg_transform(args.element.attribute("transform"), trans).transform; 1219 trans.translate( 1220 len_attr(args.element, "x", 0), 1221 len_attr(args.element, "y", 0) 1222 ); 1223 image->transform->set_transform_matrix(trans); 1224 1225 args.shape_parent->insert(std::move(image)); 1226 } 1227 1228 struct TextStyle 1229 { 1230 QString family = "sans-serif"; 1231 int weight = QFont::Normal; 1232 QFont::Style style = QFont::StyleNormal; 1233 qreal line_spacing = 0; 1234 qreal size = 64; 1235 bool keep_space = false; 1236 QPointF pos; 1237 }; 1238 1239 TextStyle parse_text_style(const ParseFuncArgs& args, const TextStyle& parent) 1240 { 1241 TextStyle out = parent; 1242 1243 Style style = parse_style(args.element, args.parent_style); 1244 1245 if ( style.contains("font-family") ) 1246 out.family = style["font-family"]; 1247 1248 if ( style.contains("font-style") ) 1249 { 1250 QString slant = style["font-style"]; 1251 if ( slant == "normal" ) out.style = QFont::StyleNormal; 1252 else if ( slant == "italic" ) out.style = QFont::StyleItalic; 1253 else if ( slant == "oblique" ) out.style = QFont::StyleOblique; 1254 } 1255 1256 if ( style.contains("font-size") ) 1257 { 1258 QString size = style["font-size"]; 1259 static const std::map<QString, int> size_names = { 1260 {{"xx-small"}, {8}}, 1261 {{"x-small"}, {16}}, 1262 {{"small"}, {32}}, 1263 {{"medium"}, {64}}, 1264 {{"large"}, {128}}, 1265 {{"x-large"}, {256}}, 1266 {{"xx-large"}, {512}}, 1267 }; 1268 if ( size == "smaller" ) 1269 out.size /= 2; 1270 else if ( size == "larger" ) 1271 out.size *= 2; 1272 else if ( size_names.count(size) ) 1273 out.size = size_names.at(size); 1274 else 1275 out.size = parse_unit(size); 1276 } 1277 1278 if ( style.contains("font-weight") ) 1279 { 1280 QString weight = style["font-weight"]; 1281 if ( weight == "bold" ) 1282 out.weight = 700; 1283 else if ( weight == "normal" ) 1284 out.weight = 400; 1285 else if ( weight == "bolder" ) 1286 out.weight = qMin(1000, out.weight + 100); 1287 else if ( weight == "lighter") 1288 out.weight = qMax(1, out.weight - 100); 1289 else 1290 out.weight = weight.toInt(); 1291 } 1292 1293 if ( style.contains("line-height") ) 1294 out.line_spacing = parse_unit(style["line-height"]); 1295 1296 1297 if ( args.element.hasAttribute("xml:space") ) 1298 out.keep_space = args.element.attribute("xml:space") == "preserve"; 1299 1300 if ( args.element.hasAttribute("x") ) 1301 out.pos.setX(len_attr(args.element, "x", 0)); 1302 if ( args.element.hasAttribute("y") ) 1303 out.pos.setY(len_attr(args.element, "y", 0)); 1304 1305 return out; 1306 } 1307 1308 QString trim_text(const QString& text) 1309 { 1310 QString trimmed = text.simplified(); 1311 if ( !text.isEmpty() && text.back().isSpace() ) 1312 trimmed += ' '; 1313 return trimmed; 1314 } 1315 1316 void apply_text_style(model::Font* font, const TextStyle& style) 1317 { 1318 font->family.set(style.family); 1319 font->size.set(unit_convert(style.size, "px", "pt")); 1320 QFont qfont; 1321 qfont.setFamily(style.family); 1322 qfont.setWeight(QFont::Weight(WeightConverter::convert(style.weight, WeightConverter::css, WeightConverter::qt))); 1323 qfont.setStyle(style.style); 1324 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 1325 QString style_string = QFontDatabase::styleString(qfont); 1326 #else 1327 QFontDatabase db; 1328 QString style_string = db.styleString(qfont); 1329 #endif 1330 font->style.set(style_string); 1331 } 1332 1333 QPointF parse_text_element(const ParseFuncArgs& args, const TextStyle& parent_style) 1334 { 1335 TextStyle style = parse_text_style(args, parent_style); 1336 Style css_style = parse_style(args.element, args.parent_style); 1337 1338 auto anim = parse_animated(args.element); 1339 1340 model::TextShape* last = nullptr; 1341 1342 QPointF offset; 1343 QPointF pos = style.pos; 1344 QString text; 1345 for ( const auto & child : ItemCountRange(args.element.childNodes()) ) 1346 { 1347 ParseFuncArgs child_args = {child.toElement(), args.shape_parent, css_style, args.in_group}; 1348 if ( child.isElement() ) 1349 { 1350 last = nullptr; 1351 style.pos = pos + offset; 1352 offset = parse_text_element(child_args, style); 1353 } 1354 else if ( child.isText() || child.isCDATASection() ) 1355 { 1356 text += child.toCharacterData().data(); 1357 1358 if ( !last ) 1359 { 1360 ShapeCollection shapes; 1361 last = push<model::TextShape>(shapes); 1362 1363 last->position.set(pos + offset); 1364 apply_text_style(last->font.get(), style); 1365 1366 for ( const auto& kf : anim.joined({"x", "y"}) ) 1367 { 1368 last->position.set_keyframe( 1369 kf.time, 1370 offset + QPointF(kf.values[0].vector()[0], kf.values[1].vector()[0]) 1371 )->set_transition(kf.transition); 1372 } 1373 1374 add_shapes(child_args, std::move(shapes)); 1375 } 1376 1377 last->text.set(style.keep_space ? text : trim_text(text)); 1378 1379 offset = last->offset_to_next_character(); 1380 } 1381 } 1382 1383 return offset; 1384 } 1385 1386 void parseshape_text(const ParseFuncArgs& args) 1387 { 1388 parse_text_element(args, {}); 1389 } 1390 1391 void parse_metadata() 1392 { 1393 auto meta = dom.elementsByTagNameNS(xmlns.at("cc"), "Work"); 1394 if ( meta.count() == 0 ) 1395 return; 1396 1397 auto work = query_element({"metadata", "RDF", "Work"}, dom.documentElement()); 1398 document->info().author = query({"creator", "Agent", "title"}, work); 1399 document->info().description = query({"description"}, work); 1400 for ( const auto& domnode : ItemCountRange(query_element({"subject", "Bag"}, work).childNodes()) ) 1401 { 1402 if ( domnode.isElement() ) 1403 { 1404 auto child = domnode.toElement(); 1405 if ( child.tagName() == "li" ) 1406 document->info().keywords.push_back(child.text()); 1407 1408 } 1409 } 1410 } 1411 1412 GroupMode group_mode; 1413 std::vector<CssStyleBlock> css_blocks; 1414 QDir default_asset_path; 1415 1416 static const std::map<QString, void (Private::*)(const ParseFuncArgs&)> shape_parsers; 1417 static const QRegularExpression transform_re; 1418 static const QRegularExpression url_re; 1419 }; 1420 1421 const std::map<QString, void (glaxnimate::io::svg::SvgParser::Private::*)(const glaxnimate::io::svg::SvgParser::Private::ParseFuncArgs&)> glaxnimate::io::svg::SvgParser::Private::shape_parsers = { 1422 {"g", &glaxnimate::io::svg::SvgParser::Private::parseshape_g}, 1423 {"rect", &glaxnimate::io::svg::SvgParser::Private::parseshape_rect}, 1424 {"ellipse", &glaxnimate::io::svg::SvgParser::Private::parseshape_ellipse}, 1425 {"circle", &glaxnimate::io::svg::SvgParser::Private::parseshape_circle}, 1426 {"line", &glaxnimate::io::svg::SvgParser::Private::parseshape_line}, 1427 {"polyline",&glaxnimate::io::svg::SvgParser::Private::parseshape_polyline}, 1428 {"polygon", &glaxnimate::io::svg::SvgParser::Private::parseshape_polygon}, 1429 {"path", &glaxnimate::io::svg::SvgParser::Private::parseshape_path}, 1430 {"use", &glaxnimate::io::svg::SvgParser::Private::parseshape_use}, 1431 {"image", &glaxnimate::io::svg::SvgParser::Private::parseshape_image}, 1432 {"text", &glaxnimate::io::svg::SvgParser::Private::parseshape_text}, 1433 }; 1434 const QRegularExpression glaxnimate::io::svg::detail::SvgParserPrivate::unit_re{R"(([-+]?(?:[0-9]*\.[0-9]+|[0-9]+)([eE][-+]?[0-9]+)?)([a-z]*))"}; 1435 const QRegularExpression glaxnimate::io::svg::SvgParser::Private::transform_re{R"(([a-zA-Z]+)\s*\(([^\)]*)\))"}; 1436 const QRegularExpression glaxnimate::io::svg::SvgParser::Private::url_re{R"(url\s*\(\s*(#[-a-zA-Z0-9_]+)\s*\)\s*)"}; 1437 const QRegularExpression glaxnimate::io::svg::detail::AnimateParser::separator{"\\s*,\\s*|\\s+"}; 1438 const QRegularExpression glaxnimate::io::svg::detail::AnimateParser::clock_re{R"((?:(?:(?<hours>[0-9]+):)?(?:(?<minutes>[0-9]{2}):)?(?<seconds>[0-9]+(?:\.[0-9]+)?))|(?:(?<timecount>[0-9]+(?:\.[0-9]+)?)(?<unit>h|min|s|ms)))"}; 1439 const QRegularExpression glaxnimate::io::svg::detail::AnimateParser::frame_separator_re{"\\s*;\\s*"}; 1440 1441 glaxnimate::io::svg::SvgParser::SvgParser( 1442 QIODevice* device, 1443 GroupMode group_mode, 1444 model::Document* document, 1445 const std::function<void(const QString&)>& on_warning, 1446 ImportExport* io, 1447 QSize forced_size, 1448 model::FrameTime default_time, 1449 QDir default_asset_path 1450 ) 1451 : d(std::make_unique<Private>(document, on_warning, io, forced_size, default_time, group_mode, default_asset_path)) 1452 { 1453 d->load(device); 1454 } 1455 1456 glaxnimate::io::svg::SvgParser::~SvgParser() 1457 { 1458 } 1459 1460 1461 glaxnimate::io::mime::DeserializedData glaxnimate::io::svg::SvgParser::parse_to_objects() 1462 { 1463 glaxnimate::io::mime::DeserializedData data; 1464 data.initialize_data(); 1465 d->parse(data.document.get()); 1466 return data; 1467 } 1468 1469 void glaxnimate::io::svg::SvgParser::parse_to_document() 1470 { 1471 d->parse(); 1472 } 1473 1474 static qreal hex(const QString& s, int start, int size) 1475 { 1476 return utils::mid_ref(s, start, size).toInt(nullptr, 16) / (size == 2 ? 255.0 : 15.0); 1477 } 1478 1479 QColor glaxnimate::io::svg::parse_color(const QString& string) 1480 { 1481 if ( string.isEmpty() ) 1482 return {}; 1483 1484 // #fff #112233 1485 if ( string[0] == '#' ) 1486 { 1487 if ( string.size() == 4 || string.size() == 5 ) 1488 { 1489 qreal alpha = string.size() == 4 ? 1. : hex(string, 4, 1); 1490 return QColor::fromRgbF(hex(string, 1, 1), hex(string, 2, 1), hex(string, 3, 1), alpha); 1491 } 1492 else if ( string.size() == 7 || string.size() == 9 ) 1493 { 1494 qreal alpha = string.size() == 7 ? 1. : hex(string, 7, 2); 1495 return QColor::fromRgbF(hex(string, 1, 2), hex(string, 3, 2), hex(string, 5, 2), alpha); 1496 } 1497 return QColor(); 1498 } 1499 1500 // transparent 1501 if ( string == "transparent" || string == "none" ) 1502 return QColor(0, 0, 0, 0); 1503 1504 QRegularExpressionMatch match; 1505 1506 // rgba(123, 123, 123, 0.7) 1507 static QRegularExpression rgba{R"(^rgba\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9.eE]+)\s*\)$)"}; 1508 match = rgba.match(string); 1509 if ( match.hasMatch() ) 1510 return QColor(match.captured(1).toInt(), match.captured(2).toInt(), match.captured(3).toInt(), match.captured(4).toDouble() * 255); 1511 1512 // rgb(123, 123, 123) 1513 static QRegularExpression rgb{R"(^rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)$)"}; 1514 match = rgb.match(string); 1515 if ( match.hasMatch() ) 1516 return QColor(match.captured(1).toInt(), match.captured(2).toInt(), match.captured(3).toInt()); 1517 1518 // rgba(60%, 30%, 20%, 0.7) 1519 static QRegularExpression rgba_pc{R"(^rgba\s*\(\s*([0-9.eE]+)%\s*,\s*([0-9.eE]+)%\s*,\s*([0-9.eE]+)%\s*,\s*([0-9.eE]+)\s*\)$)"}; 1520 match = rgba_pc.match(string); 1521 if ( match.hasMatch() ) 1522 return QColor::fromRgbF(match.captured(1).toDouble() / 100, match.captured(2).toDouble() / 100, match.captured(3).toDouble() / 100, match.captured(4).toDouble()); 1523 1524 // rgb(60%, 30%, 20%) 1525 static QRegularExpression rgb_pc{R"(^rgb\s*\(\s*([0-9.eE]+)%\s*,\s*([0-9.eE]+)%\s*,\s*([0-9.eE]+)%\s*\)$)"}; 1526 match = rgb_pc.match(string); 1527 if ( match.hasMatch() ) 1528 return QColor::fromRgbF(match.captured(1).toDouble() / 100, match.captured(2).toDouble() / 100, match.captured(3).toDouble() / 100); 1529 1530 // hsl(60, 30%, 20%) 1531 static QRegularExpression hsl{R"(^hsl\s*\(\s*([0-9.eE]+)\s*,\s*([0-9.eE]+)%\s*,\s*([0-9.eE]+)%\s*\)$)"}; 1532 match = rgb_pc.match(string); 1533 if ( match.hasMatch() ) 1534 return QColor::fromHslF(match.captured(1).toDouble() / 360, match.captured(2).toDouble() / 100, match.captured(3).toDouble() / 100); 1535 1536 // hsla(60, 30%, 20%, 0.7) 1537 static QRegularExpression hsla{R"(^hsla\s*\(\s*([0-9.eE]+)\s*,\s*([0-9.eE]+)%\s*,\s*([0-9.eE]+)%\s*,\s*([0-9.eE]+)\s*\)$)"}; 1538 match = rgb_pc.match(string); 1539 if ( match.hasMatch() ) 1540 return QColor::fromHslF(match.captured(1).toDouble() / 360, match.captured(2).toDouble() / 100, match.captured(3).toDouble() / 100, match.captured(4).toDouble()); 1541 1542 // red 1543 return QColor(string); 1544 }