Compare commits

...

12 commits

12 changed files with 127 additions and 53 deletions

View file

@ -1,4 +1,4 @@
if(NOT TARGET pugixml) if(NOT TARGET pugixml::static)
include(FetchContent) include(FetchContent)
FetchContent_Declare( FetchContent_Declare(

View file

@ -12,7 +12,7 @@ target_link_libraries(Kanimaji
KVGToolsCommon KVGToolsCommon
PRIVATE PRIVATE
pugixml pugixml::static
) )
target_include_directories(Kanimaji target_include_directories(Kanimaji

View file

@ -13,9 +13,11 @@ namespace Kanimaji
std::string Animate(const std::string& source, std::string Animate(const std::string& source,
const AnimationSettings& settings = AnimationSettings::Default()); const AnimationSettings& settings = AnimationSettings::Default());
void RemoveStrokeNumbers(const std::string& source, const std::string& destination); void RemoveStrokeNumbers(const std::string& source, const std::string& destination,
const RemovalSetttings& settings = RemovalSetttings::Default());
std::string RemoveStrokeNumbers(const std::string& source); std::string RemoveStrokeNumbers(const std::string& source,
const RemovalSetttings& settings = RemovalSetttings::Default());
} }
#endif // KANIMAJI_KANIMAJI_HPP #endif // KANIMAJI_KANIMAJI_HPP

View file

@ -49,6 +49,13 @@ namespace Kanimaji
static AnimationSettings Default(); static AnimationSettings Default();
}; };
struct RemovalSetttings
{
StrokeStyle Style;
static RemovalSetttings Default();
};
} }
#endif // KANIMAJI_CONFIG_HPP #endif // KANIMAJI_CONFIG_HPP

View file

