#include "atlas/AtlasParser.hpp" #include #include #include #include #include namespace dts { namespace { std::string trim(std::string value) { const auto isSpace = [](unsigned char character) { return std::isspace(character) != 0; }; value.erase(value.begin(), std::find_if_not(value.begin(), value.end(), isSpace)); value.erase(std::find_if_not(value.rbegin(), value.rend(), isSpace).base(), value.end()); return value; } std::vector splitValues(const std::string& value) { std::vector values; std::stringstream stream(value); std::string item; while (std::getline(stream, item, ',')) { values.push_back(trim(item)); } return values; } std::optional> parseIntegers(const std::string& value) { std::vector integers; for (const auto& item : splitValues(value)) { int number = 0; const auto result = std::from_chars(item.data(), item.data() + item.size(), number); if (result.ec != std::errc{} || result.ptr != item.data() + item.size()) { return std::nullopt; } integers.push_back(number); } return integers; } bool isPageKey(const std::string& key) { return key == "size" || key == "format" || key == "filter" || key == "repeat" || key == "pma" || key == "scale"; } bool parseBoolean(const std::string& value) { return value == "true" || value == "yes" || value == "1"; } void applyPageProperty(AtlasPage& page, const std::string& key, const std::string& value) { if (key == "size") { page.size = parseIntegers(value).value_or(std::vector{}); } else if (key == "format") { page.format = value; } else if (key == "filter") { page.filter = value; } else if (key == "repeat") { page.repeat = value; } else if (key == "pma") { page.premultipliedAlpha = parseBoolean(value); } else if (key == "scale") { try { page.scale = std::stod(value); } catch (...) { page.scale.reset(); } } } void applyRegionProperty(AtlasRegion& region, const std::string& key, const std::string& value) { if (key == "rotate") { region.rotated = parseBoolean(value) || value == "90" || value == "270"; } else if (key == "bounds") { region.bounds = parseIntegers(value).value_or(std::vector{}); } else if (key == "xy") { region.position = parseIntegers(value).value_or(std::vector{}); } else if (key == "size") { region.size = parseIntegers(value).value_or(std::vector{}); } else if (key == "orig") { region.originalSize = parseIntegers(value).value_or(std::vector{}); } else if (key == "offset" || key == "offsets") { region.offsets = parseIntegers(value).value_or(std::vector{}); } else if (key == "split") { region.split = parseIntegers(value).value_or(std::vector{}); } else if (key == "pad") { region.pad = parseIntegers(value).value_or(std::vector{}); } else if (key == "index") { const auto values = parseIntegers(value); if (values && !values->empty()) { region.index = values->front(); } } } } // namespace AtlasParseResult AtlasParser::parseFile(const std::filesystem::path& path) const { std::ifstream input(path, std::ios::binary); if (!input) { return {std::nullopt, "Atlas file could not be opened.", 0}; } std::ostringstream contents; contents << input.rdbuf(); return parseText(contents.str()); } AtlasParseResult AtlasParser::parseText(const std::string& text) const { AtlasDocument document; AtlasPage* page = nullptr; AtlasRegion* region = nullptr; bool expectPage = true; std::istringstream input(text); std::string rawLine; std::size_t lineNumber = 0; while (std::getline(input, rawLine)) { ++lineNumber; if (!rawLine.empty() && rawLine.back() == '\r') { rawLine.pop_back(); } const auto line = trim(rawLine); if (line.empty()) { expectPage = true; page = nullptr; region = nullptr; continue; } const auto colon = line.find(':'); if (colon == std::string::npos) { if (expectPage || page == nullptr) { AtlasPage nextPage; nextPage.imagePath = std::filesystem::path(line); document.pages.push_back(std::move(nextPage)); page = &document.pages.back(); region = nullptr; expectPage = false; } else { AtlasRegion nextRegion; nextRegion.name = line; page->regions.push_back(std::move(nextRegion)); region = &page->regions.back(); } continue; } if (!page) { return {std::nullopt, "Atlas property appears before a page name.", lineNumber}; } const auto key = trim(line.substr(0, colon)); const auto value = trim(line.substr(colon + 1)); if (!region && isPageKey(key)) { applyPageProperty(*page, key, value); } else if (region) { applyRegionProperty(*region, key, value); } else { document.warnings.push_back("Unknown page property at line " + std::to_string(lineNumber)); } } if (document.pages.empty()) { return {std::nullopt, "Atlas does not contain any pages.", 0}; } for (const auto& atlasPage : document.pages) { if (atlasPage.imagePath.empty()) { return {std::nullopt, "Atlas contains an empty page name.", 0}; } } return {std::move(document), {}, 0}; } } // namespace dts