| // SPDX-License-Identifier: BSD-3-Clause | |
| // Copyright (c) 2025, Syoyo Fujita and many contributors. | |
| // All rights reserved. | |
| // | |
| // EXR Reader: Reader class with error stack for safe memory reading | |
| #ifndef TINYEXR_EXR_READER_HH_ | |
| #define TINYEXR_EXR_READER_HH_ | |
| #include <vector> | |
| #include <string> | |
| #include "streamreader.hh" | |
| namespace tinyexr { | |
| // Reader class that wraps StreamReader and accumulates errors | |
| class Reader { | |
| public: | |
| Reader(const uint8_t* data, size_t length, Endian endian = Endian::Little) | |
| : stream_(data, length, endian), has_error_(false) {} | |
| // Check if any errors have occurred | |
| bool has_error() const { return has_error_; } | |
| // Get all accumulated errors | |
| const std::vector<std::string>& errors() const { return errors_; } | |
| // Get the most recent error | |
| std::string last_error() const { | |
| return errors_.empty() ? "" : errors_.back(); | |
| } | |
| // Get all errors as a single string | |
| std::string all_errors() const { | |
| std::string result; | |
| for (size_t i = 0; i < errors_.size(); i++) { | |
| if (i > 0) result += "\n"; | |
| result += errors_[i]; | |
| } | |
| return result; | |
| } | |
| // Clear error stack | |
| void clear_errors() { | |
| errors_.clear(); | |
| has_error_ = false; | |
| } | |
| // Read n bytes into destination buffer | |
| bool read(size_t n, uint8_t* dst) { | |
| if (!stream_.read(n, dst)) { | |
| add_error("Failed to read " + std::to_string(n) + " bytes at position " + | |
| std::to_string(stream_.tell())); | |
| return false; | |
| } | |
| return true; | |
| } | |
| // Read 1 byte | |
| bool read1(uint8_t* dst) { | |
| if (!stream_.read1(dst)) { | |
| add_error("Failed to read 1 byte at position " + | |
| std::to_string(stream_.tell())); | |
| return false; | |
| } | |
| return true; | |
| } | |
| // Read 2 bytes with endian swap | |
| bool read2(uint16_t* dst) { | |
| if (!stream_.read2(dst)) { | |
| add_error("Failed to read 2 bytes at position " + | |
| std::to_string(stream_.tell())); | |
| return false; | |
| } | |
| return true; | |
| } | |
| // Read 4 bytes with endian swap | |
| bool read4(uint32_t* dst) { | |
| if (!stream_.read4(dst)) { | |
| add_error("Failed to read 4 bytes at position " + | |
| std::to_string(stream_.tell())); | |
| return false; | |
| } | |
| return true; | |
| } | |
| // Read 8 bytes with endian swap | |
| bool read8(uint64_t* dst) { | |
| if (!stream_.read8(dst)) { | |
| add_error("Failed to read 8 bytes at position " + | |
| std::to_string(stream_.tell())); | |
| return false; | |
| } | |
| return true; | |
| } | |
| // Read a null-terminated string up to max_len bytes | |
| // Returns false if no null terminator found within max_len | |
| bool read_string(std::string* str, size_t max_len = 256) { | |
| if (!str) { | |
| add_error("Null pointer passed to read_string"); | |
| return false; | |
| } | |
| str->clear(); | |
| size_t start_pos = stream_.tell(); | |
| for (size_t i = 0; i < max_len; i++) { | |
| uint8_t c; | |
| if (!stream_.read1(&c)) { | |
| add_error("Failed to read string at position " + std::to_string(start_pos)); | |
| return false; | |
| } | |
| if (c == '\0') { | |
| return true; | |
| } | |
| str->push_back(static_cast<char>(c)); | |
| } | |
| add_error("String not null-terminated within " + std::to_string(max_len) + | |
| " bytes at position " + std::to_string(start_pos)); | |
| return false; | |
| } | |
| // Seek to absolute position | |
| bool seek(size_t pos) { | |
| if (!stream_.seek(pos)) { | |
| add_error("Failed to seek to position " + std::to_string(pos)); | |
| return false; | |
| } | |
| return true; | |
| } | |
| // Seek relative to current position | |
| bool seek_relative(int64_t offset) { | |
| size_t current = stream_.tell(); | |
| int64_t new_pos = static_cast<int64_t>(current) + offset; | |
| if (new_pos < 0) { | |
| add_error("Seek would move before start of stream"); | |
| return false; | |
| } | |
| return seek(static_cast<size_t>(new_pos)); | |
| } | |
| // Rewind to beginning | |
| void rewind() { | |
| stream_.rewind(); | |
| } | |
| // Get current position | |
| size_t tell() const { | |
| return stream_.tell(); | |
| } | |
| // Get remaining bytes | |
| size_t remaining() const { | |
| return stream_.remaining(); | |
| } | |
| // Check if at end | |
| bool eof() const { | |
| return stream_.eof(); | |
| } | |
| // Get total length | |
| size_t length() const { | |
| return stream_.length(); | |
| } | |
| // Add a custom error message | |
| void add_error(const std::string& msg) { | |
| errors_.push_back(msg); | |
| has_error_ = true; | |
| } | |
| // Get direct access to underlying StreamReader (use with caution) | |
| const StreamReader& stream() const { return stream_; } | |
| private: | |
| StreamReader stream_; | |
| std::vector<std::string> errors_; | |
| bool has_error_; | |
| }; | |
| } // namespace tinyexr | |
| #endif // TINYEXR_EXR_READER_HH_ |