@ -12,42 +12,55 @@ namespace Kanimaji
{ {
namespace namespace
{ {
using namespace std::string_view_literals; constexpr std::string_view StaticStrokeStyleFormat(
constexpr auto StrokeProgressionFormat = "fill:none;stroke:{};stroke-width:{};stroke-linecap:round;stroke-linejoin:round;"
);
constexpr std::string_view StrokeProgressionFormat(
" " " "
"@keyframes stroke-{} {{ " "@keyframes stroke-{} {{ "
"0.000% {{ stroke-dashoffset: {:.3f}; }} " "0.000% {{ stroke-dashoffset: {:.3f}; }} "
"{:.3f}% {{ stroke-dashoffset: {:.3f}; }} " "{:.3f}% {{ stroke-dashoffset: {:.3f}; }} "
"{:.3f}% {{ stroke-dashoffset: 0; }} " "{:.3f}% {{ stroke-dashoffset: 0; }} "
"100.000% {{ stroke-dashoffset: 0; }} }}\n"sv; "100.000% {{ stroke-dashoffset: 0; }} }}\n"
constexpr auto AnimationVisibilityFormat = );
constexpr std::string_view AnimationVisibilityFormat(
" " " "
"@keyframes display-{} {{ " "@keyframes display-{} {{ "
"{:.3f}% {{ visibility: hidden; }} " "{:.3f}% {{ visibility: hidden; }} "
"{:.3f}% {{ stroke: {}; }} }}\n"sv; "{:.3f}% {{ stroke: {}; }} }}\n"
constexpr auto AnimationProgressionFormat = );
constexpr std::string_view AnimationProgressionFormat(
" " " "
"#{} {{ " "#{} {{ "
"stroke-dasharray: {:.3f} {:.3f}; " "stroke-dasharray: {:.3f} {:.3f}; "
"stroke-dashoffset: 0; " "stroke-dashoffset: 0; "
"animation: stroke-{} {:.3f}s {} infinite, " "animation: stroke-{} {:.3f}s {} infinite, "
"display-{} {:.3f}s step-start infinite; }}\n"sv; "display-{} {:.3f}s step-start infinite; }}\n"
constexpr auto BrushVisibilityFormat = );
constexpr std::string_view BrushVisibilityFormat(
" " " "
"@keyframes display-brush-{} {{ " "@keyframes display-brush-{} {{ "
"{:.3f}% {{ visibility: hidden; }} " "{:.3f}% {{ visibility: hidden; }} "
"{:.3f}% {{ visibility: visible; }} " "{:.3f}% {{ visibility: visible; }} "
"100.000% {{ visibility: hidden; }} }}\n"sv; "100.000% {{ visibility: hidden; }} }}\n"
constexpr auto BrushProgressionFormat = );
constexpr std::string_view BrushProgressionFormat(
" " " "
"#{}, #{} {{ " "#{}, #{} {{ "
"stroke-dasharray: 0 {:.3f}; " "stroke-dasharray: 0 {:.3f}; "
"animation: stroke-{} {:.3f}s {} infinite, " "animation: stroke-{} {:.3f}s {} infinite, "
"display-brush-{} {:.3f}s step-start infinite; }}\n"sv; "display-brush-{} {:.3f}s step-start infinite; }}\n"
);
constexpr auto StylesHeader = constexpr std::string_view StylesHeader(
"\n " "\n "
"/* Styles generated automatically by Kanimaji, please avoid editing manually. */\n"sv; "/* Styles generated automatically by Kanimaji, please avoid editing manually. */\n"
);
double AsSeconds(auto duration) { double AsSeconds(auto duration) {
return std::max(0.0, duration.count()); return std::max(0.0, duration.count());
@ -65,8 +78,7 @@ namespace Kanimaji
pugi::xml_node newGroup = svg.append_child("g"); pugi::xml_node newGroup = svg.append_child("g");
newGroup.append_attribute("id") = name; newGroup.append_attribute("id") = name;
newGroup.append_attribute("style") = std::format( newGroup.append_attribute("style") = std::format(
"fill:none;stroke:{};stroke-width:{};stroke-linecap:round;stroke-linejoin:round;", StaticStrokeStyleFormat, config.Colour.ToHex(), config.Width
config.Colour.ToHex(), config.Width
); );
return newGroup; return newGroup;
} }
@ -270,7 +282,8 @@ namespace Kanimaji
return str.str(); return str.str();
} }
void RemoveStrokeNumbers(const std::string& source, const std::string& destination) void RemoveStrokeNumbers(const std::string& source, const std::string& destination,
const RemovalSetttings& settings)
{ {
pugi::xml_document doc; pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file(source.c_str(), pugi::parse_full); pugi::xml_parse_result result = doc.load_file(source.c_str(), pugi::parse_full);
@ -284,6 +297,13 @@ namespace Kanimaji
throw Error("Unexpected document format: Expected to find a SVG element"); throw Error("Unexpected document format: Expected to find a SVG element");
} }
pugi::xml_node pathsGroup = svg.find_child([](pugi::xml_node& node) {
return std::string_view(node.attribute("id").as_string()).contains("StrokePaths");
});
pathsGroup.attribute("style") = std::format(
StaticStrokeStyleFormat, settings.Style.Colour.ToHex(), settings.Style.Width
);
RemoveStrokeNumbers(svg); RemoveStrokeNumbers(svg);
if (!doc.save_file(destination.c_str())) { if (!doc.save_file(destination.c_str())) {
@ -291,7 +311,7 @@ namespace Kanimaji
} }
} }
std::string RemoveStrokeNumbers(const std::string& source) std::string RemoveStrokeNumbers(const std::string& source, const RemovalSetttings& settings)
{ {
pugi::xml_document doc; pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_string(source.c_str(), pugi::parse_full); pugi::xml_parse_result result = doc.load_string(source.c_str(), pugi::parse_full);
@ -305,6 +325,13 @@ namespace Kanimaji
throw Error("Unexpected document format: Expected to find a SVG element"); throw Error("Unexpected document format: Expected to find a SVG element");
} }
pugi::xml_node pathsGroup = svg.find_child([](pugi::xml_node& node) {
return std::string_view(node.attribute("id").as_string()).contains("StrokePaths");
});
pathsGroup.attribute("style") = std::format(
StaticStrokeStyleFormat, settings.Style.Colour.ToHex(), settings.Style.Width
);
RemoveStrokeNumbers(svg); RemoveStrokeNumbers(svg);
std::stringstream str; std::stringstream str;

