#include #include "map.h" #define KBLIB_DEF_MACROS 1 #include "kblib/convert.h" using namespace std::string_literals; static constexpr auto npos = std::string::npos; #include "gen/cmap.inc" const std::string cmaps_default{reinterpret_cast(cmap_inc), sizeof(cmap_inc)}; cmap_t current_cmap = scan_cmap(cmaps_default, "__default_maps"s); unsigned lookupMap(std::string query) { for (unsigned m = 0; m != current_cmap.size(); ++m) { if (current_cmap[m].first == query) { return m; } } return pFromStr(unsigned, query); } png::color colorFromHex(std::string& hexStr) { png::byte r, g, b; uint_fast32_t c; std::stringstream(hexStr) >> std::hex >> c; r = (c & 0xFF0000) >> 16; g = (c & 0x00FF00) >> 8; b = (c & 0x0000FF); return png::color(r, g, b); } cmap_t scan_cmap(std::string maps, std::string file) { int line{1}; /* Format of data file: * * e := {empty} * delim := : * char := [^:] * string := char* {Empty, or a char plus a string} * digit := [0-9] * number := digit+ * hexdigit := [0-9A-Fa-f] * byte := hexdigit . hexdigit * color := byte . byte . byte * space := [ \n\t,;#] * lsep := ([:punct:] | [:space:])* * clist := color . space* . clist {colors may have arbitrary spacing after} * cmap := string . delim . number . space+ . clist * * file := space* . delim . cmap . (delim . cmap)* * */ const std::string space{" \r\n\t,;#"}, eofspace{" \r\n\t"}; constexpr char delim{':'}, escape{'\\'}; cmap_t out; std::pair working; std::string number, color; int expected, remaining; // A clist must have as many colors as the preceding number // state machine: enum class states : uint8_t { Preamble, // Start of file PreEscape, //:: is an escape for : String, // A cmap-name StringEscape, //\c in a cmap-name is replaced with c Number, // A decimal number of colors to expect CList, // Between colors HexColor, // Reads 6 hex characters, ignores spacing chars } state = states::Preamble; for (auto c : maps) { if (c == '\n') { ++line; } switch (state) { case states::Preamble: if (c == delim) { state = states::PreEscape; } break; case states::PreEscape: if (c == delim) { state = states::Preamble; break; } else { state = states::String; } [[fallthrough]]; case states::String: if (c == escape) { state = states::StringEscape; } else if (c == delim) { if (!working.first.length()) { throw malformed_file_error{ file + ":" + kblib::toStr(line) + ": For cmap #"s + kblib::toStr(out.size()) + ": cmap names cannot be empty"s}; } state = states::Number; } else { working.first += c; } break; case states::StringEscape: working.first += c; state = states::String; break; case states::Number: if (isdigit(c)) { number += c; } else if (space.find(c) != npos) { if (number.empty()) { throw malformed_file_error{ file + ":" + kblib::toStr(line) + ": For cmap #"s + kblib::toStr(out.size()) + ": "s + working.first + ": "s + "cmap length cannot be blank"s}; } state = states::CList; expected = remaining = pFromStr(int, number); number.clear(); if (!expected) { throw malformed_file_error{ file + ":" + kblib::toStr(line) + ": For cmap #"s + kblib::toStr(out.size()) + ": "s + working.first + ": "s + "cmap length cannot be 0"s}; } } else { throw malformed_file_error{ file + ":" + kblib::toStr(line) + ": For cmap #"s + kblib::toStr(out.size()) + ": "s + working.first + ": "s + "encountered unexpected "s + c + " while reading number"s}; } break; case states::CList: if (isxdigit(c)) { color += c; state = states::HexColor; } else if (c == delim) { if (remaining) { throw malformed_file_error{ file + ":" + kblib::toStr(line) + ": For cmap #"s + kblib::toStr(out.size()) + ": "s + working.first + ": "s + "expected "s + kblib::toStr(expected) + " colors, got "s + kblib::toStr(expected - remaining)}; } else { out.push_back(working); working.first.clear(); working.second.clear(); state = states::String; } } break; case states::HexColor: if (isxdigit(c)) { color += c; } else if (c == delim) { throw malformed_file_error{ file + ":" + kblib::toStr(line) + ": For cmap #"s + kblib::toStr(out.size()) + ": "s + working.first + ": "s + "encountered delimiter while reading color: "s + color}; } else if (space.find(c) == npos) { throw malformed_file_error{ file + ":" + kblib::toStr(line) + ": For cmap #"s + kblib::toStr(out.size()) + ": "s + working.first + ": "s + "illegal character in color: "s + "\""s + color + c + "\" "s + kblib::toStr(+c)}; } if (color.length() == 6) { --remaining; working.second.push_back(colorFromHex(color)); color.clear(); state = states::CList; } break; } } switch (state) { case states::Preamble: throw malformed_file_error{file + ":" + kblib::toStr(line) + ": no cmaps in file"s}; break; case states::PreEscape: [[fallthrough]]; case states::StringEscape: throw malformed_file_error{file + ":" + kblib::toStr(line) + ": encountered end of file in escape"s}; break; case states::Number: throw malformed_file_error{file + ":" + kblib::toStr(line) + ": For cmap #"s + kblib::toStr(out.size()) + ": "s + working.first + ": "s + "expected number, got end of file"s}; break; case states::HexColor: throw malformed_file_error{ file + ":" + kblib::toStr(line) + ": For cmap #"s + kblib::toStr(out.size()) + ": "s + working.first + ": "s + "encountered end of file while reading color: "s + color}; break; case states::String: if (working.first.find_first_not_of(eofspace) != npos) { throw malformed_file_error{ file + ":" + kblib::toStr(line) + ": For cmap #"s + kblib::toStr(out.size()) + ": "s + "encountered end of file while reading cmap-name: "s + working.first}; } break; case states::CList: if (remaining) { throw malformed_file_error{ file + ":" + kblib::toStr(line) + ": For cmap #"s + kblib::toStr(out.size()) + ": "s + working.first + ": "s + "expected "s + kblib::toStr(expected) + " colors, got "s + kblib::toStr(expected - remaining)}; } else { out.push_back(working); } break; } return out; }