174 lines
5.7 KiB
C++
174 lines
5.7 KiB
C++
#include "atlas/AtlasParser.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <charconv>
|
|
#include <cctype>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
|
|
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<std::string> splitValues(const std::string& value) {
|
|
std::vector<std::string> values;
|
|
std::stringstream stream(value);
|
|
std::string item;
|
|
while (std::getline(stream, item, ',')) {
|
|
values.push_back(trim(item));
|
|
}
|
|
return values;
|
|
}
|
|
|
|
std::optional<std::vector<int>> parseIntegers(const std::string& value) {
|
|
std::vector<int> 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<int>{});
|
|
} 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<int>{});
|
|
} else if (key == "xy") {
|
|
region.position = parseIntegers(value).value_or(std::vector<int>{});
|
|
} else if (key == "size") {
|
|
region.size = parseIntegers(value).value_or(std::vector<int>{});
|
|
} else if (key == "orig") {
|
|
region.originalSize = parseIntegers(value).value_or(std::vector<int>{});
|
|
} else if (key == "offset" || key == "offsets") {
|
|
region.offsets = parseIntegers(value).value_or(std::vector<int>{});
|
|
} else if (key == "split") {
|
|
region.split = parseIntegers(value).value_or(std::vector<int>{});
|
|
} else if (key == "pad") {
|
|
region.pad = parseIntegers(value).value_or(std::vector<int>{});
|
|
} 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
|