#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 "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 if (expected > 256) { throw malformed_file_error{ file + ":" + kblib::toStr(line) + ": For cmap #"s + kblib::toStr(out.size()) + ": "s + working.first + ": "s + "cmap length cannot exceed 256"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; }