View file

@ -48,4 +48,14 @@ namespace Kanimaji
.DelayBetweenStrokes = 50ms, .DelayBetweenStrokes = 50ms,
}; };
} }
RemovalSetttings RemovalSetttings::Default()
{
return RemovalSetttings {
.Style = StrokeStyle {
.Width = 3.0,
.Colour = RGB::FromHex("#000000"),
},
};
}
} }

View file

@ -17,7 +17,7 @@ target_link_libraries(Tablegen
KVGToolsCommon KVGToolsCommon
PRIVATE PRIVATE
pugixml pugixml::static
) )
target_sources(Tablegen target_sources(Tablegen

View file

@ -4,7 +4,6 @@
#include <Common/RGB.hpp> #include <Common/RGB.hpp>
#include <string> #include <string>
#include <vector>
namespace Tablegen namespace Tablegen
{ {
@ -49,7 +48,6 @@ namespace Tablegen
std::size_t CharactersPerRow; std::size_t CharactersPerRow;
std::string ImageFormat; std::string ImageFormat;
std::string AnimationFormat; std::string AnimationFormat;
std::vector<CharacterInfo> Characters;
static Settings Default(); static Settings Default();
}; };

View file

@ -8,17 +8,22 @@
#endif #endif
#include <string> #include <string>
#include <vector>
namespace Tablegen namespace Tablegen
{ {
std::string GeneratePage(const Settings& settings = Settings::Default()); std::string GeneratePage(const std::vector<CharacterInfo>& characters,
const Settings& settings = Settings::Default());
void GeneratePage(const std::string& path, const Settings& settings = Settings::Default()); void GeneratePage(const std::string& path, const std::vector<CharacterInfo>& characters,
const Settings& settings = Settings::Default());
# ifdef TABLEGEN_EXPOSE_XML # ifdef TABLEGEN_EXPOSE_XML
void GenerateAsChild(pugi::xml_node& node, const Settings& settings = Settings::Default()); void GenerateAsChild(pugi::xml_node& node, const std::vector<CharacterInfo>& characters,
const Settings& settings = Settings::Default());
pugi::xml_document GenerateDocument(const Settings& settings = Settings::Default()); pugi::xml_document GenerateDocument(const std::vector<CharacterInfo>& characters,
const Settings& settings = Settings::Default());
# endif # endif
} }

View file

@ -32,7 +32,6 @@ namespace Tablegen
.CharactersPerRow = 3, .CharactersPerRow = 3,
.ImageFormat = "https://3011.io/Assets/Images/Kanji/Static/{}.png", .ImageFormat = "https://3011.io/Assets/Images/Kanji/Static/{}.png",
.AnimationFormat = "https://3011.io/Assets/Temp/{}.svg", .AnimationFormat = "https://3011.io/Assets/Temp/{}.svg",
.Characters = {},
}; };
} }
} }

View file

@ -64,11 +64,9 @@ namespace Tablegen
constexpr std::string ImageStyle(std::string_view indent, constexpr std::string ImageStyle(std::string_view indent,
std::size_t index, std::size_t index,
std::string_view imageName, std::string_view imagePath,
std::string_view imageFormat,
std::string_view extraSpecifier = "") std::string_view extraSpecifier = "")
{ {
std::string imagePath = std::vformat(imageFormat, std::make_format_args(imageName));
return std::format(imageStyleFormat, indent, index, extraSpecifier, imagePath); return std::format(imageStyleFormat, indent, index, extraSpecifier, imagePath);
} }
@ -95,9 +93,9 @@ namespace Tablegen
} }
} }
std::string GeneratePage(const Settings& settings) std::string GeneratePage(const std::vector<CharacterInfo>& characters, const Settings& settings)
{ {
pugi::xml_document doc = GenerateDocument(settings); pugi::xml_document doc = GenerateDocument(characters, settings);
std::uint32_t formatOptions; std::uint32_t formatOptions;
formatOptions = pugi::format_indent | pugi::format_no_escapes | pugi::format_no_declaration; formatOptions = pugi::format_indent | pugi::format_no_escapes | pugi::format_no_declaration;
@ -106,16 +104,18 @@ namespace Tablegen
return str.str(); return str.str();
} }
void GeneratePage(const std::string& path, const Settings& settings) void GeneratePage(const std::string& path, const std::vector<CharacterInfo>& characters,
const Settings& settings)
{ {
pugi::xml_document doc = GenerateDocument(settings); pugi::xml_document doc = GenerateDocument(characters, settings);
std::uint32_t formatOptions; std::uint32_t formatOptions;
formatOptions = pugi::format_indent | pugi::format_no_escapes | pugi::format_no_declaration; formatOptions = pugi::format_indent | pugi::format_no_escapes | pugi::format_no_declaration;
doc.save_file("test.html", " ", formatOptions); doc.save_file("test.html", " ", formatOptions);
} }
void GenerateAsChild(pugi::xml_node& node, const Settings& settings) void GenerateAsChild(pugi::xml_node& node, const std::vector<CharacterInfo>& characters,
const Settings& settings)
{ {
pugi::xml_node tableRoot = node; pugi::xml_node tableRoot = node;
@ -136,6 +136,9 @@ namespace Tablegen
pugi::xml_node comment = table.append_child(pugi::node_comment); pugi::xml_node comment = table.append_child(pugi::node_comment);
comment.set_value(" Autogenerated by Tablegen, please avoid editing manually. "); comment.set_value(" Autogenerated by Tablegen, please avoid editing manually. ");
pugi::xml_node preload = table.append_child("div");
preload.append_attribute("hidden") = "hidden";
pugi::xml_node script = table.append_child("script"); pugi::xml_node script = table.append_child("script");
pugi::xml_node styles = table.append_child("style"); pugi::xml_node styles = table.append_child("style");
@ -151,11 +154,28 @@ namespace Tablegen
std::string tagIndent((indentLevel > 0 ? indentLevel - 1 : 0) * 4, ' '); std::string tagIndent((indentLevel > 0 ? indentLevel - 1 : 0) * 4, ' ');
pugi::xml_node tableBody = table.append_child("tbody"); pugi::xml_node tableBody = table.append_child("tbody");
for (pugi::xml_node row; const auto& [i, character] : vw::enumerate(settings.Characters)) { for (pugi::xml_node row; const auto& [i, character] : vw::enumerate(characters)) {
std::string imagePath = std::vformat(
settings.ImageFormat, std::make_format_args(character.ImagePath)
);
std::string animationPath = std::vformat(
settings.AnimationFormat, std::make_format_args(character.AnimationPath)
);
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// HTML // HTML
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
if (i % 3 == 0) { pugi::xml_node preloadImage = preload.append_child("link");
preloadImage.append_attribute("rel") = "preload";
preloadImage.append_attribute("href") = imagePath;
preloadImage.append_attribute("as") = "image";
pugi::xml_node preloadAnimation = preload.append_child("link");
preloadAnimation.append_attribute("rel") = "preload";
preloadAnimation.append_attribute("href") = animationPath;
preloadAnimation.append_attribute("as") = "image";
if (i % settings.CharactersPerRow == 0) {
row = tableBody.append_child("tr"); row = tableBody.append_child("tr");
row.append_attribute("class") = "kanji-table-row"; row.append_attribute("class") = "kanji-table-row";
} }
@ -186,9 +206,8 @@ namespace Tablegen
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// CSS // CSS
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
staticImg.append(ImageStyle(indent, i, character.ImagePath, settings.ImageFormat)); staticImg.append(ImageStyle(indent, i, imagePath));
animatedImg.append(ImageStyle(indent, i, character.AnimationPath, animatedImg.append(ImageStyle(indent, i, animationPath, ":checked"));
settings.AnimationFormat, ":checked"));
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// JavaScript // JavaScript
@ -201,10 +220,11 @@ namespace Tablegen
script.append_child(pugi::node_pcdata).set_value(Script(indent, tagIndent, buttons)); script.append_child(pugi::node_pcdata).set_value(Script(indent, tagIndent, buttons));
} }
pugi::xml_document GenerateDocument(const Settings& settings) pugi::xml_document GenerateDocument(const std::vector<CharacterInfo>& characters,
const Settings& settings)
{ {
pugi::xml_document doc; pugi::xml_document doc;
GenerateAsChild(doc); GenerateAsChild(doc, characters, settings);
return doc; return doc;
} }
} }

