Files
Goober5000 43b4724240 more graceful degradation of invalid unicode
If invalid UTF-8 is encountered, return the standard Unicode replacement character 0xFFFD.  When incrementing or decrementing, make a best effort to follow the code's instructions.  It might produce one or more � characters, but that's better than an Error and a hard crash.

Also move character constants to standardized locations.
2026-06-20 16:22:21 -04:00

194 lines
5.9 KiB
C++

#pragma once
#include "globalincs/pstypes.h"
#include "mod_table/mod_table.h"
#include <iterator>
#include <cinttypes>
#include <utf8.h>
#if !HAVE_CHAR32_T
// Older compilers don't have this as a built-in type so we define it for those
typedef std::uint_least32_t char32_t;
#endif
#if !HAVE_UNICODE_CHAR_LITERAL
#define UNICODE_CHAR(c) (char32_t) c
#else
/**
* @brief Compatibility macro for compilers which don't support U'' char literals.
*
* @warning This can only handle standard ASCII character values since older compilers still use standard character
* literals
*/
#define UNICODE_CHAR(c) U##c
#endif
namespace unicode {
/**
* @brief A standard unicode codepoint
*/
typedef char32_t codepoint_t;
/**
* @brief An invalid and ignorable character, equivalent to -1
*/
constexpr codepoint_t invalid_char = static_cast<codepoint_t>(-1);
/**
* @brief Substitute for malformed UTF-8 so that a bad decode can degrade gracefully
*/
constexpr codepoint_t replacement_char = 0xFFFD;
constexpr codepoint_t comment_char = static_cast<codepoint_t>(COMMENT_CHAR);
constexpr codepoint_t eoln = static_cast<codepoint_t>(EOLN);
constexpr codepoint_t carriage_return = static_cast<codepoint_t>(CARRIAGE_RETURN);
class text_iterator {
const char* current_byte = nullptr;
const char* range_end_byte = nullptr;
const char* range_start_byte = nullptr;
bool is_from_same_range(const text_iterator& other) const;
public:
explicit text_iterator(const char* current_byte, const char* range_start_byte, const char* range_end_byte = nullptr);
using difference_type = std::ptrdiff_t;
using value_type = codepoint_t;
using pointer = codepoint_t const *;
using reference = codepoint_t const &;
using iterator_category = std::bidirectional_iterator_tag;
const char* pos() const;
text_iterator& operator++();
text_iterator& operator--();
text_iterator operator++(int);
text_iterator operator--(int);
value_type operator*() const;
bool operator==(const text_iterator& rhs) const;
bool operator!=(const text_iterator& rhs) const;
bool operator<(const text_iterator& rhs) const;
bool operator>(const text_iterator& rhs) const;
bool operator<=(const text_iterator& rhs) const;
bool operator>=(const text_iterator& rhs) const;
text_iterator operator+(ptrdiff_t diff) const;
text_iterator operator-(ptrdiff_t diff) const;
};
/**
* @brief Represents a range of unicode codepoints that can be iterated over
*
* @note This class can be used in range based for loops
*/
class codepoint_range {
const char* start = nullptr;
const char* end_ptr = nullptr;
public:
/**
* @brief Creates a codepoint range based on an UTF-8 encoded string
* @param start The start of the encoded string
* @param end The end of the encoded string. May be @c nullptr in which case @c start is assumed to be null-terminated
*/
explicit codepoint_range(const char* start, const char* end = nullptr);
/**
* @brief Retrieves an iterator for the start of the codepoint range
* @return An iterator which is located at the start of the range
*/
text_iterator begin();
/**
* @brief Retrieves an iterator for the end of the codepoint range
* @return An iterator which is located at the end of the range
*/
text_iterator end();
};
/**
* @brief Computes the byte size the given codepoint would have if it were encoded using the standard encoding
*
* The standard encoding is UTF-8 in Unicode mode and ASCII otherwise.
*
* @param cp The codepoint to determine the size for.
* @return The number of bytes required for encoding this code point.
*/
size_t encoded_size(codepoint_t cp);
/**
* @brief Appends the given code point to the byte range specified by buffer.
*
* @c buffer can be a pointer to a character buffer or an output iterator. If the engine is in Unicode mode then the
* codepoint will be encoded using UTF-8. Otherwise the codepoint will be truncated to fit into the ASCII encoding.
*
* @tparam octet_iterator The type of the output sequence.
* @param cp The codepoint to encode
* @param buffer The buffer to write the encoded data to.
* @return The value of the iterator after appending all bytes to the sequence.
*/
template<typename octet_iterator>
octet_iterator encode(codepoint_t cp, octet_iterator buffer) {
if (Unicode_text_mode) {
try {
return utf8::append(cp, buffer);
} catch(const std::exception& e) {
Error(LOCATION, "Exception while encoding Unicode code point %" PRIu32 ": %s", (uint32_t)cp, e.what());
return buffer;
}
} else {
// In the legacy mode every code point is exactly one char
*(buffer++) = (char)cp;
return buffer;
}
}
/**
* @brief Counts the number of code points in the specified byte sequence
*
* @note This respects the Unicode mod setting and should be used where the text contains Unicode characters.
*
* @tparam octet_iterator The type of the byte sequence.
* @param start An iterator located at the start of the sequence
* @param end An iterator which signals the end of the sequence
* @return The number of codepoints found between @c start and @c end
*/
template<typename octet_iterator>
size_t num_codepoints(octet_iterator start, octet_iterator end) {
if (Unicode_text_mode) {
try {
return static_cast<size_t>(utf8::distance(start, end));
} catch(const std::exception& e) {
Error(LOCATION, "Exception while counting Unicode code points: %s", e.what());
return 0;
}
} else {
return static_cast<size_t>(std::distance(start, end));
}
}
template<typename octet_iterator>
void advance(octet_iterator& start, size_t n, octet_iterator end) {
if (Unicode_text_mode) {
utf8::advance(start, n, end);
} else {
start = std::min(start + n, end);
}
}
enum class Encoding { Encoding_current, Encoding_utf8, Encoding_iso8859_1 };
bool string_is_ascii_only(const char* str, size_t len);
const char* get_encoding_string(Encoding encoding);
bool convert_encoding(SCP_string& buffer, const char* src, Encoding encoding_src, Encoding encoding_dest = Encoding::Encoding_current);
}