summaryrefslogtreecommitdiff
path: root/subprojects/pixpat/pixpat-native/src/params.h
diff options
context:
space:
mode:
authorTomi Valkeinen <tomi.valkeinen@ideasonboard.com>2026-05-08 17:22:58 +0300
committerTomi Valkeinen <tomi.valkeinen@ideasonboard.com>2026-05-08 17:22:58 +0300
commit4e2b291a4acdc2cbd39f005c88bda363bc06bd34 (patch)
treee90048d5973ad1164b109d575cf577af7daf50be /subprojects/pixpat/pixpat-native/src/params.h
parent8f94b39040e79eccd9312ed1e467fe8ebfab8860 (diff)
parente0b7d30fd437292c88141fb08d60681870b86c6e (diff)
Merge commit 'e0b7d30fd437292c88141fb08d60681870b86c6e' as 'subprojects/pixpat'
Diffstat (limited to 'subprojects/pixpat/pixpat-native/src/params.h')
-rw-r--r--subprojects/pixpat/pixpat-native/src/params.h219
1 files changed, 219 insertions, 0 deletions
diff --git a/subprojects/pixpat/pixpat-native/src/params.h b/subprojects/pixpat/pixpat-native/src/params.h
new file mode 100644
index 0000000..aa2be67
--- /dev/null
+++ b/subprojects/pixpat/pixpat-native/src/params.h
@@ -0,0 +1,219 @@
+#pragma once
+
+#include <cctype>
+#include <cstddef>
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include "layout.h"
+
+namespace pixpat
+{
+
+// Pattern-specific parameters parsed from pixpat_pattern_opts::params.
+// The wire format is a comma-separated list of "key=value" items;
+// whitespace around tokens is trimmed, keys and values are
+// case-insensitive ASCII, and neither may contain ',' or '='.
+// Malformed input leaves ok() returning false — the pattern dispatcher
+// fails the call when that happens.
+//
+// Patterns query keys by name via get() / get_int() / get_hex_color().
+// Unknown keys are ignored: each pattern handles forward compatibility,
+// not the parser.
+class Params
+{
+public:
+ explicit Params(const char* csv);
+
+ bool ok() const noexcept {
+ return ok_;
+ }
+
+ std::optional<std::string_view> get(std::string_view key) const noexcept;
+ std::optional<int> get_int(std::string_view key) const noexcept;
+ std::optional<RGB16> get_hex_color(std::string_view key) const noexcept;
+
+private:
+ std::vector<std::pair<std::string, std::string> > kv_;
+ bool ok_{ true };
+};
+
+namespace detail
+{
+
+inline char ascii_tolower(char c) noexcept
+{
+ return (c >= 'A' && c <= 'Z') ? char(c + ('a' - 'A')) : c;
+}
+
+inline std::string_view trim(std::string_view s) noexcept
+{
+ while (!s.empty() && std::isspace(static_cast<unsigned char>(s.front())))
+ s.remove_prefix(1);
+ while (!s.empty() && std::isspace(static_cast<unsigned char>(s.back())))
+ s.remove_suffix(1);
+ return s;
+}
+
+inline bool ieq(std::string_view a, std::string_view b) noexcept
+{
+ if (a.size() != b.size())
+ return false;
+ for (size_t i = 0; i < a.size(); ++i)
+ if (ascii_tolower(a[i]) != ascii_tolower(b[i]))
+ return false;
+ return true;
+}
+
+} // namespace detail
+
+
+inline Params::Params(const char* csv)
+{
+ if (!csv || *csv == '\0')
+ return;
+
+ std::string_view s(csv);
+ while (!s.empty()) {
+ const size_t comma = s.find(',');
+ std::string_view item = (comma == std::string_view::npos)
+ ? s : s.substr(0, comma);
+ s = (comma == std::string_view::npos)
+ ? std::string_view{} : s.substr(comma + 1);
+
+ item = detail::trim(item);
+ if (item.empty()) {
+ ok_ = false;
+ return;
+ }
+
+ const size_t eq = item.find('=');
+ if (eq == std::string_view::npos) {
+ ok_ = false;
+ return;
+ }
+ const std::string_view k = detail::trim(item.substr(0, eq));
+ const std::string_view v = detail::trim(item.substr(eq + 1));
+ if (k.empty()) {
+ ok_ = false;
+ return;
+ }
+ kv_.emplace_back(std::string(k), std::string(v));
+ }
+}
+
+inline std::optional<std::string_view>
+Params::get(std::string_view key) const noexcept
+{
+ for (const auto& [k, v] : kv_)
+ if (detail::ieq(k, key))
+ return std::string_view(v);
+ return std::nullopt;
+}
+
+inline std::optional<int>
+Params::get_int(std::string_view key) const noexcept
+{
+ auto v = get(key);
+ if (!v || v->empty())
+ return std::nullopt;
+ int sign = 1;
+ size_t i = 0;
+ if ((*v)[0] == '-') { sign = -1; ++i; }
+ else if ((*v)[0] == '+') { ++i; }
+ if (i == v->size())
+ return std::nullopt;
+ int out = 0;
+ for (; i < v->size(); ++i) {
+ const char c = (*v)[i];
+ if (c < '0' || c > '9')
+ return std::nullopt;
+ out = out * 10 + (c - '0');
+ }
+ return sign * out;
+}
+
+// Parses a hex color string. The optional `0x`/`0X` prefix is allowed.
+// The number of hex digits after the prefix selects the layout:
+// 6 digits — 8-bit RRGGBB (alpha defaults to opaque)
+// 8 digits — 8-bit AARRGGBB (alpha-first)
+// 12 digits — 16-bit RRRRGGGGBBBB (alpha defaults to opaque)
+// 16 digits — 16-bit AAAARRRRGGGGBBBB (alpha-first)
+// 8-bit components are byte-replicated to the normalized 16-bit form
+// (0xFF → 0xFFFF); 16-bit components are stored directly. Any other
+// length, malformed digits, or stray separators yield std::nullopt.
+inline std::optional<RGB16>
+Params::get_hex_color(std::string_view key) const noexcept
+{
+ auto v = get(key);
+ if (!v)
+ return std::nullopt;
+
+ std::string_view s = *v;
+ if (s.size() >= 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X'))
+ s.remove_prefix(2);
+
+ const auto digit = [](char c) -> int {
+ if (c >= '0' && c <= '9') return c - '0';
+ if (c >= 'a' && c <= 'f') return c - 'a' + 10;
+ if (c >= 'A' && c <= 'F') return c - 'A' + 10;
+ return -1;
+ };
+ const auto parse_n = [&](size_t off, size_t n) -> std::optional<unsigned> {
+ unsigned out = 0;
+ for (size_t i = 0; i < n; ++i) {
+ const int d = digit(s[off + i]);
+ if (d < 0)
+ return std::nullopt;
+ out = (out << 4) | unsigned(d);
+ }
+ return out;
+ };
+
+ bool has_alpha;
+ bool is_16bpc;
+ switch (s.size()) {
+ case 6: has_alpha = false; is_16bpc = false; break;
+ case 8: has_alpha = true; is_16bpc = false; break;
+ case 12: has_alpha = false; is_16bpc = true; break;
+ case 16: has_alpha = true; is_16bpc = true; break;
+ default: return std::nullopt;
+ }
+
+ const size_t per = is_16bpc ? 4 : 2;
+ const unsigned full = is_16bpc ? 0xFFFFu : 0xFFu;
+ unsigned a = full, r, g, b;
+ size_t off = 0;
+ if (has_alpha) {
+ auto av = parse_n(off, per);
+ if (!av) return std::nullopt;
+ a = *av;
+ off += per;
+ }
+ auto rv = parse_n(off, per);
+ if (!rv) return std::nullopt;
+ r = *rv;
+ off += per;
+ auto gv = parse_n(off, per);
+ if (!gv) return std::nullopt;
+ g = *gv;
+ off += per;
+ auto bv = parse_n(off, per);
+ if (!bv) return std::nullopt;
+ b = *bv;
+
+ if (is_16bpc) {
+ return RGB16{ uint16_t(r), uint16_t(g), uint16_t(b), uint16_t(a) };
+ } else {
+ const auto rep = [](unsigned x) noexcept {
+ return uint16_t((x << 8) | x);
+ };
+ return RGB16{ rep(r), rep(g), rep(b), rep(a) };
+ }
+}
+
+} // namespace pixpat