Porytiles
Loading...
Searching...
No Matches
jasc_pal_loader.cpp
Go to the documentation of this file.
2
3#include <expected>
4#include <filesystem>
5#include <fstream>
6#include <optional>
7
11
12namespace {
13
14using namespace porytiles;
15
16bool is_wildcard_marker(std::string_view line)
17{
18 // allow '-' for backwards compatibility with Porytiles1, when writing pals we'll always use '*'
19 return line == "*" || line == "-";
20}
21
22ChainableResult<std::optional<Rgba32>> parse_jasc_line(std::string_view line, const TextFormatter &format)
23{
24 if (is_wildcard_marker(line)) {
25 return std::optional<Rgba32>{std::nullopt};
26 }
27
28 const std::vector<std::string> color_components = split(std::string{line}, " ");
29
30 if (color_components.size() != 3) {
31 return FormattableError{format.format(
32 "invalid JASC color format, expected line in format '{}'", FormatParam{"R G B", Style::bold})};
33 }
34
35 auto red_result = parse_int<int>(color_components[0]);
36 auto green_result = parse_int<int>(color_components[1]);
37 auto blue_result = parse_int<int>(color_components[2]);
38
39 if (!red_result.has_value()) {
40 return FormattableError{format.format(
41 "invalid rgb red component '{}': {}", FormatParam{color_components[0], Style::bold}, red_result.error())};
42 }
43 if (!green_result.has_value()) {
44 return FormattableError{format.format(
45 "invalid rgb green component '{}': {}",
46 FormatParam{color_components[1], Style::bold},
47 green_result.error())};
48 }
49 if (!blue_result.has_value()) {
50 return FormattableError{format.format(
51 "invalid rgb blue component '{}': {}", FormatParam{color_components[2], Style::bold}, blue_result.error())};
52 }
53
54 const auto red = red_result.value();
55 const auto green = green_result.value();
56 const auto blue = blue_result.value();
57
58 if (red < 0 || red > 255) {
59 return FormattableError{format.format(
60 "invalid rgb red component '{}': range must be 0 <= red <= 255", FormatParam{red, Style::bold})};
61 }
62
63 if (green < 0 || green > 255) {
64 return FormattableError{format.format(
65 "invalid rgb green component '{}': range must be 0 <= green <= 255", FormatParam{green, Style::bold})};
66 }
67
68 if (blue < 0 || blue > 255) {
69 return FormattableError{format.format(
70 "invalid rgb blue component '{}': range must be 0 <= blue <= 255", FormatParam{blue, Style::bold})};
71 }
72
73 return std::optional{Rgba32{
74 static_cast<std::uint8_t>(red),
75 static_cast<std::uint8_t>(green),
76 static_cast<std::uint8_t>(blue),
78}
79
81 const std::filesystem::path &path,
82 bool allow_wildcards,
83 const TextFormatter &format,
84 const FileHighlightPrinter &file_printer)
85{
86 if (!exists(path)) {
87 return FormattableError{"'{}': File does not exist.", FormatParam{path.string(), Style::bold}};
88 }
89
90 // Slurp the entire file into a vector for FileHighlightPrinter support
91 std::vector<std::string> lines{};
92 {
93 std::ifstream stream{path};
94 std::string line_buf{};
95 while (std::getline(stream, line_buf)) {
96 trim_line_ending(line_buf);
97 lines.push_back(line_buf);
98 }
99 }
100
101 if (lines.empty()) {
102 std::vector<std::string> err_lines{};
103 err_lines.push_back(format.format(
104 "{}: file is empty, expected at least 3 header lines:", FormatParam{path.string(), Style::bold}));
105 err_lines.emplace_back();
106 err_lines.push_back(format.format("{}", FormatParam{"JASC-PAL", Style::italic}));
107 err_lines.push_back(format.format("{}", FormatParam{"0100", Style::italic}));
108 err_lines.push_back(format.format("{}", FormatParam{"<PAL-SIZE>", Style::italic}));
109 return FormattableError{std::move(err_lines)};
110 }
111
112 // Need at least 3 header lines
113 if (lines.size() < 3) {
114 std::vector<std::string> err_lines{};
115 err_lines.push_back(format.format(
116 "{}: file too short, expected at least 3 header lines", FormatParam{path.string(), Style::bold}));
117 err_lines.emplace_back();
118 err_lines.append_range(file_printer.print(lines, std::vector<std::size_t>{0}));
119 return FormattableError{std::move(err_lines)};
120 }
121
123
124 // First line of file *must* be "JASC-PAL"
125 if (lines[0] != "JASC-PAL") {
126 std::vector<std::string> err_lines{};
127 err_lines.push_back(format.format(
128 "{}:{}: expected '{}' as first line of file",
129 FormatParam{path.string(), Style::bold},
130 FormatParam{"1", Style::bold},
131 FormatParam{"JASC-PAL", Style::bold}));
132 err_lines.emplace_back();
133 err_lines.append_range(file_printer.print(lines, std::vector<std::size_t>{0}));
134 return FormattableError{std::move(err_lines)};
135 }
136
137 // Next line of file *must* be "0100"
138 if (lines[1] != "0100") {
139 std::vector<std::string> err_lines{};
140 err_lines.push_back(format.format(
141 "{}:{}: expected '{}' as second line of file",
142 FormatParam{path.string(), Style::bold},
143 FormatParam{"2", Style::bold},
144 FormatParam{"0100", Style::bold}));
145 err_lines.emplace_back();
146 err_lines.append_range(file_printer.print(lines, std::vector<std::size_t>{1}));
147 return FormattableError{std::move(err_lines)};
148 }
149
150 // Next line of file *must* be the declared size of the palette
151 const auto declared_size_result = parse_int<std::size_t>(lines[2]);
152 if (!declared_size_result.has_value()) {
153 std::vector<std::string> err_lines{};
154 err_lines.push_back(format.format(
155 "{}:{}: expected integral value for declared size",
156 FormatParam{path.string(), Style::bold},
157 FormatParam{"3", Style::bold}));
158 err_lines.emplace_back();
159 err_lines.append_range(file_printer.print(lines, std::vector<std::size_t>{2}));
160 return FormattableError{std::move(err_lines)};
161 }
162 const auto declared_size = declared_size_result.value();
163 if (declared_size != pal::max_size) {
164 std::vector<std::string> err_lines{};
165 err_lines.push_back(format.format(
166 "{}:{}: expected declared size to be '{}', saw '{}'",
167 FormatParam{path.string(), Style::bold},
168 FormatParam{"3", Style::bold},
169 FormatParam{pal::max_size, Style::bold},
170 FormatParam{declared_size, Style::bold}));
171 err_lines.emplace_back();
172 err_lines.append_range(file_printer.print(lines, std::vector<std::size_t>{2}));
173 return FormattableError{std::move(err_lines)};
174 }
175
176 // Rest of file lines are the colors (starting at index 3)
177 for (std::size_t color_index = 0; color_index < lines.size() - 3; ++color_index) {
178 const std::size_t line_index = color_index + 3;
179 const auto &line = lines[line_index];
180 const auto color_result = parse_jasc_line(line, format);
181
182 if (!color_result.has_value()) {
183 std::vector<std::string> err_lines{};
184 err_lines.push_back(format.format(
185 "{}:{}: error parsing color",
186 FormatParam{path.string(), Style::bold},
187 FormatParam{line_index + 1, Style::bold}));
188 err_lines.emplace_back();
189 err_lines.append_range(file_printer.print(lines, std::vector{line_index}));
191 FormattableError{std::move(err_lines)}, color_result};
192 }
193
194 if (const auto &maybe_color = color_result.value(); maybe_color.has_value()) {
195 pal.set(color_index, maybe_color.value());
196 }
197 else {
198 // Wildcard marker encountered
199 if (!allow_wildcards) {
200 std::vector<std::string> err_lines{};
201 err_lines.push_back(format.format(
202 "{}:{}: wildcard not allowed",
203 FormatParam{path.string(), Style::bold},
204 FormatParam{line_index + 1, Style::bold}));
205 err_lines.emplace_back();
206 err_lines.append_range(file_printer.print(lines, std::vector{line_index}));
207 return FormattableError{std::move(err_lines)};
208 }
209 pal.set_wildcard(color_index);
210 }
211 }
212
213 if (const std::size_t actual_color_count = lines.size() - 3; actual_color_count != declared_size) {
214 std::vector<std::string> err_lines{};
215 err_lines.push_back(format.format(
216 "{}: declared size is '{}' but only saw {} defined colors",
217 FormatParam{path.string(), Style::bold},
218 FormatParam{declared_size, Style::bold},
219 FormatParam{actual_color_count}));
220 err_lines.emplace_back();
221 err_lines.append_range(file_printer.print(lines, std::vector<std::size_t>{2}));
222 return FormattableError{std::move(err_lines)};
223 }
224
225 return pal;
226}
227
228} // namespace
229
230namespace porytiles {
231
233JascPalLoader::load_with_wildcards(const std::filesystem::path &path) const
234{
235 return parse_jasc_file(path, true, *format_, *file_printer_);
236}
237
239{
240 return parse_jasc_file(path, false, *format_, *file_printer_);
241}
242
243} // namespace porytiles
A result type that maintains a chainable sequence of errors for debugging and error reporting.
A service for printing file lines with highlighted lines and line numbers.
std::vector< std::string > print(const std::vector< std::string > &lines, const std::vector< std::size_t > &line_indices_to_highlight, std::size_t window_size=9) const
Prints lines with specified lines highlighted and line numbers shown.
A text parameter with associated styling for formatted output.
General-purpose error implementation with formatted message support.
Definition error.hpp:63
ChainableResult< Palette< Rgba32, pal::max_size > > load_with_wildcards(const std::filesystem::path &path) const override
ChainableResult< Palette< Rgba32, pal::max_size > > load(const std::filesystem::path &path) const override
A generic palette container for colors that support transparency checking.
Definition palette.hpp:47
Represents a 32-bit RGBA color.
Definition rgba32.hpp:23
static constexpr std::uint8_t alpha_opaque
Definition rgba32.hpp:26
static const Style bold
Bold text formatting.
Abstract base class for applying text styling with context-aware formatting.
virtual std::string format(const std::string &format_str, const std::vector< FormatParam > &params) const
Formats a string with styled parameters using fmtlib syntax.
constexpr std::size_t max_size
Definition palette.hpp:19
std::string & trim_line_ending(std::string &line)
Removes line ending characters from a string in-place.
@ split
Split the animation into separate animations for each palette variant (not yet implemented).
Utility functions for string manipulation and formatting.