View file

@ -21,6 +21,12 @@ void AnimateFile(const Path& source, const Path& destination,
std::string Animate(const std::string& source, std::string Animate(const std::string& source,
const AnimationSettings& settings = AnimationSettings::Default()); const AnimationSettings& settings = AnimationSettings::Default());
void RemoveStrokeNumbers(const std::string& source, const std::string& destination,
const RemovalSetttings& settings = RemovalSetttings::Default());
std::string RemoveStrokeNumbers(const std::string& source,
const RemovalSetttings& settings = RemovalSetttings::Default());
``` ```
The former function takes in two paths, where the first one specifies the file to be read, and The former function takes in two paths, where the first one specifies the file to be read, and
@ -78,14 +84,18 @@ This library generates a table of kanji images from a specified list with variou
values. It's made up of the following interface: values. It's made up of the following interface:
```cpp ```cpp
std::string GeneratePage(const Settings& settings = Settings::Default()); std::string GeneratePage(const std::vector<CharacterInfo>& characters,
const Settings& settings = Settings::Default());
void GeneratePage(const std::string& path, const Settings& settings = Settings::Default()); void GeneratePage(const std::string& path, const std::vector<CharacterInfo>& characters,
const Settings& settings = Settings::Default());
#ifdef TABLEGEN_EXPOSE_XML #ifdef TABLEGEN_EXPOSE_XML
void GenerateAsChild(pugi::xml_node& node, const Settings& settings = Settings::Default()); void GenerateAsChild(pugi::xml_node& node, const std::vector<CharacterInfo>& characters,
const Settings& settings = Settings::Default());
pugi::xml_document GenerateDocument(const Settings& settings = Settings::Default()); pugi::xml_document GenerateDocument(const std::vector<CharacterInfo>& characters,
const Settings& settings = Settings::Default());
#endif #endif
``` ```
@ -109,7 +119,7 @@ Some caveats:
The default settings generate a table with no characters (and horrible styling), you will need to The default settings generate a table with no characters (and horrible styling), you will need to
specify custom settings like this for actual use: specify custom settings like this for actual use:
```cpp ```cpp
auto doc = GeneratePage({ auto doc = GeneratePage(std::vector<CharacterInfo>(), {
.FullDocument = Flag::Enable, .FullDocument = Flag::Enable,
.OverrideIndentLevel = Flag::Disable, .OverrideIndentLevel = Flag::Disable,
.IndentLevel = 0, .IndentLevel = 0,
@ -126,9 +136,5 @@ auto doc = GeneratePage({
.CharactersPerRow = 3, .CharactersPerRow = 3,
.ImageFormat = "http://the.url/where/you/have/images/{}.svg", .ImageFormat = "http://the.url/where/you/have/images/{}.svg",
.AnimationFormat = "http://the.url/where/you/have/animations/{}.svg", .AnimationFormat = "http://the.url/where/you/have/animations/{}.svg",
.Characters = {
/* The actual characters you want in the table, example: */
{ .Label = "Day", .ImagePath = "%E6%97%A5", .AnimationPath = "%E6%97%A5", },
},
}); });
``` ```