Add better timings, and proper configuration
This commit is contained in:
parent
0b32af33b8
commit
5750bb8177
13 changed files with 385 additions and 240 deletions
|
@ -1,4 +1,3 @@
|
|||
include(${PROJECT_SOURCE_DIR}/CMake/ctre.cmake)
|
||||
include(${PROJECT_SOURCE_DIR}/CMake/pugixml.cmake)
|
||||
|
||||
add_library(Kanimaji STATIC)
|
||||
|
@ -10,7 +9,6 @@ target_compile_features(Kanimaji
|
|||
|
||||
target_link_libraries(Kanimaji
|
||||
PRIVATE
|
||||
ctre
|
||||
pugixml
|
||||
)
|
||||
|
||||
|
@ -21,7 +19,8 @@ target_include_directories(Kanimaji
|
|||
|
||||
target_sources(Kanimaji
|
||||
PRIVATE
|
||||
"Source/Colour.cpp"
|
||||
"Source/Error.cpp"
|
||||
"Source/Kanimaji.cpp"
|
||||
"Source/Settings.cpp"
|
||||
"Source/SVG.cpp"
|
||||
)
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
#ifndef KANIMAJI_CONFIG_HPP
|
||||
#define KANIMAJI_CONFIG_HPP
|
||||
|
||||
#include "Colour.hpp"
|
||||
|
||||
namespace Kanimaji
|
||||
{
|
||||
struct GroupConfig
|
||||
{
|
||||
double Width;
|
||||
Colour Colour;
|
||||
};
|
||||
|
||||
struct AnimationSettings
|
||||
{
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
#endif // KANIMAJI_CONFIG_HPP
|
|
@ -1,9 +1,32 @@
|
|||
#ifndef KANIMAJI_ERROR_HPP
|
||||
#define KANIMAJI_ERROR_HPP
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace Kanimaji
|
||||
{
|
||||
class Error : public std::runtime_error
|
||||
{
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
class FileError : public Error
|
||||
{
|
||||
using Error::Error;
|
||||
};
|
||||
|
||||
class ParseError : public Error
|
||||
{
|
||||
public:
|
||||
ParseError(std::size_t current, std::string_view message);
|
||||
|
||||
std::size_t Position() const
|
||||
{
|
||||
return mPosition;
|
||||
}
|
||||
private:
|
||||
std::size_t mPosition;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // KANIMAJI_ERROR_HPP
|
||||
|
|
|
@ -1,16 +1,20 @@
|
|||
#ifndef KANIMAJI_KANIMAJI_HPP
|
||||
#define KANIMAJI_KANIMAJI_HPP
|
||||
|
||||
#include "Settings.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
namespace Kanimaji
|
||||
{
|
||||
bool Animate(const std::string& source, const std::string& destination);
|
||||
using Path = std::filesystem::path;
|
||||
|
||||
constexpr bool Animate(const std::string& filename)
|
||||
{
|
||||
return Animate(filename, filename);
|
||||
}
|
||||
void AnimateFile(const Path& source, const Path& destination,
|
||||
const AnimationSettings& settings = AnimationSettings::Default());
|
||||
|
||||
std::string Animate(const std::string& source,
|
||||
const AnimationSettings& settings = AnimationSettings::Default());
|
||||
}
|
||||
|
||||
#endif // KANIMAJI_KANIMAJI_HPP
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#ifndef KANIMAJI_COLOUR_HPP
|
||||
#define KANIMAJI_COLOUR_HPP
|
||||
#ifndef KANIMAJI_RGB_HPP
|
||||
#define KANIMAJI_RGB_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
@ -56,16 +56,16 @@ namespace Kanimaji
|
|||
}
|
||||
}
|
||||
|
||||
struct Colour
|
||||
struct RGB
|
||||
{
|
||||
std::uint8_t Red;
|
||||
std::uint8_t Green;
|
||||
std::uint8_t Blue;
|
||||
|
||||
static constexpr Colour FromHex(std::string_view hex)
|
||||
static constexpr RGB FromHex(std::string_view hex)
|
||||
{
|
||||
if (hex.empty()) {
|
||||
return Colour { .Red = 0, .Green = 0, .Blue = 0 };
|
||||
return RGB { .Red = 0, .Green = 0, .Blue = 0 };
|
||||
}
|
||||
|
||||
if (hex[0] == '#') {
|
||||
|
@ -74,12 +74,12 @@ namespace Kanimaji
|
|||
|
||||
for (char ch : hex) {
|
||||
if ((ch < '0' || ch > '9') && (ch < 'A' || ch > 'F') && (ch < 'a' && ch > 'f')) {
|
||||
return Colour { .Red = 0, .Green = 0, .Blue = 0 };
|
||||
return RGB { .Red = 0, .Green = 0, .Blue = 0 };
|
||||
}
|
||||
}
|
||||
|
||||
if (hex.length() == 3) {
|
||||
return Colour {
|
||||
return RGB {
|
||||
.Red = Implementation::ToDec(hex[0]),
|
||||
.Green = Implementation::ToDec(hex[1]),
|
||||
.Blue = Implementation::ToDec(hex[2]),
|
||||
|
@ -87,14 +87,14 @@ namespace Kanimaji
|
|||
}
|
||||
|
||||
if (hex.length() == 6) {
|
||||
return Colour {
|
||||
return RGB {
|
||||
.Red = Implementation::ToDec(hex[0], hex[1]),
|
||||
.Green = Implementation::ToDec(hex[2], hex[3]),
|
||||
.Blue = Implementation::ToDec(hex[4], hex[5]),
|
||||
};
|
||||
}
|
||||
|
||||
return Colour { .Red = 0, .Green = 0, .Blue = 0 };
|
||||
return RGB { .Red = 0, .Green = 0, .Blue = 0 };
|
||||
}
|
||||
|
||||
constexpr std::string ToHex() const
|
||||
|
@ -108,4 +108,4 @@ namespace Kanimaji
|
|||
};
|
||||
}
|
||||
|
||||
#endif // KANIMAJI_COLOUR_HPP
|
||||
#endif // KANIMAJI_RGB_HPP
|
52
Libraries/Kanimaji/Include/Kanimaji/Settings.hpp
Normal file
52
Libraries/Kanimaji/Include/Kanimaji/Settings.hpp
Normal file
|
@ -0,0 +1,52 @@
|
|||
#ifndef KANIMAJI_CONFIG_HPP
|
||||
#define KANIMAJI_CONFIG_HPP
|
||||
|
||||
#include "RGB.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
|
||||
namespace Kanimaji
|
||||
{
|
||||
enum class Flag {
|
||||
Enable, Disable,
|
||||
};
|
||||
|
||||
enum class Progression {
|
||||
Linear,
|
||||
EaseIn,
|
||||
EaseOut,
|
||||
EaseInOut,
|
||||
};
|
||||
|
||||
std::string ToString(Progression progression);
|
||||
|
||||
struct StrokeStyle
|
||||
{
|
||||
double Width;
|
||||
RGB Colour;
|
||||
};
|
||||
|
||||
using TimeScalingFunc = std::function<double(double)>;
|
||||
using Duration = std::chrono::duration<double, std::ratio<1>>;
|
||||
|
||||
struct AnimationSettings
|
||||
{
|
||||
Progression StrokeProgression;
|
||||
StrokeStyle UnfilledStroke;
|
||||
StrokeStyle FilledStroke;
|
||||
RGB StrokeFillingColour;
|
||||
|
||||
Flag EnableBrush;
|
||||
StrokeStyle Brush;
|
||||
StrokeStyle BrushBorder;
|
||||
|
||||
TimeScalingFunc LengthToTimeScaling;
|
||||
Duration WaitBeforeRepeating;
|
||||
Duration DelayBetweenStrokes;
|
||||
|
||||
static AnimationSettings Default();
|
||||
};
|
||||
}
|
||||
|
||||
#endif // KANIMAJI_CONFIG_HPP
|
|
@ -1 +0,0 @@
|
|||
#include "Kanimaji/Colour.hpp"
|
11
Libraries/Kanimaji/Source/Error.cpp
Normal file
11
Libraries/Kanimaji/Source/Error.cpp
Normal file
|
@ -0,0 +1,11 @@
|
|||
#include "Kanimaji/Error.hpp"
|
||||
|
||||
#include <format>
|
||||
|
||||
namespace Kanimaji
|
||||
{
|
||||
ParseError::ParseError(std::size_t current, std::string_view message)
|
||||
: Error(std::format("[At: {}] {}", current, message))
|
||||
, mPosition(current)
|
||||
{}
|
||||
}
|
|
@ -1,9 +1,7 @@
|
|||
#include "Kanimaji/Colour.hpp"
|
||||
#include "Kanimaji/Config.hpp"
|
||||
#include "Kanimaji/Kanimaji.hpp"
|
||||
#include "Kanimaji/Settings.hpp"
|
||||
#include "SVG.hpp"
|
||||
|
||||
#include <ctre.hpp>
|
||||
#include <pugixml.hpp>
|
||||
|
||||
#include <format>
|
||||
|
@ -13,57 +11,48 @@ namespace Kanimaji
|
|||
{
|
||||
namespace
|
||||
{
|
||||
constexpr GroupConfig StrokeBorderConfig { .Width = 5, .Colour = Colour::FromHex("#666") };
|
||||
constexpr GroupConfig StrokeUnfilledConfig{ .Width = 2, .Colour = Colour::FromHex("#EEE") };
|
||||
constexpr GroupConfig StrokeFilledConfig { .Width = 3.1, .Colour = Colour::FromHex("#000") };
|
||||
constexpr GroupConfig BrushConfig { .Width = 5.5, .Colour = Colour::FromHex("#F00") };
|
||||
constexpr GroupConfig BrushBorderConfig { .Width = 7, .Colour = Colour::FromHex("#666") };
|
||||
|
||||
constexpr Colour StrokeFillingColour = Colour::FromHex("#F00");
|
||||
|
||||
// What the fuck is this?
|
||||
constexpr double WaitAfter = 1.5;
|
||||
|
||||
using namespace std::string_view_literals;
|
||||
constexpr auto StrokeProgressionFormat = (
|
||||
" @keyframes stroke-{} {{\n"
|
||||
" 0.000% {{ stroke-dashoffset: {:.3f}; }}\n"
|
||||
" {:.3f}% {{ stroke-dashoffset: {:.3f}; }}\n"
|
||||
" {:.3f}% {{ stroke-dashoffset: 0; }}\n"
|
||||
" 100.000% {{ stroke-dashoffset: 0; }}\n"
|
||||
" }}\n"sv
|
||||
);
|
||||
constexpr auto AnimationVisibilityFormat = (
|
||||
" @keyframes showhide-{} {{\n"
|
||||
" {:.3f}% {{ visibility: hidden; }}\n"
|
||||
" {:.3f}% {{ stroke: {}; }}\n"
|
||||
" }}\n"sv
|
||||
);
|
||||
constexpr auto AnimationProgressionFormat = (
|
||||
" #{} {{\n"
|
||||
" stroke-dasharray: {:.3f} {:.3f};\n"
|
||||
" stroke-dashoffset: 0;\n"
|
||||
" animation: stroke-{} {:.3f}s {} infinite,\n"
|
||||
" showhide-{} {:.3f}s step-start infinite;\n"
|
||||
" }}\n"sv
|
||||
);
|
||||
constexpr auto BrushVisibilityFormat = (
|
||||
" @keyframes showhide-brush-{} {{\n"
|
||||
" {:.3f}% {{ visibility: hidden; }}\n"
|
||||
" {:.3f}% {{ visibility: visible; }}\n"
|
||||
" 100.000% {{ visibility: hidden; }}\n"
|
||||
" }}\n"sv
|
||||
);
|
||||
constexpr auto BrushProgressionFormat = (
|
||||
" #{}, #{} {{\n"
|
||||
" stroke-dasharray: 0 {:.3f};\n"
|
||||
" animation: stroke-{} {:.3f}s {} infinite,\n"
|
||||
" showhide-brush-{} {:.3f}s step-start infinite;\n"
|
||||
" }}\n"sv
|
||||
);
|
||||
constexpr auto StrokeProgressionFormat =
|
||||
" "
|
||||
"@keyframes stroke-{} {{ "
|
||||
"0.000% {{ stroke-dashoffset: {:.3f}; }} "
|
||||
"{:.3f}% {{ stroke-dashoffset: {:.3f}; }} "
|
||||
"{:.3f}% {{ stroke-dashoffset: 0; }} "
|
||||
"100.000% {{ stroke-dashoffset: 0; }} }}\n"sv;
|
||||
constexpr auto AnimationVisibilityFormat =
|
||||
" "
|
||||
"@keyframes display-{} {{ "
|
||||
"{:.3f}% {{ visibility: hidden; }} "
|
||||
"{:.3f}% {{ stroke: {}; }} }}\n"sv;
|
||||
constexpr auto AnimationProgressionFormat =
|
||||
" "
|
||||
"#{} {{ "
|
||||
"stroke-dasharray: {:.3f} {:.3f}; "
|
||||
"stroke-dashoffset: 0; "
|
||||
"animation: stroke-{} {:.3f}s {} infinite, "
|
||||
"display-{} {:.3f}s step-start infinite; }}\n"sv;
|
||||
constexpr auto BrushVisibilityFormat =
|
||||
" "
|
||||
"@keyframes display-brush-{} {{ "
|
||||
"{:.3f}% {{ visibility: hidden; }} "
|
||||
"{:.3f}% {{ visibility: visible; }} "
|
||||
"100.000% {{ visibility: hidden; }} }}\n"sv;
|
||||
constexpr auto BrushProgressionFormat =
|
||||
" "
|
||||
"#{}, #{} {{ "
|
||||
"stroke-dasharray: 0 {:.3f}; "
|
||||
"animation: stroke-{} {:.3f}s {} infinite, "
|
||||
"display-brush-{} {:.3f}s step-start infinite; }}\n"sv;
|
||||
|
||||
constexpr auto StylesHeader =
|
||||
"\n "
|
||||
"/* Styles generated automatically by Kanimaji, please avoid editing manually. */\n"sv;
|
||||
|
||||
pugi::xml_node append_group(pugi::xml_node &svg, std::string_view name, GroupConfig config)
|
||||
double AsSeconds(auto duration) {
|
||||
return std::max(0.0, duration.count());
|
||||
}
|
||||
|
||||
pugi::xml_node AppendGroup(pugi::xml_node& svg, std::string_view name, StrokeStyle config)
|
||||
{
|
||||
pugi::xml_node newGroup = svg.append_child("g");
|
||||
newGroup.append_attribute("id") = name;
|
||||
|
@ -73,140 +62,196 @@ namespace Kanimaji
|
|||
);
|
||||
return newGroup;
|
||||
}
|
||||
|
||||
void AmendComment(pugi::xml_document& doc)
|
||||
{
|
||||
pugi::xml_node comment = doc.find_child([](pugi::xml_node& node) {
|
||||
return node.type() == pugi::node_comment;
|
||||
});
|
||||
if (comment) {
|
||||
std::string text = comment.value();
|
||||
text.append(
|
||||
"\n"
|
||||
"===============================================================================\n"
|
||||
"\n"
|
||||
"This file has been modified by the Kanimaji tool\n"
|
||||
"avilable here: (Still very WIP, will be available later.)\n"
|
||||
"\n"
|
||||
"The Kanimaji tool is based on the original kanimaji.py script.\n"
|
||||
"Copyright (c) 2016 Maurizio Monge\n"
|
||||
"The script is available here: https://github.com/maurimo/kanimaji\n"
|
||||
"Used under the terms of the MIT licence as specified in the project's README.md\n"
|
||||
"\n"
|
||||
"Modifications (compared to the original KanjiVG file):\n"
|
||||
"* This comment was amended with this section\n"
|
||||
"* The stroke numbers were removed to prevent decrease clutter\n"
|
||||
"* Special Styles section (and accompanying <g> and <use> tags) were generated\n"
|
||||
);
|
||||
comment.set_value(text);
|
||||
}
|
||||
}
|
||||
|
||||
void Animate(pugi::xml_document& doc, const AnimationSettings& settings)
|
||||
{
|
||||
pugi::xml_node svg = doc.child("svg");
|
||||
if (!svg) {
|
||||
throw Error("Unexpected document format: Expected to find a SVG element");
|
||||
}
|
||||
|
||||
svg.remove_child(svg.find_child([](pugi::xml_node& node) {
|
||||
return std::string_view(node.attribute("id").as_string()).contains("StrokeNumbers");
|
||||
}));
|
||||
|
||||
pugi::xml_node pathsContainer = svg.find_child([](pugi::xml_node& node) {
|
||||
return std::string_view(node.attribute("id").as_string()).contains("StrokePaths");
|
||||
});
|
||||
pathsContainer.attribute("style") = "fill:none;visibility:hidden;";
|
||||
std::string baseId = pathsContainer.attribute("id").as_string();
|
||||
baseId.erase(0, baseId.find('_') + 1);
|
||||
|
||||
// 1st pass for getting information
|
||||
double totalLength = 0.0;
|
||||
pugi::xpath_node_set paths = svg.select_nodes("//path");
|
||||
for (const auto& path : paths) {
|
||||
std::string_view d = path.node().attribute("d").as_string();
|
||||
totalLength += settings.LengthToTimeScaling(SVG::Path(d).Length());
|
||||
}
|
||||
|
||||
std::string strokeKeyframes;
|
||||
std::string strokeDisplay;
|
||||
std::string strokeProgress;
|
||||
std::string brushKeyframes;
|
||||
std::string brushProgress;
|
||||
|
||||
auto background = AppendGroup(svg, "kvg:Kanimaji_Stroke_Background_" + baseId,
|
||||
settings.UnfilledStroke);
|
||||
auto brushBorder = AppendGroup(svg, "kvg:Kanimaji_Brush_Border_" + baseId,
|
||||
settings.BrushBorder);
|
||||
auto animation = AppendGroup(svg, "kvg:Kanimaji_Animation_" + baseId,
|
||||
settings.FilledStroke);
|
||||
auto brush = AppendGroup(svg, "kvg:Kanimaji_Brush_" + baseId, settings.Brush);
|
||||
|
||||
totalLength += paths.size() * AsSeconds(settings.DelayBetweenStrokes);
|
||||
double totalTime = totalLength
|
||||
+ AsSeconds(settings.WaitBeforeRepeating)
|
||||
- AsSeconds(settings.DelayBetweenStrokes);
|
||||
double previousLength = 0.0;
|
||||
double currentLength = 0.0;
|
||||
// 2nd pass to prepare the CSS
|
||||
for (const auto& path : paths) {
|
||||
std::string_view d = path.node().attribute("d").as_string();
|
||||
|
||||
double segmentLength = SVG::Path(d).Length();
|
||||
previousLength = currentLength;
|
||||
currentLength += settings.LengthToTimeScaling(segmentLength);
|
||||
|
||||
double startTime = 100.0 * (previousLength / totalTime);
|
||||
double endTime = 100.0 * (currentLength / totalTime);
|
||||
|
||||
currentLength += AsSeconds(settings.DelayBetweenStrokes);
|
||||
|
||||
pugi::xml_attribute idAttr = path.node().attribute("id");
|
||||
if (!idAttr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string id = idAttr.as_string();
|
||||
std::string css = id;
|
||||
if (auto colonPos = css.find(':'); colonPos != css.npos) {
|
||||
css.replace(css.find(":"), 1, "\\:");
|
||||
}
|
||||
|
||||
std::string pathId = id;
|
||||
if (pathId.starts_with("kvg:")) {
|
||||
pathId = pathId.substr(4);
|
||||
}
|
||||
|
||||
strokeKeyframes.append(std::format(StrokeProgressionFormat,
|
||||
pathId, segmentLength, startTime, segmentLength, endTime));
|
||||
strokeDisplay.append(std::format(AnimationVisibilityFormat,
|
||||
pathId, startTime, endTime,
|
||||
settings.StrokeFillingColour.ToHex()));
|
||||
strokeProgress.append(std::format(AnimationProgressionFormat,
|
||||
css + "-kanimaji-animation", segmentLength, segmentLength,
|
||||
pathId, totalTime, ToString(settings.StrokeProgression),
|
||||
pathId, totalTime));
|
||||
if (settings.EnableBrush == Flag::Enable) {
|
||||
brushKeyframes.append(std::format(BrushVisibilityFormat,
|
||||
pathId, startTime, endTime - 0.001));
|
||||
brushProgress.append(std::format(BrushProgressionFormat,
|
||||
css + "-kanimaji-brush",
|
||||
css + "-kanimaji-brush-border", segmentLength,
|
||||
pathId, totalTime,
|
||||
ToString(settings.StrokeProgression), pathId,
|
||||
totalTime));
|
||||
}
|
||||
|
||||
|
||||
pugi::xml_node useBackground = background.append_child("use");
|
||||
useBackground.append_attribute("id") = id + "-kanimaji-background";
|
||||
useBackground.append_attribute("href") = "#" + id;
|
||||
|
||||
pugi::xml_node useAnimation = animation.append_child("use");
|
||||
useAnimation.append_attribute("id") = id + "-kanimaji-animation";
|
||||
useAnimation.append_attribute("href") = "#" + id;
|
||||
|
||||
if (settings.EnableBrush == Flag::Enable) {
|
||||
pugi::xml_node useBrushBackground = brushBorder.append_child("use");
|
||||
useBrushBackground.append_attribute("id") = id + "-kanimaji-brush-border";
|
||||
useBrushBackground.append_attribute("href") = "#" + id;
|
||||
|
||||
pugi::xml_node useBrush = brush.append_child("use");
|
||||
useBrush.append_attribute("id") = id + "-kanimaji-brush";
|
||||
useBrush.append_attribute("href") = "#" + id;
|
||||
}
|
||||
}
|
||||
|
||||
// This is how we add styles - prepare the whole thing in a separate buffer first.
|
||||
std::string styles;
|
||||
styles.append(StylesHeader);
|
||||
styles.append(strokeKeyframes);
|
||||
styles.append(strokeDisplay);
|
||||
styles.append(strokeProgress);
|
||||
styles.append(brushKeyframes);
|
||||
styles.append(brushProgress);
|
||||
styles.append(" ");
|
||||
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 Animate(const std::string& source, const std::string& destination)
|
||||
void AnimateFile(const Path& source, const Path& destination, const AnimationSettings& settings)
|
||||
{
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result readResult = doc.load_file(source.c_str(), pugi::parse_full);
|
||||
if (!readResult) {
|
||||
return false;
|
||||
pugi::xml_parse_result result = doc.load_file(source.c_str(), pugi::parse_full);
|
||||
if (!result) {
|
||||
constexpr std::string_view errorFormat = "Failed to load file from {}, due to: {}";
|
||||
throw FileError(std::format(errorFormat, source.string(), result.description()));
|
||||
}
|
||||
|
||||
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 script.\n"
|
||||
"Copyright (c) 2016 Maurizio Monge\n"
|
||||
"The script is available here: https://github.com/maurimo/kanimaji\n"
|
||||
"Used under the terms of the MIT licence as specified in the project's README.md\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);
|
||||
AmendComment(doc);
|
||||
Animate(doc, settings);
|
||||
|
||||
pugi::xml_node svg = doc.child("svg");
|
||||
if (!doc.save_file(destination.c_str())) {
|
||||
throw FileError(std::format("Failed to save file to {}", destination.string()));
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
});
|
||||
paths.attribute("style") = "fill:none;visibility:hidden;";
|
||||
std::string baseId = paths.attribute("id").as_string();
|
||||
baseId.erase(0, baseId.find('_') + 1);
|
||||
|
||||
// 1st pass for getting information
|
||||
double totalLength = 0.0;
|
||||
for (const auto& path : svg.select_nodes("//path")) {
|
||||
std::string_view d = path.node().attribute("d").as_string();
|
||||
try {
|
||||
totalLength += SVG::Path(d).Length();
|
||||
}
|
||||
catch (const SVG::ParseError& e) {
|
||||
return false;
|
||||
}
|
||||
std::string Animate(const std::string& source, const AnimationSettings& settings)
|
||||
{
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result = doc.load_string(source.c_str(), pugi::parse_full);
|
||||
if (!result) {
|
||||
constexpr std::string_view errorFormat = "Failed to parse SVG document: {}";
|
||||
throw ParseError(result.offset, std::format(errorFormat, result.description()));
|
||||
}
|
||||
|
||||
std::string styles =
|
||||
"\n /* Styles autogenerated by Kanimaji, please do not edit manually. */\n";
|
||||
AmendComment(doc);
|
||||
Animate(doc, settings);
|
||||
|
||||
auto background = append_group(svg, "kvg:Kanimaji_Stroke_Background_" + baseId, StrokeUnfilledConfig);
|
||||
auto brushBorder = append_group(svg, "kvg:Kanimaji_Brush_Background_" + baseId, BrushBorderConfig);
|
||||
auto animation = append_group(svg, "kvg:Kanimaji_Animation_" + baseId, StrokeFilledConfig);
|
||||
auto brush = append_group(svg, "kvg:Kanimaji_Brush_" + baseId, BrushConfig);
|
||||
|
||||
double previousLength = 0.0;
|
||||
double currentLength = 0.0;
|
||||
// 2nd pass to prepare the CSS
|
||||
for (const auto& xpath : svg.select_nodes("//path")) {
|
||||
pugi::xml_node path = xpath.node();
|
||||
|
||||
double segmentLength = SVG::Path(path.attribute("d").as_string()).Length();
|
||||
previousLength = currentLength;
|
||||
currentLength += segmentLength;
|
||||
|
||||
double startTime = 100.0 * (previousLength / totalLength);
|
||||
double endTime = 100.0 * (currentLength / totalLength);
|
||||
|
||||
pugi::xml_attribute idAttr = path.attribute("id");
|
||||
if (!idAttr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string id = idAttr.as_string();
|
||||
std::string css = id;
|
||||
if (auto colonPos = css.find(':'); colonPos != css.npos) {
|
||||
css.replace(css.find(":"), 1, "\\:");
|
||||
}
|
||||
|
||||
std::string pathId = id;
|
||||
if (pathId.starts_with("kvg:")) {
|
||||
pathId = pathId.substr(4);
|
||||
}
|
||||
|
||||
double totalTime = 4.0;
|
||||
|
||||
styles.append(std::format(StrokeProgressionFormat,
|
||||
pathId, segmentLength, startTime, segmentLength, endTime));
|
||||
styles.append(std::format(AnimationVisibilityFormat,
|
||||
pathId, startTime, endTime, StrokeFillingColour.ToHex()));
|
||||
styles.append(std::format(AnimationProgressionFormat,
|
||||
css + "-kanimaji-animation", segmentLength, segmentLength,
|
||||
pathId, totalTime, "ease-in-out", pathId, totalTime));
|
||||
styles.append(std::format(BrushVisibilityFormat, pathId, startTime, endTime - 0.001));
|
||||
styles.append(std::format(BrushProgressionFormat,
|
||||
css + "-kanimaji-brush", css + "-kanimaji-brush-background",
|
||||
segmentLength, pathId, totalTime, "ease-in-out", pathId,
|
||||
totalTime));
|
||||
|
||||
|
||||
pugi::xml_node useBackground = background.append_child("use");
|
||||
useBackground.append_attribute("id") = id + "-kanimaji-background";
|
||||
useBackground.append_attribute("href") = "#" + id;
|
||||
|
||||
pugi::xml_node useBrushBackground = brushBorder.append_child("use");
|
||||
useBrushBackground.append_attribute("id") = id + "-kanimaji-brush-background";
|
||||
useBrushBackground.append_attribute("href") = "#" + id;
|
||||
|
||||
pugi::xml_node useAnimation = animation.append_child("use");
|
||||
useAnimation.append_attribute("id") = id + "-kanimaji-animation";
|
||||
useAnimation.append_attribute("href") = "#" + id;
|
||||
|
||||
pugi::xml_node useBrush = brush.append_child("use");
|
||||
useBrush.append_attribute("id") = id + "-kanimaji-brush";
|
||||
useBrush.append_attribute("href") = "#" + id;
|
||||
}
|
||||
|
||||
// This is how we add styles - prepare the whole thing in a separate buffer first.
|
||||
styles.append(" ");
|
||||
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;
|
||||
std::stringstream str;
|
||||
doc.save(str);
|
||||
return str.str();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,11 +9,6 @@
|
|||
|
||||
namespace Kanimaji::SVG
|
||||
{
|
||||
ParseError::ParseError(std::size_t current, std::string_view message)
|
||||
: Error(std::format("[At: {}] {}", current, message))
|
||||
, mPosition(current)
|
||||
{}
|
||||
|
||||
struct Vec2
|
||||
{
|
||||
double x;
|
||||
|
|
|
@ -1,31 +1,14 @@
|
|||
#ifndef KANIMAJI_SVG_HPP
|
||||
#define KANIMAJI_SVG_HPP
|
||||
|
||||
#include "Kanimaji/Error.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace Kanimaji::SVG
|
||||
{
|
||||
class Error : public std::runtime_error
|
||||
{
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
class ParseError : public Error
|
||||
{
|
||||
public:
|
||||
ParseError(std::size_t current, std::string_view message);
|
||||
|
||||
std::size_t Position() const
|
||||
{
|
||||
return mPosition;
|
||||
}
|
||||
private:
|
||||
std::size_t mPosition;
|
||||
};
|
||||
|
||||
class Path
|
||||
{
|
||||
public:
|
||||
|
|
51
Libraries/Kanimaji/Source/Settings.cpp
Normal file
51
Libraries/Kanimaji/Source/Settings.cpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
#include "Kanimaji/Settings.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <utility>
|
||||
|
||||
namespace Kanimaji
|
||||
{
|
||||
std::string ToString(Progression progression)
|
||||
{
|
||||
switch (progression) {
|
||||
case Progression::Linear: return "linear";
|
||||
case Progression::EaseIn: return "ease-in";
|
||||
case Progression::EaseOut: return "ease-out";
|
||||
case Progression::EaseInOut: return "ease-in-out";
|
||||
|
||||
default: std::unreachable();
|
||||
};
|
||||
}
|
||||
|
||||
AnimationSettings AnimationSettings::Default()
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
return AnimationSettings {
|
||||
.StrokeProgression = Progression::EaseInOut,
|
||||
.UnfilledStroke = StrokeStyle {
|
||||
.Width = 2.0,
|
||||
.Colour = RGB::FromHex("#EEEEEE"),
|
||||
},
|
||||
.FilledStroke = StrokeStyle {
|
||||
.Width = 3.0,
|
||||
.Colour = RGB::FromHex("#000000"),
|
||||
},
|
||||
.StrokeFillingColour = RGB::FromHex("#FF0000"),
|
||||
.EnableBrush = Flag::Enable,
|
||||
.Brush = StrokeStyle {
|
||||
.Width = 5.5,
|
||||
.Colour = RGB::FromHex("#FF0000"),
|
||||
},
|
||||
.BrushBorder = StrokeStyle {
|
||||
.Width = 7.0,
|
||||
.Colour = RGB::FromHex("#666666"),
|
||||
},
|
||||
.LengthToTimeScaling = [] (double length) -> double {
|
||||
return std::pow(length, 1.0 / 3.0) / 6.0;
|
||||
},
|
||||
.WaitBeforeRepeating = 1s,
|
||||
.DelayBetweenStrokes = 50ms,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -12,8 +12,11 @@ int main(const int argc, const char *argv[])
|
|||
// return 1;
|
||||
//}
|
||||
|
||||
if (!Kanimaji::Animate("084b8.svg", "084b8-out.svg")) {
|
||||
std::println("Could not animate the specified file.");
|
||||
try {
|
||||
Kanimaji::AnimateFile("084b8.svg", "084b8-out.svg");
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
std::println("Could not animate the input file");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue