Add paint stroke script automation
This commit is contained in:
210
src/paint/stroke_script.cpp
Normal file
210
src/paint/stroke_script.cpp
Normal file
@@ -0,0 +1,210 @@
|
||||
#include "paint/stroke_script.h"
|
||||
|
||||
#include <array>
|
||||
#include <cerrno>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace pp::paint {
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] std::string_view trim(std::string_view text) noexcept
|
||||
{
|
||||
while (!text.empty() && (text.front() == ' ' || text.front() == '\t' || text.front() == '\r')) {
|
||||
text.remove_prefix(1);
|
||||
}
|
||||
|
||||
while (!text.empty() && (text.back() == ' ' || text.back() == '\t' || text.back() == '\r')) {
|
||||
text.remove_suffix(1);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string_view strip_comment(std::string_view line) noexcept
|
||||
{
|
||||
const auto comment = line.find('#');
|
||||
if (comment == std::string_view::npos) {
|
||||
return line;
|
||||
}
|
||||
|
||||
return line.substr(0, comment);
|
||||
}
|
||||
|
||||
[[nodiscard]] pp::foundation::Result<float> parse_float_token(std::string_view token) noexcept
|
||||
{
|
||||
token = trim(token);
|
||||
if (token.empty() || token.size() >= 64U) {
|
||||
return pp::foundation::Result<float>::failure(
|
||||
pp::foundation::Status::invalid_argument("stroke script numeric token is invalid"));
|
||||
}
|
||||
|
||||
std::array<char, 64> buffer {};
|
||||
for (std::size_t i = 0; i < token.size(); ++i) {
|
||||
buffer[i] = token[i];
|
||||
}
|
||||
|
||||
char* end = nullptr;
|
||||
errno = 0;
|
||||
const auto value = std::strtof(buffer.data(), &end);
|
||||
if (errno != 0 || end != buffer.data() + static_cast<std::ptrdiff_t>(token.size()) || !std::isfinite(value)) {
|
||||
return pp::foundation::Result<float>::failure(
|
||||
pp::foundation::Status::invalid_argument("stroke script numeric token is invalid"));
|
||||
}
|
||||
|
||||
return pp::foundation::Result<float>::success(value);
|
||||
}
|
||||
|
||||
[[nodiscard]] pp::foundation::Result<std::size_t> split_tokens(
|
||||
std::string_view line,
|
||||
std::array<std::string_view, 8>& tokens) noexcept
|
||||
{
|
||||
std::size_t count = 0;
|
||||
std::size_t offset = 0;
|
||||
while (offset < line.size()) {
|
||||
while (offset < line.size() && (line[offset] == ' ' || line[offset] == '\t')) {
|
||||
++offset;
|
||||
}
|
||||
|
||||
if (offset >= line.size()) {
|
||||
break;
|
||||
}
|
||||
|
||||
const auto token_start = offset;
|
||||
while (offset < line.size() && line[offset] != ' ' && line[offset] != '\t') {
|
||||
++offset;
|
||||
}
|
||||
|
||||
if (count >= tokens.size()) {
|
||||
return pp::foundation::Result<std::size_t>::failure(
|
||||
pp::foundation::Status::invalid_argument("stroke script line has too many tokens"));
|
||||
}
|
||||
|
||||
tokens[count] = line.substr(token_start, offset - token_start);
|
||||
++count;
|
||||
}
|
||||
|
||||
return pp::foundation::Result<std::size_t>::success(count);
|
||||
}
|
||||
|
||||
[[nodiscard]] pp::foundation::Result<StrokeScriptStroke> parse_stroke_line(std::string_view line) noexcept
|
||||
{
|
||||
std::array<std::string_view, 8> tokens {};
|
||||
const auto token_count = split_tokens(line, tokens);
|
||||
if (!token_count) {
|
||||
return pp::foundation::Result<StrokeScriptStroke>::failure(token_count.status());
|
||||
}
|
||||
|
||||
if (token_count.value() != tokens.size() || tokens[0] != "stroke") {
|
||||
return pp::foundation::Result<StrokeScriptStroke>::failure(
|
||||
pp::foundation::Status::invalid_argument("stroke script line must be 'stroke x1 y1 p1 x2 y2 p2 spacing'"));
|
||||
}
|
||||
|
||||
const auto x1 = parse_float_token(tokens[1]);
|
||||
const auto y1 = parse_float_token(tokens[2]);
|
||||
const auto p1 = parse_float_token(tokens[3]);
|
||||
const auto x2 = parse_float_token(tokens[4]);
|
||||
const auto y2 = parse_float_token(tokens[5]);
|
||||
const auto p2 = parse_float_token(tokens[6]);
|
||||
const auto spacing = parse_float_token(tokens[7]);
|
||||
if (!x1) {
|
||||
return pp::foundation::Result<StrokeScriptStroke>::failure(x1.status());
|
||||
}
|
||||
if (!y1) {
|
||||
return pp::foundation::Result<StrokeScriptStroke>::failure(y1.status());
|
||||
}
|
||||
if (!p1) {
|
||||
return pp::foundation::Result<StrokeScriptStroke>::failure(p1.status());
|
||||
}
|
||||
if (!x2) {
|
||||
return pp::foundation::Result<StrokeScriptStroke>::failure(x2.status());
|
||||
}
|
||||
if (!y2) {
|
||||
return pp::foundation::Result<StrokeScriptStroke>::failure(y2.status());
|
||||
}
|
||||
if (!p2) {
|
||||
return pp::foundation::Result<StrokeScriptStroke>::failure(p2.status());
|
||||
}
|
||||
if (!spacing) {
|
||||
return pp::foundation::Result<StrokeScriptStroke>::failure(spacing.status());
|
||||
}
|
||||
|
||||
if (spacing.value() <= 0.0F) {
|
||||
return pp::foundation::Result<StrokeScriptStroke>::failure(
|
||||
pp::foundation::Status::invalid_argument("stroke script spacing must be greater than zero"));
|
||||
}
|
||||
|
||||
return pp::foundation::Result<StrokeScriptStroke>::success(StrokeScriptStroke {
|
||||
.start = StrokePoint {
|
||||
.x = x1.value(),
|
||||
.y = y1.value(),
|
||||
.pressure = p1.value(),
|
||||
},
|
||||
.end = StrokePoint {
|
||||
.x = x2.value(),
|
||||
.y = y2.value(),
|
||||
.pressure = p2.value(),
|
||||
},
|
||||
.spacing = spacing.value(),
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pp::foundation::Result<StrokeScript> parse_stroke_script(std::string_view text)
|
||||
{
|
||||
if (text.empty()) {
|
||||
return pp::foundation::Result<StrokeScript>::failure(
|
||||
pp::foundation::Status::invalid_argument("stroke script must not be empty"));
|
||||
}
|
||||
|
||||
if (text.size() > max_stroke_script_bytes) {
|
||||
return pp::foundation::Result<StrokeScript>::failure(
|
||||
pp::foundation::Status::out_of_range("stroke script exceeds the configured size limit"));
|
||||
}
|
||||
|
||||
StrokeScript script;
|
||||
std::size_t offset = 0;
|
||||
while (offset <= text.size()) {
|
||||
const auto line_start = offset;
|
||||
const auto line_end = text.find('\n', line_start);
|
||||
if (line_end == std::string_view::npos) {
|
||||
offset = text.size() + 1U;
|
||||
} else {
|
||||
offset = line_end + 1U;
|
||||
}
|
||||
|
||||
auto line = text.substr(line_start, (line_end == std::string_view::npos) ? std::string_view::npos : line_end - line_start);
|
||||
if (line.size() > max_stroke_script_line_length) {
|
||||
return pp::foundation::Result<StrokeScript>::failure(
|
||||
pp::foundation::Status::out_of_range("stroke script line exceeds the configured length limit"));
|
||||
}
|
||||
|
||||
line = trim(strip_comment(line));
|
||||
if (line.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (script.strokes.size() >= max_stroke_script_strokes) {
|
||||
return pp::foundation::Result<StrokeScript>::failure(
|
||||
pp::foundation::Status::out_of_range("stroke script stroke count exceeds the configured limit"));
|
||||
}
|
||||
|
||||
const auto stroke = parse_stroke_line(line);
|
||||
if (!stroke) {
|
||||
return pp::foundation::Result<StrokeScript>::failure(stroke.status());
|
||||
}
|
||||
|
||||
script.strokes.push_back(stroke.value());
|
||||
}
|
||||
|
||||
if (script.strokes.empty()) {
|
||||
return pp::foundation::Result<StrokeScript>::failure(
|
||||
pp::foundation::Status::invalid_argument("stroke script must contain at least one stroke"));
|
||||
}
|
||||
|
||||
return pp::foundation::Result<StrokeScript>::success(script);
|
||||
}
|
||||
|
||||
}
|
||||
28
src/paint/stroke_script.h
Normal file
28
src/paint/stroke_script.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "foundation/result.h"
|
||||
#include "paint/stroke.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace pp::paint {
|
||||
|
||||
constexpr std::size_t max_stroke_script_bytes = 1024 * 1024;
|
||||
constexpr std::size_t max_stroke_script_line_length = 512;
|
||||
constexpr std::size_t max_stroke_script_strokes = 10000;
|
||||
|
||||
struct StrokeScriptStroke {
|
||||
StrokePoint start;
|
||||
StrokePoint end;
|
||||
float spacing = 1.0F;
|
||||
};
|
||||
|
||||
struct StrokeScript {
|
||||
std::vector<StrokeScriptStroke> strokes;
|
||||
};
|
||||
|
||||
[[nodiscard]] pp::foundation::Result<StrokeScript> parse_stroke_script(std::string_view text);
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user