Start implementing the SVG animation library (and tool)

This commit is contained in:
TennesseeTrash 2025-05-30 19:28:42 +02:00
parent e1af823502
commit 4e87a67296
5 changed files with 298 additions and 11 deletions

View file

@ -1,20 +1,150 @@
#include "Kanimaji/Kanimaji.hpp"
#include "SVG.hpp"
#include <ctre.hpp>
#include <pugixml.hpp>
#include <print>
#include <string_view>
namespace Kanimaji
{
namespace
{
constexpr std::string_view strokeBorderWidth = "4.5";
constexpr std::string_view strokeBorderColour = "#666";
constexpr std::string_view strokeUnfilledWidth = "3";
constexpr std::string_view strokeUnfilledColour = "#EEE";
constexpr std::string_view strokeFillingColour = "#F00";
constexpr std::string_view strokeFilledWidth = "3.1";
constexpr std::string_view strokeFilledColour = "#000";
constexpr std::string_view brushColour = "#F00";
constexpr std::string_view brushWidth = "5.5";
constexpr std::string_view brushBorderColour = "#666";
constexpr std::string_view brushBorderWidth = "7";
}
bool Animate(const std::string& source, const std::string& destination)
{
pugi::xml_document doc;
pugi::xml_parse_result res = doc.load_file(source.c_str());
if (!res) {
pugi::xml_parse_result readResult = doc.load_file(source.c_str(), pugi::parse_full);
if (!readResult) {
return false;
}
pugi::xml_node comment = doc.find_child([](pugi::xml_node& node) {
return node.type() == pugi::node_comment;
});
std::string text = comment.value();
text.append(
"\n"
"===============================================================================\n"
"\n"
"This file has been modified by the Kanimaji tool\n"
"avilable here: (TODO)\n\n"
"The Kanimaji tool is based on the original kanimaji.py\n"
"script available here: https://github.com/maurimo/kanimaji\n\n"
"Modifications (compared to the original KanjiVG file):\n"
"* The stroke numbers were removed\n"
"* Special Styles section (and accompanying <g> and <use> tags) were generated\n"
);
comment.set_value(text);
pugi::xml_node svg = doc.child("svg");
pugi::xml_attribute xmlns = svg.attribute("xmlns");
svg.append_attribute("xmlns:xlink") = "http://www.w3.org/1999/xlink";
svg.append_attribute("xlink:used") = "";
return false;
svg.remove_child(svg.find_child([](pugi::xml_node& node) {
return std::string_view(node.attribute("id").as_string()).contains("StrokeNumbers");
}));
pugi::xml_node paths = svg.find_child([](pugi::xml_node& node) {
return std::string_view(node.attribute("id").as_string()).contains("StrokePaths");
});
std::string baseId = paths.attribute("id").as_string();
baseId.erase(0, baseId.find('_') + 1);
std::println("{}", baseId);
// 1st pass for getting information
float totalLength = 50;
for (const auto& path : svg.select_nodes("//path")) {
std::println("{}", path.node().attribute("d").as_string());
std::string_view d = path.node().attribute("d").as_string();
totalLength += SVG::Path(d).Length();
}
std::string styles = "/* Styles autogenerated by Kanimaji, please do not edit manually. */";
pugi::xml_node background = svg.append_child("g");
background.append_attribute("id") = "kvg:Kanimaji_Background_" + baseId;
background.append_attribute("style") = std::format(
"fill:none;stroke:{};stroke-width:{};stroke-linecap:round;stroke-linejoin:round;",
strokeUnfilledColour, strokeUnfilledWidth
);
pugi::xml_node brushBackground = svg.append_child("g");
brushBackground.append_attribute("id") = "kvg:Kanimaji_BrushBackground_" + baseId;
brushBackground.append_attribute("style") = std::format(
"fill:none;stroke:{};stroke-width:{},stroke-linecap:round;stroke-linejoin:round;",
brushBorderColour, brushBorderWidth
);
pugi::xml_node animation = svg.append_child("g");
animation.append_attribute("id") = "kvg:Kanimaji_Animation_" + baseId;
animation.append_attribute("style") = std::format(
"fill:none;stroke:{};stroke-width:{};stroke-linecap:round;stroke-linejoin:round;",
strokeFilledColour, strokeFilledWidth
);
pugi::xml_node brush = svg.append_child("g");
brush.append_attribute("id") = "kvg:Kanimaji_Brush_" + baseId;
brush.append_attribute("style") = std::format(
"fill:none;stroke:{};stroke-width:{};stroke-linecap:round;stroke-linejoin:round;",
brushColour, brushWidth
);
// 2nd pass to prepare the CSS
for (const auto& xpath : svg.select_nodes("//path")) {
pugi::xml_node path = xpath.node();
std::string id = path.attribute("id").as_string();
std::string css = id;
css.replace(css.find(":"), 1, "\\\\3a ");
// Generate the actual styles
// - A few simplifications for now
// - Do not use the path lengths and appropriate scaled time,
// use a simplified version based solely on the stroke number.
pugi::xml_node useBackground = background.append_child("use");
useBackground.append_attribute("id") = id + "-background";
useBackground.append_attribute("xlink:href") = "#" + id;
pugi::xml_node useBrushBackground = brushBackground.append_child("use");
useBrushBackground.append_attribute("id") = id + "-brush-background";
useBrushBackground.append_attribute("xlink:href") = "#" + id;
pugi::xml_node useAnimation = animation.append_child("use");
useAnimation.append_attribute("id") = id + "-animation";
useAnimation.append_attribute("xlink:href") = "#" + id;
pugi::xml_node useBrush = brush.append_child("use");
useBrush.append_attribute("id") = id + "-brush";
useBrush.append_attribute("xlink:href") = "#" + id;
}
// This is how we add styles - prepare the whole thing in a separate buffer first.
pugi::xml_node style = svg.prepend_child("style");
style.append_attribute("id") = "kvg:Kanimaji_Style";
style.append_child(pugi::node_pcdata).set_value(styles);
bool writeResult = doc.save_file(destination.c_str());
if (!writeResult) {
return false;
}
return true;
}
}