Abfahrtsanzeiger Display Basic Library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
libmonoformat/cpp/src/monoformat_structured.cpp

3156 lines
109 KiB

#include "monoformat_structured.hpp"
#include "monoformat_fontreader.hpp"
#include "monoformat_bithelpers.hpp"
#include <iostream>
#include <stdexcept>
#include <format>
namespace monoformat {
static std::string base64Encode(std::span<std::byte const> buffer);
static std::vector<std::byte> base64Decode(std::string_view data);
NLOHMANN_JSON_SERIALIZE_ENUM(SectionType, {
{SectionType::AlwaysDrawn, "AlwaysDrawn"},
{SectionType::TimeBasedDrawn, "TimeBasedDrawn"},
{SectionType::MultiTimeBasedDrawn, "MultiTimeBasedDrawn"},
{SectionType::ExpiryDate, "ExpiryDate"},
{SectionType::CustomFont, "CustomFont"},
})
NLOHMANN_JSON_SERIALIZE_ENUM(ElementType, {
{ElementType::Image, "Image"},
{ElementType::Animation, "Animation"},
{ElementType::HScrollImage, "HScrollImage"},
{ElementType::VScrollImage, "VScrollImage"},
{ElementType::Line, "Line"},
{ElementType::Box, "Box"},
{ElementType::ClippedText, "ClippedText"},
{ElementType::HScrollText, "HScrollText"},
{ElementType::CurrentTime, "CurrentTime"},
})
NLOHMANN_JSON_SERIALIZE_ENUM(LineStyle, {
{LineStyle::Solid, "Solid"},
})
NLOHMANN_JSON_SERIALIZE_ENUM(FillPattern, {
{FillPattern::Solid, "Solid"},
})
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ScrollFlags, endless, invertDirection, padBefore, padAfter)
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(LineFlags, dark)
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(FillFlags, dark)
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(TextFlags, dark)
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(TimeDisplayFlags, use12h, showHours, showMinutes, showSeconds)
ImageElement::ImageElement(std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height)
: m_x{x}
, m_y{y}
, m_width{width}
, m_height{height}
{
std::size_t const bufferSize = (m_width * m_height + 8 - 1) / 8;\
m_buffer.resize(bufferSize);
}
std::uint16_t ImageElement::x() const noexcept {
return m_x;
}
std::uint16_t ImageElement::y() const noexcept {
return m_y;
}
std::uint16_t ImageElement::width() const noexcept {
return m_width;
}
std::uint16_t ImageElement::height() const noexcept {
return m_height;
}
std::span<std::byte> ImageElement::buffer() noexcept {
return m_buffer;
}
std::span<std::byte const> ImageElement::buffer() const noexcept {
return m_buffer;
}
MemoryOneBitBuffer ImageElement::image() noexcept {
return MemoryOneBitBuffer{m_buffer, m_width, m_height};
}
ConstMemoryOneBitBuffer ImageElement::image() const noexcept {
return ConstMemoryOneBitBuffer{m_buffer, m_width, m_height};
}
void ImageElement::updateBuffer(std::span<std::byte const> newBufferData) {
std::size_t n = std::min(m_buffer.size(), newBufferData.size());
std::copy(newBufferData.begin(), newBufferData.begin() + n, m_buffer.begin());
std::fill(m_buffer.begin() + n, m_buffer.end(), std::byte{});
}
ImageElement::~ImageElement() {
}
ElementType ImageElement::elementType() const {
return ElementType::Image;
}
std::uint32_t ImageElement::minimumFormatVersion() const {
return 1;
}
std::size_t ImageElement::serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const {
std::ignore = formatVersion;
std::size_t pos = 0;
pos = writeU16LE(target, pos, static_cast<std::uint16_t>(ElementType::Image));
pos = writeU16LE(target, pos, m_x);
pos = writeU16LE(target, pos, m_y);
pos = writeU16LE(target, pos, m_width);
pos = writeU16LE(target, pos, m_height);
pos = writeU16LE(target, pos, 0);
pos = writeBuffer(target, pos, m_buffer);
return alignNextWrite(target, pos, 4);
}
nlohmann::json ImageElement::serializeJSON(std::uint32_t formatVersion) const {
std::ignore = formatVersion;
nlohmann::json result;
result["type"] = "Image";
result["x"] = m_x;
result["y"] = m_y;
result["width"] = m_width;
result["height"] = m_height;
result["image"] = nlohmann::json::array();
auto ownImage = image();
for (std::uint16_t dy = 0; dy < m_height; ++dy) {
for (std::uint16_t dx = 0; dx < m_width; ++dx) {
result["image"].push_back(ownImage.isPixelSet(dx, dy) ? 1 : 0);
}
}
return result;
}
void ImageElement::drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span<CustomFontSection const*> customFonts) {
std::ignore = animationTick;
std::ignore = currentTimestamp;
std::ignore = customFonts;
auto ownImage = image();
for (std::uint16_t dy = 0; dy < m_height; ++dy) {
for (std::uint16_t dx = 0; dx < m_width; ++dx) {
imageBuffer->setPixel(m_x + dx, m_y + dy, ownImage.isPixelSet(dx, dy));
}
}
}
std::expected<std::unique_ptr<ImageElement>, ParseError> ImageElement::parse(std::span<std::byte const>& buffer, std::uint32_t formatVersion) {
std::ignore = formatVersion;
auto type = readU16LE(buffer);
if (!type) {
return std::unexpected(type.error());
}
if (*type != static_cast<std::uint16_t>(ElementType::Image)) {
return std::unexpected(ParseError::InvalidValue);
}
auto x = readU16LE(buffer);
if (!x) {
return std::unexpected(x.error());
}
auto y = readU16LE(buffer);
if (!y) {
return std::unexpected(y.error());
}
auto width = readU16LE(buffer);
if (!width) {
return std::unexpected(width.error());
}
auto height = readU16LE(buffer);
if (!height) {
return std::unexpected(height.error());
}
auto reserved_ = readU16LE(buffer);
if (!reserved_) {
return std::unexpected(reserved_.error());
}
std::size_t imageSize = ((*width) * (*height) + 8 - 1) / 8;
auto imageData = readBuffer(buffer, imageSize);
if (!imageData) {
return std::unexpected(reserved_.error());
}
std::size_t rounded = (imageSize + 4 - 1) / 4 * 4;
if (imageSize < rounded) {
buffer = buffer.subspan(rounded - imageSize);
}
auto result = std::make_unique<ImageElement>(*x, *y, *width, *height);
result->updateBuffer(*imageData);
return result;
}
std::unique_ptr<ImageElement> ImageElement::parseJSON(nlohmann::json const& j, std::uint32_t formatVersion) {
std::ignore = formatVersion;
std::uint16_t x = j.at("x");
std::uint16_t y = j.at("y");
std::uint16_t width = j.at("width");
std::uint16_t height = j.at("height");
std::vector<int> pixels = j.at("image");
if (pixels.size() != width * height) {
throw std::runtime_error(std::format("Error unserializing an Image element: the width, {:d}, and height {:d}, of the image don't match the number of pixels in the image data, {:d}, expected {:d} instead", width, height, pixels.size(), width * height));
}
auto result = std::make_unique<ImageElement>(x, y, width, height);
auto image = result->image();
for (std::uint16_t dy = 0; dy < height; ++dy) {
std::size_t base = static_cast<std::size_t>(dy) * width;
for (std::uint16_t dx = 0; dx < width; ++dx) {
image.setPixel(dx, dy, !!pixels[base + dx]);
}
}
return result;
}
AnimationElement::AnimationElement(std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height, std::uint16_t numberOfFrames, std::uint16_t updateInterval)
: m_x{x}
, m_y{y}
, m_width{width}
, m_height{height}
, m_numberOfFrames{numberOfFrames}
, m_updateInterval{updateInterval}
{
std::size_t const imageBufferSize = (m_width * m_height + 8 - 1) / 8;
m_buffer.resize(imageBufferSize * numberOfFrames);
}
std::uint16_t AnimationElement::x() const noexcept {
return m_x;
}
std::uint16_t AnimationElement::y() const noexcept {
return m_y;
}
std::uint16_t AnimationElement::width() const noexcept {
return m_width;
}
std::uint16_t AnimationElement::height() const noexcept {
return m_height;
}
std::uint16_t AnimationElement::numberOfFrames() const noexcept {
return m_numberOfFrames;
}
std::uint16_t AnimationElement::updateInterval() const noexcept {
return m_updateInterval;
}
std::span<std::byte> AnimationElement::buffers() noexcept {
return m_buffer;
}
std::span<std::byte const> AnimationElement::buffers() const noexcept {
return m_buffer;
}
std::span<std::byte> AnimationElement::buffer(std::uint16_t frameIndex) noexcept {
if (frameIndex >= m_numberOfFrames) {
return {};
}
std::size_t const imageBufferSize = (m_width * m_height + 8 - 1) / 8;
return std::span{m_buffer}.subspan(frameIndex * imageBufferSize, imageBufferSize);
}
std::span<std::byte const> AnimationElement::buffer(std::uint16_t frameIndex) const noexcept {
if (frameIndex >= m_numberOfFrames) {
return {};
}
std::size_t const imageBufferSize = (m_width * m_height + 8 - 1) / 8;
return std::span{m_buffer}.subspan(frameIndex * imageBufferSize, imageBufferSize);
}
MemoryOneBitBuffer AnimationElement::image(std::uint16_t frameIndex) noexcept {
return MemoryOneBitBuffer{buffer(frameIndex), m_width, m_height};
}
ConstMemoryOneBitBuffer AnimationElement::image(std::uint16_t frameIndex) const noexcept {
return ConstMemoryOneBitBuffer{buffer(frameIndex), m_width, m_height};
}
void AnimationElement::updateBuffer(std::span<std::byte const> newBufferData) {
std::size_t n = std::min(m_buffer.size(), newBufferData.size());
std::copy(newBufferData.begin(), newBufferData.begin() + n, m_buffer.begin());
std::fill(m_buffer.begin() + n, m_buffer.end(), std::byte{});
}
AnimationElement::~AnimationElement() {
}
ElementType AnimationElement::elementType() const {
return ElementType::Animation;
}
std::uint32_t AnimationElement::minimumFormatVersion() const {
return 1;
}
std::size_t AnimationElement::serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const {
std::ignore = formatVersion;
std::size_t pos = 0;
pos = writeU16LE(target, pos, static_cast<std::uint16_t>(ElementType::Animation));
pos = writeU16LE(target, pos, m_x);
pos = writeU16LE(target, pos, m_y);
pos = writeU16LE(target, pos, m_width);
pos = writeU16LE(target, pos, m_height);
pos = writeU16LE(target, pos, m_numberOfFrames);
pos = writeU16LE(target, pos, m_updateInterval);
pos = writeU16LE(target, pos, 0);
pos = writeBuffer(target, pos, m_buffer);
return alignNextWrite(target, pos, 4);
}
nlohmann::json AnimationElement::serializeJSON(std::uint32_t formatVersion) const {
std::ignore = formatVersion;
nlohmann::json result;
result["type"] = "Animation";
result["x"] = m_x;
result["y"] = m_y;
result["width"] = m_width;
result["height"] = m_height;
result["updateInterval"] = m_updateInterval;
result["frames"] = nlohmann::json::array();
for (std::uint16_t f = 0; f < m_numberOfFrames; ++f) {
nlohmann::json frame = nlohmann::json::array();
auto ownImage = image(f);
for (std::uint16_t dy = 0; dy < m_height; ++dy) {
for (std::uint16_t dx = 0; dx < m_width; ++dx) {
frame.push_back(ownImage.isPixelSet(dx, dy) ? 1 : 0);
}
}
result["frames"].push_back(frame);
}
return result;
}
void AnimationElement::drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span<CustomFontSection const*> customFonts) {
std::ignore = currentTimestamp;
std::ignore = customFonts;
if (m_numberOfFrames == 0) {
return;
}
std::size_t const tick = animationTick / (static_cast<std::size_t>(m_updateInterval) + 1);
std::size_t const frameIndex = tick % m_numberOfFrames;
auto ownImage = image(frameIndex);
for (std::uint16_t dy = 0; dy < m_height; ++dy) {
for (std::uint16_t dx = 0; dx < m_width; ++dx) {
imageBuffer->setPixel(m_x + dx, m_y + dy, ownImage.isPixelSet(dx, dy));
}
}
}
std::expected<std::unique_ptr<AnimationElement>, ParseError> AnimationElement::parse(std::span<std::byte const>& buffer, std::uint32_t formatVersion) {
std::ignore = formatVersion;
auto type = readU16LE(buffer);
if (!type) {
return std::unexpected(type.error());
}
if (*type != static_cast<std::uint16_t>(ElementType::Animation)) {
return std::unexpected(ParseError::InvalidValue);
}
auto x = readU16LE(buffer);
if (!x) {
return std::unexpected(x.error());
}
auto y = readU16LE(buffer);
if (!y) {
return std::unexpected(y.error());
}
auto width = readU16LE(buffer);
if (!width) {
return std::unexpected(width.error());
}
auto height = readU16LE(buffer);
if (!height) {
return std::unexpected(height.error());
}
auto numberOfFrames = readU16LE(buffer);
if (!numberOfFrames) {
return std::unexpected(numberOfFrames.error());
}
auto updateInterval = readU16LE(buffer);
if (!updateInterval) {
return std::unexpected(updateInterval.error());
}
auto reserved_ = readU16LE(buffer);
if (!reserved_) {
return std::unexpected(reserved_.error());
}
std::size_t imageSize = ((*width) * (*height) + 8 - 1) / 8;
auto imageData = readBuffer(buffer, imageSize);
if (!imageData) {
return std::unexpected(reserved_.error());
}
std::size_t rounded = (imageSize + 4 - 1) / 4 * 4;
if (imageSize < rounded) {
buffer = buffer.subspan(rounded - imageSize);
}
auto result = std::make_unique<AnimationElement>(*x, *y, *width, *height, *numberOfFrames, *updateInterval);
result->updateBuffer(*imageData);
return result;
}
std::unique_ptr<AnimationElement> AnimationElement::parseJSON(nlohmann::json const& j, std::uint32_t formatVersion) {
std::ignore = formatVersion;
std::uint16_t x = j.at("x");
std::uint16_t y = j.at("y");
std::uint16_t width = j.at("width");
std::uint16_t height = j.at("height");
std::uint16_t updateInterval = j.at("updateInterval");
std::vector<std::vector<int>> frames = j.at("frames");
if (frames.size() > std::numeric_limits<std::uint16_t>::max()) {
throw std::runtime_error(std::format("Error unserializing an Animation element: the number of frames, {:d}, is too large", frames.size()));
}
for (std::size_t i = 0; i < frames.size(); ++i) {
if (frames[i].size() != width * height) {
throw std::runtime_error(std::format("Error unserializing an Animation element: in frame {:d}, the width, {:d}, and height {:d}, of the image don't match the number of pixels in the image data, {:d}, expected {:d} instead", i, width, height, frames[i].size(), width * height));
}
}
auto result = std::make_unique<AnimationElement>(x, y, width, height, static_cast<std::uint16_t>(frames.size()), updateInterval);
for (std::size_t i = 0; i < frames.size(); ++i) {
auto image = result->image(i);
for (std::uint16_t dy = 0; dy < height; ++dy) {
std::size_t base = static_cast<std::size_t>(dy) * width;
for (std::uint16_t dx = 0; dx < width; ++dx) {
image.setPixel(dx, dy, !!frames[i][base + dx]);
}
}
}
return result;
}
HScrollImageElement::HScrollImageElement(std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height, std::uint16_t contentWidth, ScrollFlags flags, std::uint8_t scrollSpeed)
: m_x{x}
, m_y{y}
, m_width{width}
, m_height{height}
, m_contentWidth{contentWidth}
, m_flags{flags}
, m_scrollSpeed{scrollSpeed}
{
std::size_t const imageBufferSize = (m_contentWidth * m_height + 8 - 1) / 8;
m_buffer.resize(imageBufferSize);
}
std::uint16_t HScrollImageElement::x() const noexcept {
return m_x;
}
std::uint16_t HScrollImageElement::y() const noexcept {
return m_y;
}
std::uint16_t HScrollImageElement::width() const noexcept {
return m_width;
}
std::uint16_t HScrollImageElement::height() const noexcept {
return m_height;
}
std::uint16_t HScrollImageElement::contentWidth() const noexcept {
return m_contentWidth;
}
ScrollFlags HScrollImageElement::flags() const noexcept {
return m_flags;
}
std::uint8_t HScrollImageElement::scrollSpeed() const noexcept {
return m_scrollSpeed;
}
std::span<std::byte> HScrollImageElement::buffer() noexcept {
return std::span{m_buffer};
}
std::span<std::byte const> HScrollImageElement::buffer() const noexcept {
return std::span{m_buffer};
}
MemoryOneBitBuffer HScrollImageElement::image() noexcept {
return MemoryOneBitBuffer{m_buffer, m_contentWidth, m_height};
}
ConstMemoryOneBitBuffer HScrollImageElement::image() const noexcept {
return ConstMemoryOneBitBuffer{m_buffer, m_contentWidth, m_height};
}
void HScrollImageElement::updateBuffer(std::span<std::byte const> newBufferData) {
std::size_t n = std::min(m_buffer.size(), newBufferData.size());
std::copy(newBufferData.begin(), newBufferData.begin() + n, m_buffer.begin());
std::fill(m_buffer.begin() + n, m_buffer.end(), std::byte{});
}
HScrollImageElement::~HScrollImageElement() {
}
ElementType HScrollImageElement::elementType() const {
return ElementType::HScrollImage;
}
std::uint32_t HScrollImageElement::minimumFormatVersion() const {
return 1;
}
std::size_t HScrollImageElement::serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const {
std::ignore = formatVersion;
std::size_t pos = 0;
pos = writeU16LE(target, pos, static_cast<std::uint16_t>(ElementType::HScrollImage));
pos = writeU16LE(target, pos, m_x);
pos = writeU16LE(target, pos, m_y);
pos = writeU16LE(target, pos, m_width);
pos = writeU16LE(target, pos, m_height);
pos = writeU16LE(target, pos, m_contentWidth);
pos = writeU8LE(target, pos, static_cast<std::uint8_t>(m_flags));
pos = writeU8LE(target, pos, m_scrollSpeed);
pos = writeU16LE(target, pos, 0);
pos = writeBuffer(target, pos, m_buffer);
return alignNextWrite(target, pos, 4);
}
nlohmann::json HScrollImageElement::serializeJSON(std::uint32_t formatVersion) const {
std::ignore = formatVersion;
nlohmann::json result;
result["type"] = "HScrollImage";
result["x"] = m_x;
result["y"] = m_y;
result["width"] = m_width;
result["height"] = m_height;
result["contentWidth"] = m_contentWidth;
result["flags"] = m_flags;
result["scrollSpeed"] = int(m_scrollSpeed);
result["image"] = nlohmann::json::array();
auto ownImage = image();
for (std::uint16_t dy = 0; dy < m_height; ++dy) {
for (std::uint16_t dx = 0; dx < m_contentWidth; ++dx) {
result["image"].push_back(ownImage.isPixelSet(dx, dy) ? 1 : 0);
}
}
return result;
}
void HScrollImageElement::drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span<CustomFontSection const*> customFonts) {
std::ignore = currentTimestamp;
std::ignore = customFonts;
auto ownImage = image();
ClippedImage target{imageBuffer, m_x, m_y, m_width, m_height};
if (m_contentWidth == 0) {
return;
}
bool restarting = !m_flags.endless;
bool invert = m_flags.invertDirection;
bool padBefore = m_flags.padBefore;
bool padAfter = m_flags.padAfter;
if (!padBefore && !padAfter && m_contentWidth < m_width) {
std::int16_t offset = invert ? (m_width - m_contentWidth) : 0;
for (std::size_t dy = 0; dy < m_height; ++dy) {
for (std::size_t dx = 0; dx < m_contentWidth; ++dx) {
target.setPixel(dx + offset, dy, ownImage.isPixelSet(dx, dy));
}
}
return;
}
std::int32_t totalSize = m_contentWidth;
if (padBefore) {
totalSize += m_width;
}
if (padAfter) {
totalSize += m_width;
}
// FIXME: handle overflow here...
std::int32_t offset = static_cast<std::int32_t>(animationTick * (m_scrollSpeed + 1) / 8) % totalSize;
if (invert) {
offset = totalSize - offset - 1;
}
if (restarting) {
offset = std::min(offset, totalSize - m_width);
}
std::int32_t partOffset = 0;
if (padBefore) {
for (std::int32_t dy = 0; dy < m_height; ++dy) {
for (std::int32_t dx = 0; dx < m_width; ++dx) {
std::int32_t ddx = dx + partOffset;
if ((ddx - offset) < 0) {
continue;
}
target.setPixel(ddx - offset, dy, false);
}
}
partOffset += m_width;
}
for (std::int32_t dy = 0; dy < m_height; ++dy) {
for (std::int32_t dx = 0; dx < m_contentWidth; ++dx) {
std::int32_t ddx = dx + partOffset;
if ((ddx - offset) < 0) {
continue;
}
target.setPixel(ddx - offset, dy, ownImage.isPixelSet(dx, dy));
}
partOffset += m_contentWidth;
}
if (padAfter) {
for (std::int32_t dy = 0; dy < m_height; ++dy) {
for (std::int32_t dx = 0; dx < m_width; ++dx) {
std::int32_t ddx = dx + partOffset;
if ((ddx - offset) < 0) {
continue;
}
target.setPixel(ddx - offset, dy, false);
}
}
partOffset += m_width;
}
if (!restarting) {
if (padBefore) {
for (std::int32_t dy = 0; dy < m_height; ++dy) {
for (std::int32_t dx = 0; dx < m_width; ++dx) {
std::int32_t ddx = dx + partOffset;
if ((ddx - offset) < 0) {
continue;
}
target.setPixel(ddx - offset, dy, false);
}
}
partOffset += m_width;
}
for (std::int32_t dy = 0; dy < m_height; ++dy) {
for (std::int32_t dx = 0; dx < m_contentWidth; ++dx) {
std::int32_t ddx = dx + partOffset;
if ((ddx - offset) < 0) {
continue;
}
target.setPixel(ddx - offset, dy, ownImage.isPixelSet(dx, dy));
}
}
partOffset += m_contentWidth;
}
}
std::expected<std::unique_ptr<HScrollImageElement>, ParseError> HScrollImageElement::parse(std::span<std::byte const>& buffer, std::uint32_t formatVersion) {
std::ignore = formatVersion;
auto type = readU16LE(buffer);
if (!type) {
return std::unexpected(type.error());
}
if (*type != static_cast<std::uint16_t>(ElementType::HScrollImage)) {
return std::unexpected(ParseError::InvalidValue);
}
auto x = readU16LE(buffer);
if (!x) {
return std::unexpected(x.error());
}
auto y = readU16LE(buffer);
if (!y) {
return std::unexpected(y.error());
}
auto width = readU16LE(buffer);
if (!width) {
return std::unexpected(width.error());
}
auto height = readU16LE(buffer);
if (!height) {
return std::unexpected(height.error());
}
auto contentWidth = readU16LE(buffer);
if (!contentWidth) {
return std::unexpected(contentWidth.error());
}
auto flags = readU8LE(buffer);
if (!flags) {
return std::unexpected(flags.error());
}
auto scrollSpeed = readU8LE(buffer);
if (!scrollSpeed) {
return std::unexpected(scrollSpeed.error());
}
auto reserved_ = readU16LE(buffer);
if (!reserved_) {
return std::unexpected(reserved_.error());
}
std::size_t imageSize = ((*contentWidth) * (*height) + 8 - 1) / 8;
auto imageData = readBuffer(buffer, imageSize);
if (!imageData) {
return std::unexpected(reserved_.error());
}
std::size_t rounded = (imageSize + 4 - 1) / 4 * 4;
if (imageSize < rounded) {
buffer = buffer.subspan(rounded - imageSize);
}
auto result = std::make_unique<HScrollImageElement>(*x, *y, *width, *height, *contentWidth, ScrollFlags{*flags}, *scrollSpeed);
result->updateBuffer(*imageData);
return result;
}
std::unique_ptr<HScrollImageElement> HScrollImageElement::parseJSON(nlohmann::json const& j, std::uint32_t formatVersion) {
std::ignore = formatVersion;
std::uint16_t x = j.at("x");
std::uint16_t y = j.at("y");
std::uint16_t width = j.at("width");
std::uint16_t height = j.at("height");
std::uint16_t contentWidth = j.at("contentWidth");
ScrollFlags flags = j.at("flags");
int scrollSpeed = j.at("scrollSpeed");
std::vector<int> pixels = j.at("image");
if (scrollSpeed < 0 || scrollSpeed > 255) {
throw std::runtime_error(std::format("Error unserializing an HScrollImage element: invalid scroll speed {:d} specified", scrollSpeed));
}
if (pixels.size() != contentWidth * height) {
throw std::runtime_error(std::format("Error unserializing an HScrollImage element: the content width, {:d}, and height {:d}, of the image don't match the number of pixels in the image data, {:d}, expected {:d} instead", contentWidth, height, pixels.size(), contentWidth * height));
}
auto result = std::make_unique<HScrollImageElement>(x, y, width, height, contentWidth, flags, static_cast<std::uint8_t>(scrollSpeed));
auto image = result->image();
for (std::uint16_t dy = 0; dy < height; ++dy) {
std::size_t base = static_cast<std::size_t>(dy) * contentWidth;
for (std::uint16_t dx = 0; dx < contentWidth; ++dx) {
image.setPixel(dx, dy, !!pixels[base + dx]);
}
}
return result;
}
VScrollImageElement::VScrollImageElement(std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height, std::uint16_t contentHeight, ScrollFlags flags, std::uint8_t scrollSpeed)
: m_x{x}
, m_y{y}
, m_width{width}
, m_height{height}
, m_contentHeight{contentHeight}
, m_flags{flags}
, m_scrollSpeed{scrollSpeed}
{
std::size_t const imageBufferSize = (m_width * m_contentHeight + 8 - 1) / 8;
m_buffer.resize(imageBufferSize);
}
std::uint16_t VScrollImageElement::x() const noexcept {
return m_x;
}
std::uint16_t VScrollImageElement::y() const noexcept {
return m_y;
}
std::uint16_t VScrollImageElement::width() const noexcept {
return m_width;
}
std::uint16_t VScrollImageElement::height() const noexcept {
return m_height;
}
std::uint16_t VScrollImageElement::contentHeight() const noexcept {
return m_contentHeight;
}
ScrollFlags VScrollImageElement::flags() const noexcept {
return m_flags;
}
std::uint8_t VScrollImageElement::scrollSpeed() const noexcept {
return m_scrollSpeed;
}
std::span<std::byte> VScrollImageElement::buffer() noexcept {
return std::span{m_buffer};
}
std::span<std::byte const> VScrollImageElement::buffer() const noexcept {
return std::span{m_buffer};
}
MemoryOneBitBuffer VScrollImageElement::image() noexcept {
return MemoryOneBitBuffer{m_buffer, m_width, m_contentHeight};
}
ConstMemoryOneBitBuffer VScrollImageElement::image() const noexcept {
return ConstMemoryOneBitBuffer{m_buffer, m_width, m_contentHeight};
}
void VScrollImageElement::updateBuffer(std::span<std::byte const> newBufferData) {
std::size_t n = std::min(m_buffer.size(), newBufferData.size());
std::copy(newBufferData.begin(), newBufferData.begin() + n, m_buffer.begin());
std::fill(m_buffer.begin() + n, m_buffer.end(), std::byte{});
}
VScrollImageElement::~VScrollImageElement() {
}
ElementType VScrollImageElement::elementType() const {
return ElementType::VScrollImage;
}
std::uint32_t VScrollImageElement::minimumFormatVersion() const {
return 1;
}
std::size_t VScrollImageElement::serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const {
std::ignore = formatVersion;
std::size_t pos = 0;
pos = writeU16LE(target, pos, static_cast<std::uint16_t>(ElementType::VScrollImage));
pos = writeU16LE(target, pos, m_x);
pos = writeU16LE(target, pos, m_y);
pos = writeU16LE(target, pos, m_width);
pos = writeU16LE(target, pos, m_height);
pos = writeU16LE(target, pos, m_contentHeight);
pos = writeU8LE(target, pos, static_cast<std::uint8_t>(m_flags));
pos = writeU8LE(target, pos, m_scrollSpeed);
pos = writeU16LE(target, pos, 0);
pos = writeBuffer(target, pos, m_buffer);
return alignNextWrite(target, pos, 4);
}
nlohmann::json VScrollImageElement::serializeJSON(std::uint32_t formatVersion) const {
std::ignore = formatVersion;
nlohmann::json result;
result["type"] = "VScrollImage";
result["x"] = m_x;
result["y"] = m_y;
result["width"] = m_width;
result["height"] = m_height;
result["contentHeight"] = m_contentHeight;
result["flags"] = m_flags;
result["scrollSpeed"] = int(m_scrollSpeed);
result["image"] = nlohmann::json::array();
auto ownImage = image();
for (std::uint16_t dy = 0; dy < m_contentHeight; ++dy) {
for (std::uint16_t dx = 0; dx < m_width; ++dx) {
result["image"].push_back(ownImage.isPixelSet(dx, dy) ? 1 : 0);
}
}
return result;
}
void VScrollImageElement::drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span<CustomFontSection const*> customFonts) {
std::ignore = currentTimestamp;
std::ignore = customFonts;
auto ownImage = image();
ClippedImage target{imageBuffer, m_x, m_y, m_width, m_height};
if (m_contentHeight == 0) {
return;
}
bool restarting = !m_flags.endless;
bool invert = m_flags.invertDirection;
bool padBefore = m_flags.padBefore;
bool padAfter = m_flags.padAfter;
if (!padBefore && !padAfter && m_contentHeight < m_height) {
std::int16_t offset = invert ? (m_height - m_contentHeight) : 0;
for (std::size_t dy = 0; dy < m_contentHeight; ++dy) {
for (std::size_t dx = 0; dx < m_width; ++dx) {
target.setPixel(dx, dy + offset, ownImage.isPixelSet(dx, dy));
}
}
return;
}
std::int32_t totalSize = m_contentHeight;
if (padBefore) {
totalSize += m_height;
}
if (padAfter) {
totalSize += m_height;
}
// FIXME: handle overflow here...
std::int32_t offset = static_cast<std::int32_t>(animationTick * (m_scrollSpeed + 1) / 8) % totalSize;
if (invert) {
offset = totalSize - offset - 1;
}
if (restarting) {
offset = std::min(offset, totalSize - m_height);
}
std::int32_t partOffset = 0;
if (padBefore) {
for (std::int32_t dy = 0; dy < m_height; ++dy) {
std::int32_t ddy = dy + partOffset;
if ((ddy - offset) < 0) {
continue;
}
for (std::int32_t dx = 0; dx < m_width; ++dx) {
target.setPixel(dx, ddy - offset, false);
}
}
partOffset += m_height;
}
for (std::int32_t dy = 0; dy < m_contentHeight; ++dy) {
std::int32_t ddy = dy + partOffset;
if ((ddy - offset) < 0) {
continue;
}
for (std::int32_t dx = 0; dx < m_width; ++dx) {
target.setPixel(dx, ddy - offset, ownImage.isPixelSet(dx, dy));
}
partOffset += m_contentHeight;
}
if (padAfter) {
for (std::int32_t dy = 0; dy < m_height; ++dy) {
std::int32_t ddy = dy + partOffset;
if ((ddy - offset) < 0) {
continue;
}
for (std::int32_t dx = 0; dx < m_width; ++dx) {
target.setPixel(dx, ddy - offset, false);
}
}
partOffset += m_height;
}
if (!restarting) {
if (padBefore) {
for (std::int32_t dy = 0; dy < m_height; ++dy) {
std::int32_t ddy = dy + partOffset;
if ((ddy - offset) < 0) {
continue;
}
for (std::int32_t dx = 0; dx < m_width; ++dx) {
target.setPixel(dx, ddy - offset, false);
}
}
partOffset += m_height;
}
for (std::int32_t dy = 0; dy < m_contentHeight; ++dy) {
std::int32_t ddy = dy + partOffset;
if ((ddy - offset) < 0) {
continue;
}
for (std::int32_t dx = 0; dx < m_width; ++dx) {
target.setPixel(dx, ddy - offset, ownImage.isPixelSet(dx, dy));
}
}
partOffset += m_contentHeight;
}
}
std::expected<std::unique_ptr<VScrollImageElement>, ParseError> VScrollImageElement::parse(std::span<std::byte const>& buffer, std::uint32_t formatVersion) {
std::ignore = formatVersion;
auto type = readU16LE(buffer);
if (!type) {
return std::unexpected(type.error());
}
if (*type != static_cast<std::uint16_t>(ElementType::VScrollImage)) {
return std::unexpected(ParseError::InvalidValue);
}
auto x = readU16LE(buffer);
if (!x) {
return std::unexpected(x.error());
}
auto y = readU16LE(buffer);
if (!y) {
return std::unexpected(y.error());
}
auto width = readU16LE(buffer);
if (!width) {
return std::unexpected(width.error());
}
auto height = readU16LE(buffer);
if (!height) {
return std::unexpected(height.error());
}
auto contentHeight = readU16LE(buffer);
if (!contentHeight) {
return std::unexpected(contentHeight.error());
}
auto flags = readU8LE(buffer);
if (!flags) {
return std::unexpected(flags.error());
}
auto scrollSpeed = readU8LE(buffer);
if (!scrollSpeed) {
return std::unexpected(scrollSpeed.error());
}
auto reserved_ = readU16LE(buffer);
if (!reserved_) {
return std::unexpected(reserved_.error());
}
std::size_t imageSize = ((*width) * (*contentHeight) + 8 - 1) / 8;
auto imageData = readBuffer(buffer, imageSize);
if (!imageData) {
return std::unexpected(reserved_.error());
}
std::size_t rounded = (imageSize + 4 - 1) / 4 * 4;
if (imageSize < rounded) {
buffer = buffer.subspan(rounded - imageSize);
}
auto result = std::make_unique<VScrollImageElement>(*x, *y, *width, *height, *contentHeight, ScrollFlags{*flags}, *scrollSpeed);
result->updateBuffer(*imageData);
return result;
}
std::unique_ptr<VScrollImageElement> VScrollImageElement::parseJSON(nlohmann::json const& j, std::uint32_t formatVersion) {
std::ignore = formatVersion;
std::uint16_t x = j.at("x");
std::uint16_t y = j.at("y");
std::uint16_t width = j.at("width");
std::uint16_t height = j.at("height");
std::uint16_t contentHeight = j.at("contentHeight");
ScrollFlags flags = j.at("flags");
int scrollSpeed = j.at("scrollSpeed");
std::vector<int> pixels = j.at("image");
if (scrollSpeed < 0 || scrollSpeed > 255) {
throw std::runtime_error(std::format("Error unserializing an VScrollImage element: invalid scroll speed {:d} specified", scrollSpeed));
}
if (pixels.size() != width * contentHeight) {
throw std::runtime_error(std::format("Error unserializing an VScrollImage element: the width, {:d}, and content height {:d}, of the image don't match the number of pixels in the image data, {:d}, expected {:d} instead", width, contentHeight, pixels.size(), width * contentHeight));
}
auto result = std::make_unique<VScrollImageElement>(x, y, width, height, contentHeight, flags, static_cast<std::uint8_t>(scrollSpeed));
auto image = result->image();
for (std::uint16_t dy = 0; dy < contentHeight; ++dy) {
std::size_t base = static_cast<std::size_t>(dy) * width;
for (std::uint16_t dx = 0; dx < width; ++dx) {
image.setPixel(dx, dy, !!pixels[base + dx]);
}
}
return result;
}
LineElement::LineElement(std::uint16_t originX, std::uint16_t originY, std::uint16_t targetX, std::uint16_t targetY, LineStyle lineStyle, LineFlags flags)
: m_originX{originX}
, m_originY{originY}
, m_targetX{targetX}
, m_targetY{targetY}
, m_lineStyle{lineStyle}
, m_flags{flags}
{
}
std::uint16_t LineElement::originX() const noexcept {
return m_originX;
}
std::uint16_t LineElement::originY() const noexcept {
return m_originY;
}
std::uint16_t LineElement::targetX() const noexcept {
return m_targetX;
}
std::uint16_t LineElement::targetY() const noexcept {
return m_targetY;
}
LineStyle LineElement::lineStyle() const noexcept {
return m_lineStyle;
}
LineFlags LineElement::flags() const noexcept {
return m_flags;
}
LineElement::~LineElement() {
}
ElementType LineElement::elementType() const {
return ElementType::Line;
}
std::uint32_t LineElement::minimumFormatVersion() const {
if (static_cast<std::uint8_t>(m_flags) != 0) {
return 2;
} else {
return 1;
}
}
std::size_t LineElement::serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const {
std::ignore = formatVersion;
std::size_t pos = 0;
pos = writeU16LE(target, pos, static_cast<std::uint16_t>(ElementType::Line));
pos = writeU16LE(target, pos, m_originX);
pos = writeU16LE(target, pos, m_originY);
pos = writeU16LE(target, pos, m_targetX);
pos = writeU16LE(target, pos, m_targetY);
pos = writeU8LE(target, pos, static_cast<std::uint8_t>(m_lineStyle));
pos = writeU8LE(target, pos, static_cast<std::uint8_t>(m_flags));
return alignNextWrite(target, pos, 4);
}
nlohmann::json LineElement::serializeJSON(std::uint32_t formatVersion) const {
std::ignore = formatVersion;
nlohmann::json result;
result["type"] = "Line";
result["originX"] = m_originX;
result["originY"] = m_originY;
result["targetX"] = m_targetX;
result["targetY"] = m_targetY;
result["lineStyle"] = m_lineStyle;
result["flags"] = m_flags;
return result;
}
void LineElement::drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span<CustomFontSection const*> customFonts) {
std::ignore = animationTick;
std::ignore = currentTimestamp;
std::ignore = customFonts;
std::int32_t dx = static_cast<std::int32_t>(m_targetX) - static_cast<std::int32_t>(m_originX);
std::int32_t dy = static_cast<std::int32_t>(m_targetY) - static_cast<std::int32_t>(m_originY);
bool value = true;
if (dx == 0) {
for (std::int32_t i = 0; i <= dy; ++i) {
imageBuffer->setPixel(m_originX, m_originY + i, value);
}
} else if (dy == 0) {
for (std::int32_t i = 0; i <= dx; ++i) {
imageBuffer->setPixel(m_originX + i, m_originY, value);
}
} else {
for (std::int32_t i = 0; i <= dx; ++i) {
std::int32_t x = m_originX + i;
std::int32_t y = i * dy / dx + m_originY;
imageBuffer->setPixel(static_cast<std::uint16_t>(x), static_cast<std::uint16_t>(y), value);
}
}
}
std::expected<std::unique_ptr<LineElement>, ParseError> LineElement::parse(std::span<std::byte const>& buffer, std::uint32_t formatVersion) {
std::ignore = formatVersion;
auto type = readU16LE(buffer);
if (!type) {
return std::unexpected(type.error());
}
if (*type != static_cast<std::uint16_t>(ElementType::Line)) {
return std::unexpected(ParseError::InvalidValue);
}
auto originX = readU16LE(buffer);
if (!originX) {
return std::unexpected(originX.error());
}
auto originY = readU16LE(buffer);
if (!originY) {
return std::unexpected(originY.error());
}
auto targetX = readU16LE(buffer);
if (!targetX) {
return std::unexpected(targetX.error());
}
auto targetY = readU16LE(buffer);
if (!targetY) {
return std::unexpected(targetY.error());
}
auto lineStyle = readU8LE(buffer);
if (!lineStyle) {
return std::unexpected(lineStyle.error());
}
if (*lineStyle != static_cast<std::uint8_t>(LineStyle::Solid)) {
return std::unexpected(ParseError::InvalidValue);
}
auto flags = readU8LE(buffer);
if (!flags) {
return std::unexpected(flags.error());
}
auto result = std::make_unique<LineElement>(*originX, *originY, *targetX, *targetY, static_cast<LineStyle>(*lineStyle), LineFlags{*flags});
return result;
}
std::unique_ptr<LineElement> LineElement::parseJSON(nlohmann::json const& j, std::uint32_t formatVersion) {
std::ignore = formatVersion;
std::uint16_t originX = j.at("originX");
std::uint16_t originY = j.at("originY");
std::uint16_t targetX = j.at("targetX");
std::uint16_t targetY = j.at("targetY");
LineStyle lineStyle = j.at("lineStyle");
LineFlags flags = j.at("flags");
auto result = std::make_unique<LineElement>(originX, originY, targetX, targetY, lineStyle, flags);
return result;
}
BoxElement::BoxElement(std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height, FillPattern fillPattern, FillFlags flags)
: m_x{x}
, m_y{y}
, m_width{width}
, m_height{height}
, m_fillPattern{fillPattern}
, m_flags{flags}
{
}
std::uint16_t BoxElement::x() const noexcept {
return m_x;
}
std::uint16_t BoxElement::y() const noexcept {
return m_y;
}
std::uint16_t BoxElement::width() const noexcept {
return m_width;
}
std::uint16_t BoxElement::height() const noexcept {
return m_height;
}
FillPattern BoxElement::fillPattern() const noexcept {
return m_fillPattern;
}
FillFlags BoxElement::flags() const noexcept {
return m_flags;
}
BoxElement::~BoxElement() {
}
ElementType BoxElement::elementType() const {
return ElementType::Box;
}
std::uint32_t BoxElement::minimumFormatVersion() const {
return 2;
}
std::size_t BoxElement::serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const {
std::ignore = formatVersion;
std::size_t pos = 0;
pos = writeU16LE(target, pos, static_cast<std::uint16_t>(ElementType::Box));
pos = writeU16LE(target, pos, m_x);
pos = writeU16LE(target, pos, m_y);
pos = writeU16LE(target, pos, m_width);
pos = writeU16LE(target, pos, m_height);
pos = writeU8LE(target, pos, static_cast<std::uint8_t>(m_fillPattern));
pos = writeU8LE(target, pos, static_cast<std::uint8_t>(m_flags));
return alignNextWrite(target, pos, 4);
}
nlohmann::json BoxElement::serializeJSON(std::uint32_t formatVersion) const {
std::ignore = formatVersion;
nlohmann::json result;
result["type"] = "Box";
result["x"] = m_x;
result["y"] = m_y;
result["width"] = m_width;
result["height"] = m_height;
result["fillPattern"] = m_fillPattern;
result["flags"] = m_flags;
return result;
}
void BoxElement::drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span<CustomFontSection const*> customFonts) {
std::ignore = animationTick;
std::ignore = currentTimestamp;
std::ignore = customFonts;
bool color = !m_flags.dark;
for (std::uint16_t dy = 0; dy < m_height; ++dy) {
for (std::uint16_t dx = 0; dx < m_width; ++dx) {
imageBuffer->setPixel(m_x + dx, m_y + dy, color);
}
}
}
std::expected<std::unique_ptr<BoxElement>, ParseError> BoxElement::parse(std::span<std::byte const>& buffer, std::uint32_t formatVersion) {
std::ignore = formatVersion;
auto type = readU16LE(buffer);
if (!type) {
return std::unexpected(type.error());
}
if (*type != static_cast<std::uint16_t>(ElementType::Image)) {
return std::unexpected(ParseError::InvalidValue);
}
auto x = readU16LE(buffer);
if (!x) {
return std::unexpected(x.error());
}
auto y = readU16LE(buffer);
if (!y) {
return std::unexpected(y.error());
}
auto width = readU16LE(buffer);
if (!width) {
return std::unexpected(width.error());
}
auto height = readU16LE(buffer);
if (!height) {
return std::unexpected(height.error());
}
auto fillPattern = readU8LE(buffer);
if (!fillPattern) {
return std::unexpected(fillPattern.error());
}
auto flags = readU8LE(buffer);
if (!flags) {
return std::unexpected(flags.error());
}
auto result = std::make_unique<BoxElement>(*x, *y, *width, *height, static_cast<FillPattern>(*fillPattern), FillFlags{*flags});
return result;
}
std::unique_ptr<BoxElement> BoxElement::parseJSON(nlohmann::json const& j, std::uint32_t formatVersion) {
std::ignore = formatVersion;
std::uint16_t x = j.at("x");
std::uint16_t y = j.at("y");
std::uint16_t width = j.at("width");
std::uint16_t height = j.at("height");
FillPattern fillPattern = j.at("fillPattern");
FillFlags flags = j.at("flags");
auto result = std::make_unique<BoxElement>(x, y, width, height, fillPattern, flags);
return result;
}
ClippedTextElement::ClippedTextElement(std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height, TextFlags textFlags, std::uint16_t fontIndex, std::string text)
: m_x{x}
, m_y{y}
, m_width{width}
, m_height{height}
, m_textFlags{textFlags}
, m_fontIndex{fontIndex}
, m_text{std::move(text)}
{
}
std::uint16_t ClippedTextElement::x() const noexcept {
return m_x;
}
std::uint16_t ClippedTextElement::y() const noexcept {
return m_y;
}
std::uint16_t ClippedTextElement::width() const noexcept {
return m_width;
}
std::uint16_t ClippedTextElement::height() const noexcept {
return m_height;
}
TextFlags ClippedTextElement::textFlags() const noexcept {
return m_textFlags;
}
std::uint16_t ClippedTextElement::fontIndex() const noexcept {
return m_fontIndex;
}
std::string ClippedTextElement::text() const {
return m_text;
}
ClippedTextElement::~ClippedTextElement() {
}
ElementType ClippedTextElement::elementType() const {
return ElementType::ClippedText;
}
std::uint32_t ClippedTextElement::minimumFormatVersion() const {
if (static_cast<std::uint8_t>(m_textFlags) != 0) {
return 2;
} else {
return 1;
}
}
std::size_t ClippedTextElement::serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const {
std::size_t pos = 0;
pos = writeU16LE(target, pos, static_cast<std::uint16_t>(ElementType::ClippedText));
pos = writeU16LE(target, pos, m_x);
pos = writeU16LE(target, pos, m_y);
pos = writeU16LE(target, pos, m_width);
pos = writeU16LE(target, pos, m_height);
if (formatVersion >= 2) {
pos = writeU8LE(target, pos, static_cast<std::uint8_t>(m_textFlags));
pos = writeU8LE(target, pos, 0);
}
pos = writeU16LE(target, pos, m_fontIndex);
pos = writeU16LE(target, pos, m_text.size());
pos = writeBuffer(target, pos, std::as_bytes(std::span{m_text}));
return alignNextWrite(target, pos, 4);
}
nlohmann::json ClippedTextElement::serializeJSON(std::uint32_t formatVersion) const {
nlohmann::json result;
result["type"] = "ClippedText";
result["x"] = m_x;
result["y"] = m_y;
result["width"] = m_width;
result["height"] = m_height;
if (formatVersion >= 2) {
result["textFlags"] = m_textFlags;
}
result["fontIndex"] = m_fontIndex;
result["text"] = m_text;
return result;
}
void ClippedTextElement::drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span<CustomFontSection const*> customFonts) {
std::ignore = animationTick;
std::ignore = currentTimestamp;
std::span<std::byte const> fontData;
if (m_fontIndex & 0x8000u) {
std::size_t fontIndex = m_fontIndex & ~0x8000u;
if (fontIndex >= customFonts.size()) {
return;
}
fontData = customFonts[fontIndex]->fontData();
} else {
fontData = findEmbeddedFont(m_fontIndex);
}
if (fontData.size() < 23) {
return;
}
ClippedImage target{imageBuffer, m_x, m_y, m_width, m_height};
std::optional<bool> bgColor;
bgColor = m_textFlags.dark;
bool fgColor = !m_textFlags.dark;
auto renderer = FontRenderer{fontData}.withIgnoreUnknownChars(true);
renderer.render(m_text, Point{0, static_cast<std::int32_t>(renderer.lineHeight() - 1)}, target, fgColor, bgColor);
}
std::expected<std::unique_ptr<ClippedTextElement>, ParseError> ClippedTextElement::parse(std::span<std::byte const>& buffer, std::uint32_t formatVersion) {
auto type = readU16LE(buffer);
if (!type) {
return std::unexpected(type.error());
}
if (*type != static_cast<std::uint16_t>(ElementType::ClippedText)) {
return std::unexpected(ParseError::InvalidValue);
}
auto x = readU16LE(buffer);
if (!x) {
return std::unexpected(x.error());
}
auto y = readU16LE(buffer);
if (!y) {
return std::unexpected(y.error());
}
auto width = readU16LE(buffer);
if (!width) {
return std::unexpected(width.error());
}
auto height = readU16LE(buffer);
if (!height) {
return std::unexpected(height.error());
}
std::expected<std::uint8_t, ParseError> textFlags = 0;
if (formatVersion >= 2) {
textFlags = readU8LE(buffer);
if (!textFlags) {
return std::unexpected(textFlags.error());
}
auto reserved1 = readU8LE(buffer);
if (!reserved1) {
return std::unexpected(reserved1.error());
}
}
auto fontIndex = readU16LE(buffer);
if (!fontIndex) {
return std::unexpected(fontIndex.error());
}
auto textLength = readU16LE(buffer);
if (!textLength) {
return std::unexpected(textLength.error());
}
std::size_t alignedRoundedUp = (*textLength + 2 + 4 - 1) / 4 * 4 - 2;
auto textData = readBuffer(buffer, alignedRoundedUp);
if (!textData) {
return std::unexpected(textData.error());
}
std::string_view text{reinterpret_cast<char const*>(textData->data()), *textLength};
auto result = std::make_unique<ClippedTextElement>(*x, *y, *width, *height, TextFlags{*textFlags}, *fontIndex, std::string{text});
return result;
}
std::unique_ptr<ClippedTextElement> ClippedTextElement::parseJSON(nlohmann::json const& j, std::uint32_t formatVersion) {
std::uint16_t x = j.at("x");
std::uint16_t y = j.at("y");
std::uint16_t width = j.at("width");
std::uint16_t height = j.at("height");
TextFlags textFlags;
if (formatVersion >= 2) {
textFlags = j.at("textFlags");
}
std::uint16_t fontIndex = j.at("fontIndex");
std::string text = j.at("text");
auto result = std::make_unique<ClippedTextElement>(x, y, width, height, textFlags, fontIndex, std::move(text));
return result;
}
HScrollTextElement::HScrollTextElement(std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height, TextFlags textFlags, ScrollFlags flags, std::uint8_t scrollSpeed, std::uint16_t fontIndex, std::string text)
: m_x{x}
, m_y{y}
, m_width{width}
, m_height{height}
, m_textFlags{textFlags}
, m_flags{flags}
, m_scrollSpeed{scrollSpeed}
, m_fontIndex{fontIndex}
, m_text{std::move(text)}
{
}
std::uint16_t HScrollTextElement::x() const noexcept {
return m_x;
}
std::uint16_t HScrollTextElement::y() const noexcept {
return m_y;
}
std::uint16_t HScrollTextElement::width() const noexcept {
return m_width;
}
std::uint16_t HScrollTextElement::height() const noexcept {
return m_height;
}
TextFlags HScrollTextElement::textFlags() const noexcept {
return m_textFlags;
}
ScrollFlags HScrollTextElement::flags() const noexcept {
return m_flags;
}
std::uint8_t HScrollTextElement::scrollSpeed() const noexcept {
return m_scrollSpeed;
}
std::uint16_t HScrollTextElement::fontIndex() const noexcept {
return m_fontIndex;
}
std::string HScrollTextElement::text() const {
return m_text;
}
HScrollTextElement::~HScrollTextElement() {
}
ElementType HScrollTextElement::elementType() const {
return ElementType::HScrollText;
}
std::uint32_t HScrollTextElement::minimumFormatVersion() const {
if (static_cast<std::uint8_t>(m_textFlags) != 0) {
return 2;
} else {
return 1;
}
}
std::size_t HScrollTextElement::serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const {
std::size_t pos = 0;
pos = writeU16LE(target, pos, static_cast<std::uint16_t>(ElementType::HScrollText));
pos = writeU16LE(target, pos, m_x);
pos = writeU16LE(target, pos, m_y);
pos = writeU16LE(target, pos, m_width);
pos = writeU16LE(target, pos, m_height);
if (formatVersion >= 2) {
pos = writeU8LE(target, pos, static_cast<std::uint8_t>(m_textFlags));
pos = writeU8LE(target, pos, 0);
}
pos = writeU8LE(target, pos, static_cast<std::uint8_t>(m_flags));
pos = writeU8LE(target, pos, m_scrollSpeed);
if (formatVersion >= 2) {
pos = writeU16LE(target, pos, 0);
}
pos = writeU16LE(target, pos, m_fontIndex);
pos = writeU16LE(target, pos, m_text.size());
pos = writeBuffer(target, pos, std::as_bytes(std::span{m_text}));
return alignNextWrite(target, pos, 4);
}
nlohmann::json HScrollTextElement::serializeJSON(std::uint32_t formatVersion) const {
std::ignore = formatVersion;
nlohmann::json result;
result["type"] = "HScrollText";
result["x"] = m_x;
result["y"] = m_y;
result["width"] = m_width;
result["height"] = m_height;
if (formatVersion >= 2) {
result["textFlags"] = m_textFlags;
}
result["flags"] = m_flags;
result["scrollSpeed"] = int(m_scrollSpeed);
result["fontIndex"] = m_fontIndex;
result["text"] = m_text;
return result;
}
void HScrollTextElement::drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span<CustomFontSection const*> customFonts) {
std::ignore = currentTimestamp;
std::span<std::byte const> fontData;
if (m_fontIndex & 0x8000u) {
std::size_t fontIndex = m_fontIndex & ~0x8000u;
if (fontIndex >= customFonts.size()) {
return;
}
fontData = customFonts[fontIndex]->fontData();
} else {
fontData = findEmbeddedFont(m_fontIndex);
}
if (fontData.size() < 23) {
return;
}
auto renderer = FontRenderer{fontData}.withIgnoreUnknownChars(true);
auto dimensions = renderer.getRenderedDimensions(m_text, {0, static_cast<std::int32_t>(renderer.lineHeight() - 1)});
if (!dimensions || !dimensions->boundingBox) {
return;
}
std::uint16_t contentWidth = dimensions->boundingBox->size.width;
ClippedImage target{imageBuffer, m_x, m_y, m_width, m_height};
if (contentWidth == 0) {
return;
}
std::optional<bool> bgColor;
bgColor = m_textFlags.dark;
bool fgColor = !m_textFlags.dark;
bool restarting = !m_flags.endless;
bool invert = m_flags.invertDirection;
bool padBefore = m_flags.padBefore;
bool padAfter = m_flags.padAfter;
if (!padBefore && !padAfter && contentWidth < m_width) {
std::uint16_t offset = invert ? (m_width - contentWidth) : 0;
renderer.render(m_text, Point{offset, static_cast<std::int32_t>(renderer.lineHeight() - 1)}, target, fgColor, bgColor);
return;
}
std::int32_t totalSize = contentWidth;
if (padBefore) {
totalSize += m_width;
}
if (padAfter) {
totalSize += m_width;
}
// FIXME: handle overflow here...
std::int32_t offset = static_cast<std::int32_t>(animationTick * (m_scrollSpeed + 1) / 8) % totalSize;
if (invert) {
offset = totalSize - offset - 1;
}
if (restarting) {
offset = std::min(offset, totalSize - m_width);
}
std::int32_t partOffset = 0;
if (padBefore) {
if (bgColor) {
for (std::int32_t dy = 0; dy < m_height; ++dy) {
for (std::int32_t dx = 0; dx < m_width; ++dx) {
std::int32_t ddx = dx + partOffset;
if ((ddx - offset) < 0) {
continue;
}
target.setPixel(ddx - offset, dy, *bgColor);
}
}
}
partOffset += m_width;
}
renderer.render(m_text, Point{partOffset - offset, static_cast<std::int32_t>(renderer.lineHeight() - 1)}, target, fgColor, bgColor);
partOffset += contentWidth;
if (padAfter) {
if (bgColor) {
for (std::int32_t dy = 0; dy < m_height; ++dy) {
for (std::int32_t dx = 0; dx < m_width; ++dx) {
std::int32_t ddx = dx + partOffset;
if ((ddx - offset) < 0) {
continue;
}
target.setPixel(ddx - offset, dy, *bgColor);
}
}
}
partOffset += m_width;
}
if (!restarting) {
if (padBefore) {
if (bgColor) {
for (std::int32_t dy = 0; dy < m_height; ++dy) {
for (std::int32_t dx = 0; dx < m_width; ++dx) {
std::int32_t ddx = dx + partOffset;
if ((ddx - offset) < 0) {
continue;
}
target.setPixel(ddx - offset, dy, *bgColor);
}
}
}
partOffset += m_width;
}
renderer.render(m_text, Point{partOffset - offset, static_cast<std::int32_t>(renderer.lineHeight() - 1)}, target, fgColor, bgColor);
}
}
std::expected<std::unique_ptr<HScrollTextElement>, ParseError> HScrollTextElement::parse(std::span<std::byte const>& buffer, std::uint32_t formatVersion) {
auto type = readU16LE(buffer);
if (!type) {
return std::unexpected(type.error());
}
if (*type != static_cast<std::uint16_t>(ElementType::HScrollText)) {
return std::unexpected(ParseError::InvalidValue);
}
auto x = readU16LE(buffer);
if (!x) {
return std::unexpected(x.error());
}
auto y = readU16LE(buffer);
if (!y) {
return std::unexpected(y.error());
}
auto width = readU16LE(buffer);
if (!width) {
return std::unexpected(width.error());
}
auto height = readU16LE(buffer);
if (!height) {
return std::unexpected(height.error());
}
std::expected<std::uint8_t, ParseError> textFlags = 0;
if (formatVersion >= 2) {
textFlags = readU8LE(buffer);
if (!textFlags) {
return std::unexpected(textFlags.error());
}
auto reserved1 = readU8LE(buffer);
if (!reserved1) {
return std::unexpected(reserved1.error());
}
}
auto flags = readU8LE(buffer);
if (!flags) {
return std::unexpected(flags.error());
}
auto scrollSpeed = readU8LE(buffer);
if (!scrollSpeed) {
return std::unexpected(scrollSpeed.error());
}
if (formatVersion >= 2) {
auto reserved2 = readU16LE(buffer);
if (!reserved2) {
return std::unexpected(reserved2.error());
}
}
auto fontIndex = readU16LE(buffer);
if (!fontIndex) {
return std::unexpected(fontIndex.error());
}
auto textLength = readU16LE(buffer);
if (!textLength) {
return std::unexpected(textLength.error());
}
std::size_t alignedRoundedUp = (*textLength + 4 - 1) / 4 * 4;
auto textData = readBuffer(buffer, alignedRoundedUp);
if (!textData) {
return std::unexpected(textData.error());
}
std::string_view text{reinterpret_cast<char const*>(textData->data()), *textLength};
auto result = std::make_unique<HScrollTextElement>(*x, *y, *width, *height, TextFlags{*textFlags}, ScrollFlags{*flags}, *scrollSpeed, *fontIndex, std::string{text});
return result;
}
std::unique_ptr<HScrollTextElement> HScrollTextElement::parseJSON(nlohmann::json const& j, std::uint32_t formatVersion) {
std::uint16_t x = j.at("x");
std::uint16_t y = j.at("y");
std::uint16_t width = j.at("width");
std::uint16_t height = j.at("height");
TextFlags textFlags;
if (formatVersion >= 2) {
textFlags = j.at("textFlags");
}
ScrollFlags flags = j.at("flags");
int scrollSpeed = j.at("scrollSpeed");
std::uint16_t fontIndex = j.at("fontIndex");
std::string text = j.at("text");
if (scrollSpeed < 0 || scrollSpeed > 255) {
throw std::runtime_error(std::format("Error unserializing an HScrollText element: invalid scroll speed {:d} specified", scrollSpeed));
}
auto result = std::make_unique<HScrollTextElement>(x, y, width, height, textFlags, flags, static_cast<std::uint8_t>(scrollSpeed), fontIndex, std::move(text));
return result;
}
CurrentTimeElement::CurrentTimeElement(std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height, TextFlags textFlags, std::uint16_t fontIndex, std::uint16_t utcOffset, TimeDisplayFlags flags)
: m_x{x}
, m_y{y}
, m_width{width}
, m_height{height}
, m_textFlags{textFlags}
, m_fontIndex{fontIndex}
, m_utcOffset{utcOffset}
, m_flags{flags}
{
}
std::uint16_t CurrentTimeElement::x() const noexcept {
return m_x;
}
std::uint16_t CurrentTimeElement::y() const noexcept {
return m_y;
}
std::uint16_t CurrentTimeElement::width() const noexcept {
return m_width;
}
std::uint16_t CurrentTimeElement::height() const noexcept {
return m_height;
}
TextFlags CurrentTimeElement::textFlags() const noexcept {
return m_textFlags;
}
std::uint16_t CurrentTimeElement::fontIndex() const noexcept {
return m_fontIndex;
}
std::uint16_t CurrentTimeElement::utcOffset() const noexcept {
return m_utcOffset;
}
TimeDisplayFlags CurrentTimeElement::flags() const noexcept {
return m_flags;
}
CurrentTimeElement::~CurrentTimeElement() {
}
ElementType CurrentTimeElement::elementType() const {
return ElementType::CurrentTime;
}
std::uint32_t CurrentTimeElement::minimumFormatVersion() const {
if (static_cast<std::uint8_t>(m_textFlags) != 0) {
return 2;
} else {
return 1;
}
}
std::size_t CurrentTimeElement::serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const {
std::size_t pos = 0;
pos = writeU16LE(target, pos, static_cast<std::uint16_t>(ElementType::CurrentTime));
pos = writeU16LE(target, pos, m_x);
pos = writeU16LE(target, pos, m_y);
pos = writeU16LE(target, pos, m_width);
pos = writeU16LE(target, pos, m_height);
if (formatVersion >= 2) {
pos = writeU8LE(target, pos, static_cast<std::uint8_t>(m_textFlags));
pos = writeU8LE(target, pos, 0);
}
pos = writeU16LE(target, pos, m_fontIndex);
pos = writeU16LE(target, pos, m_utcOffset);
pos = writeU16LE(target, pos, static_cast<std::uint16_t>(m_flags));
if (formatVersion >= 2) {
pos = writeU16LE(target, pos, 0);
}
return alignNextWrite(target, pos, 4);
}
nlohmann::json CurrentTimeElement::serializeJSON(std::uint32_t formatVersion) const {
nlohmann::json result;
result["type"] = "CurrentTime";
result["x"] = m_x;
result["y"] = m_y;
result["width"] = m_width;
result["height"] = m_height;
if (formatVersion >= 2) {
result["textFlags"] = m_textFlags;
}
result["fontIndex"] = m_fontIndex;
result["utcOffset"] = m_utcOffset;
result["flags"] = m_flags;
return result;
}
void CurrentTimeElement::drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span<CustomFontSection const*> customFonts) {
std::ignore = animationTick;
std::span<std::byte const> fontData;
if (m_fontIndex & 0x8000u) {
std::size_t fontIndex = m_fontIndex & ~0x8000u;
if (fontIndex >= customFonts.size()) {
return;
}
fontData = customFonts[fontIndex]->fontData();
} else {
fontData = findEmbeddedFont(m_fontIndex);
}
if (fontData.size() < 23) {
return;
}
bool use12h = m_flags.use12h;
bool showHours = m_flags.showHours;
bool showMinutes = m_flags.showMinutes;
bool showSeconds = m_flags.showSeconds;
if (showHours && showSeconds) {
showMinutes = true;
}
std::optional<bool> bgColor;
bgColor = m_textFlags.dark;
bool fgColor = !m_textFlags.dark;
ClippedImage target{imageBuffer, m_x, m_y, m_width, m_height};
auto renderer = FontRenderer{fontData}.withIgnoreUnknownChars(true);
std::uint32_t seconds = (currentTimestamp + static_cast<std::int64_t>(m_utcOffset) * 60) % 86400;
std::uint32_t hours = seconds / 3600;
std::uint32_t minutes = (seconds - hours * 3600) / 60;
seconds = seconds % 60;
if (use12h) {
hours = hours % 12;
if (hours == 0) {
hours = 12;
}
}
std::string text;
if (showHours && showMinutes && showSeconds) {
text = std::format("{:02d}:{:02d}:{:02d}", hours, minutes, seconds);
} else if (showHours && showMinutes) {
text = std::format("{:02d}:{:02d}", hours, minutes);
} else if (showHours) {
text = std::format("{:02d}", hours);
} else if (showMinutes && showSeconds) {
text = std::format("{:02d}:{:02d}", minutes, seconds);
} else if (showSeconds) {
text = std::format("{:02d}", seconds);
} else {
return;
}
renderer.render(text, Point{0, static_cast<std::int32_t>(renderer.lineHeight() - 1)}, target, fgColor, bgColor);
}
std::expected<std::unique_ptr<CurrentTimeElement>, ParseError> CurrentTimeElement::parse(std::span<std::byte const>& buffer, std::uint32_t formatVersion) {
auto type = readU16LE(buffer);
if (!type) {
return std::unexpected(type.error());
}
if (*type != static_cast<std::uint16_t>(ElementType::CurrentTime)) {
return std::unexpected(ParseError::InvalidValue);
}
auto x = readU16LE(buffer);
if (!x) {
return std::unexpected(x.error());
}
auto y = readU16LE(buffer);
if (!y) {
return std::unexpected(y.error());
}
auto width = readU16LE(buffer);
if (!width) {
return std::unexpected(width.error());
}
auto height = readU16LE(buffer);
if (!height) {
return std::unexpected(height.error());
}
std::expected<std::uint8_t, ParseError> textFlags = 0;
if (formatVersion >= 2) {
textFlags = readU8LE(buffer);
if (!textFlags) {
return std::unexpected(textFlags.error());
}
auto reserved1 = readU8LE(buffer);
if (!reserved1) {
return std::unexpected(reserved1.error());
}
}
auto fontIndex = readU16LE(buffer);
if (!fontIndex) {
return std::unexpected(fontIndex.error());
}
auto utcOffset = readU16LE(buffer);
if (!utcOffset) {
return std::unexpected(utcOffset.error());
}
auto flags = readU16LE(buffer);
if (!flags) {
return std::unexpected(flags.error());
}
if (formatVersion >= 2) {
auto reserved2 = readU16LE(buffer);
if (!reserved2) {
return std::unexpected(reserved2.error());
}
}
auto result = std::make_unique<CurrentTimeElement>(*x, *y, *width, *height, TextFlags{*textFlags}, *fontIndex, *utcOffset, TimeDisplayFlags{*flags});
return result;
}
std::unique_ptr<CurrentTimeElement> CurrentTimeElement::parseJSON(nlohmann::json const& j, std::uint32_t formatVersion) {
std::uint16_t x = j.at("x");
std::uint16_t y = j.at("y");
std::uint16_t width = j.at("width");
std::uint16_t height = j.at("height");
TextFlags textFlags;
if (formatVersion >= 2) {
textFlags = j.at("textFlags");
}
std::uint16_t fontIndex = j.at("fontIndex");
std::uint16_t utcOffset = j.at("utcOffset");
TimeDisplayFlags flags = j.at("flags");
auto result = std::make_unique<CurrentTimeElement>(x, y, width, height, textFlags, fontIndex, utcOffset, flags);
return result;
}
AlwaysDrawnSection::~AlwaysDrawnSection() {
}
bool AlwaysDrawnSection::doesDrawOnFront() const noexcept {
return m_drawOnFront;
}
bool AlwaysDrawnSection::doesDrawOnBack() const noexcept {
return m_drawOnBack;
}
bool AlwaysDrawnSection::doesClearBeforeDrawing() const noexcept {
return m_clearBeforeDrawing;
}
void AlwaysDrawnSection::setDrawOnFront(bool value) noexcept {
m_drawOnFront = value;
}
void AlwaysDrawnSection::setDrawOnBack(bool value) noexcept {
m_drawOnBack = value;
}
void AlwaysDrawnSection::setClearBeforeDrawing(bool value) noexcept {
m_clearBeforeDrawing = value;
}
std::size_t AlwaysDrawnSection::elementCount() const noexcept {
return m_elements.size();
}
Element* AlwaysDrawnSection::elementAt(std::size_t index) const {
if (index < m_elements.size()) {
return m_elements[index].get();
} else {
return nullptr;
}
}
void AlwaysDrawnSection::appendElement(std::unique_ptr<Element> element) {
m_elements.push_back(std::move(element));
}
void AlwaysDrawnSection::insertElement(std::size_t index, std::unique_ptr<Element> element) {
if (index < m_elements.size()) {
m_elements.insert(m_elements.begin() + index, std::move(element));
} else {
m_elements.push_back(std::move(element));
}
}
std::unique_ptr<Element> AlwaysDrawnSection::replaceElement(std::size_t index, std::unique_ptr<Element> element) {
std::unique_ptr<Element> result;
if (index < m_elements.size()) {
result = std::move(m_elements[index]);
m_elements[index] = std::move(element);
}
return result;
}
std::unique_ptr<Element> AlwaysDrawnSection::eraseElement(std::size_t index) {
std::unique_ptr<Element> result;
if (index < m_elements.size()) {
result = std::move(m_elements[index]);
m_elements.erase(m_elements.begin() + index);
}
return result;
}
SectionType AlwaysDrawnSection::sectionType() const {
return SectionType::AlwaysDrawn;
}
std::uint32_t AlwaysDrawnSection::minimumFormatVersion() const {
std::uint32_t result = 1;
for (auto const& element : m_elements) {
result = std::max(result, element->minimumFormatVersion());
}
return result;
}
std::size_t AlwaysDrawnSection::serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const {
std::size_t pos = 0;
pos = writeU8LE(target, pos, static_cast<std::uint16_t>(SectionType::AlwaysDrawn));
// Will be replaced later
pos = writeU24LE(target, pos, 0);
std::uint16_t flags = 0;
maybeSetBit(flags, 0, m_drawOnFront);
maybeSetBit(flags, 1, m_drawOnBack);
maybeSetBit(flags, 2, m_clearBeforeDrawing);
pos = writeU16LE(target, pos, flags);
pos = writeU16LE(target, pos, m_elements.size());
for (auto const& element : m_elements) {
if (pos < target.size()) {
pos += element->serializeTo(target.subspan(pos), formatVersion);
} else {
pos += element->serializeTo({}, formatVersion);
}
}
pos = alignNextWrite(target, pos, 4);
std::ignore = writeU24LE(target, 1, pos);
return pos;
}
nlohmann::json AlwaysDrawnSection::serializeJSON(std::uint32_t formatVersion) const {
std::ignore = formatVersion;
nlohmann::json result;
result["type"] = "AlwaysDrawn";
result["flags"]["drawOnFront"] = m_drawOnFront;
result["flags"]["drawOnBack"] = m_drawOnBack;
result["flags"]["clearBeforeDrawing"] = m_clearBeforeDrawing;
result["elements"] = nlohmann::json::array();
for (auto const& element : m_elements) {
result["elements"].push_back(element->serializeJSON(formatVersion));
}
return result;
}
std::expected<std::unique_ptr<AlwaysDrawnSection>, ParseError> AlwaysDrawnSection::parse(std::span<std::byte const>& buffer, std::uint32_t formatVersion) {
auto type = readU8LE(buffer);
if (!type) {
return std::unexpected(type.error());
}
if (*type != static_cast<std::uint8_t>(SectionType::AlwaysDrawn)) {
return std::unexpected(ParseError::InvalidValue);
}
auto size = readU24LE(buffer);
if (!size) {
return std::unexpected(size.error());
}
if (*size < 8) {
return std::unexpected(ParseError::InvalidValue);
}
auto flags = readU16LE(buffer);
if (!flags) {
return std::unexpected(flags.error());
}
auto elementCount = readU16LE(buffer);
if (!elementCount) {
return std::unexpected(elementCount.error());
}
auto sectionData = buffer.subspan(0, *size - 8);
buffer = buffer.subspan(*size - 8);
auto section = std::make_unique<AlwaysDrawnSection>();
section->setDrawOnFront(isBitSet(*flags, 0));
section->setDrawOnBack(isBitSet(*flags, 1));
section->setClearBeforeDrawing(isBitSet(*flags, 2));
section->m_elements.reserve(*elementCount);
for (std::size_t i = 0; i < *elementCount; ++i) {
auto type = peekU16LE(sectionData);
if (!type) {
return std::unexpected(type.error());
}
std::expected<std::unique_ptr<Element>, ParseError> element;
switch (static_cast<ElementType>(*type)) {
case ElementType::Image: element = ImageElement::parse(sectionData, formatVersion); break;
case ElementType::Animation: element = AnimationElement::parse(sectionData, formatVersion); break;
case ElementType::HScrollImage: element = HScrollImageElement::parse(sectionData, formatVersion); break;
case ElementType::VScrollImage: element = VScrollImageElement::parse(sectionData, formatVersion); break;
case ElementType::Line: element = LineElement::parse(sectionData, formatVersion); break;
case ElementType::Box: element = BoxElement::parse(sectionData, formatVersion); break;
case ElementType::ClippedText: element = ClippedTextElement::parse(sectionData, formatVersion); break;
case ElementType::HScrollText: element = HScrollTextElement::parse(sectionData, formatVersion); break;
case ElementType::CurrentTime: element = CurrentTimeElement::parse(sectionData, formatVersion); break;
default:
return std::unexpected(ParseError::InvalidValue);
}
if (!element) {
return std::unexpected(element.error());
}
section->m_elements.push_back(std::move(*element));
}
return section;
}
std::unique_ptr<AlwaysDrawnSection> AlwaysDrawnSection::parseJSON(nlohmann::json const& j, std::uint32_t formatVersion) {
bool drawOnFront = j.at("flags").at("drawOnFront");
bool drawOnBack = j.at("flags").at("drawOnBack");
bool clearBeforeDrawing = j.at("flags").at("clearBeforeDrawing");
auto section = std::make_unique<AlwaysDrawnSection>();
section->setDrawOnFront(drawOnFront);
section->setDrawOnBack(drawOnBack);
section->setClearBeforeDrawing(clearBeforeDrawing);
std::size_t const elementCount = j.at("elements").size();
for (std::size_t i = 0; i < elementCount; ++i) {
nlohmann::json const& jElement = j.at("elements").at(i);
ElementType type = jElement.at("type");
std::unique_ptr<Element> element;
switch (type) {
case ElementType::Image: element = ImageElement::parseJSON(jElement, formatVersion); break;
case ElementType::Animation: element = AnimationElement::parseJSON(jElement, formatVersion); break;
case ElementType::HScrollImage: element = HScrollImageElement::parseJSON(jElement, formatVersion); break;
case ElementType::VScrollImage: element = VScrollImageElement::parseJSON(jElement, formatVersion); break;
case ElementType::Line: element = LineElement::parseJSON(jElement, formatVersion); break;
case ElementType::Box: element = BoxElement::parseJSON(jElement, formatVersion); break;
case ElementType::ClippedText: element = ClippedTextElement::parseJSON(jElement, formatVersion); break;
case ElementType::HScrollText: element = HScrollTextElement::parseJSON(jElement, formatVersion); break;
case ElementType::CurrentTime: element = CurrentTimeElement::parseJSON(jElement, formatVersion); break;
default: throw std::runtime_error(std::format("Unsupported element type {:s} encountered while parsing a section",
jElement.at("type").get<std::string>()));
}
section->m_elements.push_back(std::move(element));
}
return section;
}
TimeBasedDrawnSection::~TimeBasedDrawnSection() {
}
bool TimeBasedDrawnSection::doesDrawOnFront() const noexcept {
return m_drawOnFront;
}
bool TimeBasedDrawnSection::doesDrawOnBack() const noexcept {
return m_drawOnBack;
}
bool TimeBasedDrawnSection::doesClearBeforeDrawing() const noexcept {
return m_clearBeforeDrawing;
}
void TimeBasedDrawnSection::setDrawOnFront(bool value) noexcept {
m_drawOnFront = value;
}
void TimeBasedDrawnSection::setDrawOnBack(bool value) noexcept {
m_drawOnBack = value;
}
void TimeBasedDrawnSection::setClearBeforeDrawing(bool value) noexcept {
m_clearBeforeDrawing = value;
}
std::size_t TimeBasedDrawnSection::elementCount() const noexcept {
return m_elements.size();
}
Element* TimeBasedDrawnSection::elementAt(std::size_t index) const {
if (index < m_elements.size()) {
return m_elements[index].get();
} else {
return nullptr;
}
}
void TimeBasedDrawnSection::appendElement(std::unique_ptr<Element> element) {
m_elements.push_back(std::move(element));
}
void TimeBasedDrawnSection::insertElement(std::size_t index, std::unique_ptr<Element> element) {
if (index < m_elements.size()) {
m_elements.insert(m_elements.begin() + index, std::move(element));
} else {
m_elements.push_back(std::move(element));
}
}
std::unique_ptr<Element> TimeBasedDrawnSection::replaceElement(std::size_t index, std::unique_ptr<Element> element) {
std::unique_ptr<Element> result;
if (index < m_elements.size()) {
result = std::move(m_elements[index]);
m_elements[index] = std::move(element);
}
return result;
}
std::unique_ptr<Element> TimeBasedDrawnSection::eraseElement(std::size_t index) {
std::unique_ptr<Element> result;
if (index < m_elements.size()) {
result = std::move(m_elements[index]);
m_elements.erase(m_elements.begin() + index);
}
return result;
}
std::int64_t TimeBasedDrawnSection::startTimestamp() const noexcept {
return m_startTimestamp;
}
std::int64_t TimeBasedDrawnSection::endTimestamp() const noexcept {
return m_endTimestamp;
}
void TimeBasedDrawnSection::setStartTimestamp(std::int64_t value) {
m_startTimestamp = value;
}
void TimeBasedDrawnSection::setEndTimestamp(std::int64_t value) {
m_endTimestamp = value;
}
SectionType TimeBasedDrawnSection::sectionType() const {
return SectionType::TimeBasedDrawn;
}
std::uint32_t TimeBasedDrawnSection::minimumFormatVersion() const {
std::uint32_t result = 1;
for (auto const& element : m_elements) {
result = std::max(result, element->minimumFormatVersion());
}
return result;
}
std::size_t TimeBasedDrawnSection::serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const {
std::size_t pos = 0;
pos = writeU8LE(target, pos, static_cast<std::uint16_t>(SectionType::TimeBasedDrawn));
// Will be replaced later
pos = writeU24LE(target, pos, 0);
std::uint16_t flags = 0;
maybeSetBit(flags, 0, m_drawOnFront);
maybeSetBit(flags, 1, m_drawOnBack);
maybeSetBit(flags, 2, m_clearBeforeDrawing);
pos = writeU16LE(target, pos, flags);
pos = writeU16LE(target, pos, m_elements.size());
pos = writeU64LE(target, pos, std::bit_cast<std::uint64_t>(m_startTimestamp));
pos = writeU64LE(target, pos, std::bit_cast<std::uint64_t>(m_endTimestamp));
for (auto const& element : m_elements) {
if (pos < target.size()) {
pos += element->serializeTo(target.subspan(pos), formatVersion);
} else {
pos += element->serializeTo({}, formatVersion);
}
}
pos = alignNextWrite(target, pos, 4);
std::ignore = writeU24LE(target, 1, pos);
return pos;
}
nlohmann::json TimeBasedDrawnSection::serializeJSON(std::uint32_t formatVersion) const {
nlohmann::json result;
result["type"] = "TimeBasedDrawn";
result["flags"]["drawOnFront"] = m_drawOnFront;
result["flags"]["drawOnBack"] = m_drawOnBack;
result["flags"]["clearBeforeDrawing"] = m_clearBeforeDrawing;
result["startTimestamp"] = m_startTimestamp;
result["endTimestamp"] = m_endTimestamp;
result["elements"] = nlohmann::json::array();
for (auto const& element : m_elements) {
result["elements"].push_back(element->serializeJSON(formatVersion));
}
return result;
}
std::expected<std::unique_ptr<TimeBasedDrawnSection>, ParseError> TimeBasedDrawnSection::parse(std::span<std::byte const>& buffer, std::uint32_t formatVersion) {
std::ignore = formatVersion;
auto type = readU8LE(buffer);
if (!type) {
return std::unexpected(type.error());
}
if (*type != static_cast<std::uint8_t>(SectionType::TimeBasedDrawn)) {
return std::unexpected(ParseError::InvalidValue);
}
auto size = readU24LE(buffer);
if (!size) {
return std::unexpected(size.error());
}
if (*size < 24) {
return std::unexpected(ParseError::InvalidValue);
}
auto flags = readU16LE(buffer);
if (!flags) {
return std::unexpected(flags.error());
}
auto elementCount = readU16LE(buffer);
if (!elementCount) {
return std::unexpected(elementCount.error());
}
auto startTimestamp = readU64LE(buffer);
if (!startTimestamp) {
return std::unexpected(startTimestamp.error());
}
auto endTimestamp = readU64LE(buffer);
if (!endTimestamp) {
return std::unexpected(endTimestamp.error());
}
auto sectionData = buffer.subspan(0, *size - 24);
buffer = buffer.subspan(*size - 24);
auto section = std::make_unique<TimeBasedDrawnSection>();
section->setDrawOnFront(isBitSet(*flags, 0));
section->setDrawOnBack(isBitSet(*flags, 1));
section->setClearBeforeDrawing(isBitSet(*flags, 2));
section->setStartTimestamp(std::bit_cast<std::int64_t>(*startTimestamp));
section->setEndTimestamp(std::bit_cast<std::int64_t>(*endTimestamp));
section->m_elements.reserve(*elementCount);
for (std::size_t i = 0; i < *elementCount; ++i) {
auto type = peekU16LE(sectionData);
if (!type) {
return std::unexpected(type.error());
}
std::expected<std::unique_ptr<Element>, ParseError> element;
switch (static_cast<ElementType>(*type)) {
case ElementType::Image: element = ImageElement::parse(sectionData, formatVersion); break;
case ElementType::Animation: element = AnimationElement::parse(sectionData, formatVersion); break;
case ElementType::HScrollImage: element = HScrollImageElement::parse(sectionData, formatVersion); break;
case ElementType::VScrollImage: element = VScrollImageElement::parse(sectionData, formatVersion); break;
case ElementType::Line: element = LineElement::parse(sectionData, formatVersion); break;
case ElementType::Box: element = BoxElement::parse(sectionData, formatVersion); break;
case ElementType::ClippedText: element = ClippedTextElement::parse(sectionData, formatVersion); break;
case ElementType::HScrollText: element = HScrollTextElement::parse(sectionData, formatVersion); break;
case ElementType::CurrentTime: element = CurrentTimeElement::parse(sectionData, formatVersion); break;
default:
return std::unexpected(ParseError::InvalidValue);
}
if (!element) {
return std::unexpected(element.error());
}
section->m_elements.push_back(std::move(*element));
}
return section;
}
std::unique_ptr<TimeBasedDrawnSection> TimeBasedDrawnSection::parseJSON(nlohmann::json const& j, std::uint32_t formatVersion) {
bool drawOnFront = j.at("flags").at("drawOnFront");
bool drawOnBack = j.at("flags").at("drawOnBack");
bool clearBeforeDrawing = j.at("flags").at("clearBeforeDrawing");
std::uint64_t startTimestamp = j.at("startTimestamp");
std::uint64_t endTimestamp = j.at("endTimestamp");
auto section = std::make_unique<TimeBasedDrawnSection>();
section->setDrawOnFront(drawOnFront);
section->setDrawOnBack(drawOnBack);
section->setClearBeforeDrawing(clearBeforeDrawing);
section->setStartTimestamp(startTimestamp);
section->setEndTimestamp(endTimestamp);
std::size_t const elementCount = j.at("elements").size();
for (std::size_t i = 0; i < elementCount; ++i) {
nlohmann::json const& jElement = j.at("elements").at(i);
ElementType type = jElement.at("type");
std::unique_ptr<Element> element;
switch (type) {
case ElementType::Image: element = ImageElement::parseJSON(jElement, formatVersion); break;
case ElementType::Animation: element = AnimationElement::parseJSON(jElement, formatVersion); break;
case ElementType::HScrollImage: element = HScrollImageElement::parseJSON(jElement, formatVersion); break;
case ElementType::VScrollImage: element = VScrollImageElement::parseJSON(jElement, formatVersion); break;
case ElementType::Line: element = LineElement::parseJSON(jElement, formatVersion); break;
case ElementType::Box: element = BoxElement::parseJSON(jElement, formatVersion); break;
case ElementType::ClippedText: element = ClippedTextElement::parseJSON(jElement, formatVersion); break;
case ElementType::HScrollText: element = HScrollTextElement::parseJSON(jElement, formatVersion); break;
case ElementType::CurrentTime: element = CurrentTimeElement::parseJSON(jElement, formatVersion); break;
default: throw std::runtime_error(std::format("Unsupported element type {:s} encountered while parsing a section",
jElement.at("type").get<std::string>()));
}
section->m_elements.push_back(std::move(element));
}
return section;
}
MultiTimeBasedDrawnSection::~MultiTimeBasedDrawnSection() {
}
bool MultiTimeBasedDrawnSection::doesDrawOnFront() const noexcept {
return m_drawOnFront;
}
bool MultiTimeBasedDrawnSection::doesDrawOnBack() const noexcept {
return m_drawOnBack;
}
bool MultiTimeBasedDrawnSection::doesClearBeforeDrawing() const noexcept {
return m_clearBeforeDrawing;
}
void MultiTimeBasedDrawnSection::setDrawOnFront(bool value) noexcept {
m_drawOnFront = value;
}
void MultiTimeBasedDrawnSection::setDrawOnBack(bool value) noexcept {
m_drawOnBack = value;
}
void MultiTimeBasedDrawnSection::setClearBeforeDrawing(bool value) noexcept {
m_clearBeforeDrawing = value;
}
std::size_t MultiTimeBasedDrawnSection::elementCount() const noexcept {
return m_elements.size();
}
Element* MultiTimeBasedDrawnSection::elementAt(std::size_t index) const {
if (index < m_elements.size()) {
return m_elements[index].get();
} else {
return nullptr;
}
}
void MultiTimeBasedDrawnSection::appendElement(std::unique_ptr<Element> element) {
m_elements.push_back(std::move(element));
}
void MultiTimeBasedDrawnSection::insertElement(std::size_t index, std::unique_ptr<Element> element) {
if (index < m_elements.size()) {
m_elements.insert(m_elements.begin() + index, std::move(element));
} else {
m_elements.push_back(std::move(element));
}
}
std::unique_ptr<Element> MultiTimeBasedDrawnSection::replaceElement(std::size_t index, std::unique_ptr<Element> element) {
std::unique_ptr<Element> result;
if (index < m_elements.size()) {
result = std::move(m_elements[index]);
m_elements[index] = std::move(element);
}
return result;
}
std::unique_ptr<Element> MultiTimeBasedDrawnSection::eraseElement(std::size_t index) {
std::unique_ptr<Element> result;
if (index < m_elements.size()) {
result = std::move(m_elements[index]);
m_elements.erase(m_elements.begin() + index);
}
return result;
}
std::vector<std::pair<std::int64_t, std::int64_t>> MultiTimeBasedDrawnSection::timeRanges() const {
return m_timeRanges;
}
void MultiTimeBasedDrawnSection::setTimeRanges(std::vector<std::pair<std::int64_t, std::int64_t>> ranges) {
m_timeRanges = std::move(ranges);
}
SectionType MultiTimeBasedDrawnSection::sectionType() const {
return SectionType::MultiTimeBasedDrawn;
}
std::uint32_t MultiTimeBasedDrawnSection::minimumFormatVersion() const {
std::uint32_t result = 2;
for (auto const& element : m_elements) {
result = std::max(result, element->minimumFormatVersion());
}
return result;
}
std::size_t MultiTimeBasedDrawnSection::serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const {
std::size_t pos = 0;
pos = writeU8LE(target, pos, static_cast<std::uint16_t>(SectionType::MultiTimeBasedDrawn));
// Will be replaced later
pos = writeU24LE(target, pos, 0);
std::uint16_t flags = 0;
maybeSetBit(flags, 0, m_drawOnFront);
maybeSetBit(flags, 1, m_drawOnBack);
maybeSetBit(flags, 2, m_clearBeforeDrawing);
pos = writeU16LE(target, pos, flags);
pos = writeU16LE(target, pos, m_elements.size());
pos = writeU16LE(target, pos, m_timeRanges.size());
pos = writeU16LE(target, pos, 0);
for (std::size_t i = 0; i < m_timeRanges.size(); ++i) {
pos = writeU64LE(target, pos, std::bit_cast<std::uint64_t>(m_timeRanges[i].first));
pos = writeU64LE(target, pos, std::bit_cast<std::uint64_t>(m_timeRanges[i].second));
}
for (auto const& element : m_elements) {
if (pos < target.size()) {
pos += element->serializeTo(target.subspan(pos), formatVersion);
} else {
pos += element->serializeTo({}, formatVersion);
}
}
pos = alignNextWrite(target, pos, 4);
std::ignore = writeU24LE(target, 1, pos);
return pos;
}
nlohmann::json MultiTimeBasedDrawnSection::serializeJSON(std::uint32_t formatVersion) const {
nlohmann::json result;
result["type"] = "MultiTimeBasedDrawn";
result["flags"]["drawOnFront"] = m_drawOnFront;
result["flags"]["drawOnBack"] = m_drawOnBack;
result["flags"]["clearBeforeDrawing"] = m_clearBeforeDrawing;
result["timeRanges"] = m_timeRanges;
result["elements"] = nlohmann::json::array();
for (auto const& element : m_elements) {
result["elements"].push_back(element->serializeJSON(formatVersion));
}
return result;
}
std::expected<std::unique_ptr<MultiTimeBasedDrawnSection>, ParseError> MultiTimeBasedDrawnSection::parse(std::span<std::byte const>& buffer, std::uint32_t formatVersion) {
std::ignore = formatVersion;
auto type = readU8LE(buffer);
if (!type) {
return std::unexpected(type.error());
}
if (*type != static_cast<std::uint8_t>(SectionType::TimeBasedDrawn)) {
return std::unexpected(ParseError::InvalidValue);
}
auto size = readU24LE(buffer);
if (!size) {
return std::unexpected(size.error());
}
if (*size < 24) {
return std::unexpected(ParseError::InvalidValue);
}
auto flags = readU16LE(buffer);
if (!flags) {
return std::unexpected(flags.error());
}
auto elementCount = readU16LE(buffer);
if (!elementCount) {
return std::unexpected(elementCount.error());
}
auto rangeCount = readU16LE(buffer);
if (!rangeCount) {
return std::unexpected(rangeCount.error());
}
auto reserved1 = readU16LE(buffer);
if (!reserved1) {
return std::unexpected(reserved1.error());
}
std::vector<std::pair<std::int64_t, std::int64_t>> timeRanges;
timeRanges.reserve(*rangeCount);
for (std::size_t i = 0; i < *rangeCount; ++i) {
auto startTimestamp = readU64LE(buffer);
if (!startTimestamp) {
return std::unexpected(startTimestamp.error());
}
auto endTimestamp = readU64LE(buffer);
if (!endTimestamp) {
return std::unexpected(endTimestamp.error());
}
timeRanges.push_back(std::make_pair(*startTimestamp, *endTimestamp));
}
auto sectionData = buffer.subspan(0, *size - 24);
buffer = buffer.subspan(*size - 24);
auto section = std::make_unique<MultiTimeBasedDrawnSection>();
section->setDrawOnFront(isBitSet(*flags, 0));
section->setDrawOnBack(isBitSet(*flags, 1));
section->setClearBeforeDrawing(isBitSet(*flags, 2));
section->setTimeRanges(std::move(timeRanges));
section->m_elements.reserve(*elementCount);
for (std::size_t i = 0; i < *elementCount; ++i) {
auto type = peekU16LE(sectionData);
if (!type) {
return std::unexpected(type.error());
}
std::expected<std::unique_ptr<Element>, ParseError> element;
switch (static_cast<ElementType>(*type)) {
case ElementType::Image: element = ImageElement::parse(sectionData, formatVersion); break;
case ElementType::Animation: element = AnimationElement::parse(sectionData, formatVersion); break;
case ElementType::HScrollImage: element = HScrollImageElement::parse(sectionData, formatVersion); break;
case ElementType::VScrollImage: element = VScrollImageElement::parse(sectionData, formatVersion); break;
case ElementType::Line: element = LineElement::parse(sectionData, formatVersion); break;
case ElementType::Box: element = BoxElement::parse(sectionData, formatVersion); break;
case ElementType::ClippedText: element = ClippedTextElement::parse(sectionData, formatVersion); break;
case ElementType::HScrollText: element = HScrollTextElement::parse(sectionData, formatVersion); break;
case ElementType::CurrentTime: element = CurrentTimeElement::parse(sectionData, formatVersion); break;
default:
return std::unexpected(ParseError::InvalidValue);
}
if (!element) {
return std::unexpected(element.error());
}
section->m_elements.push_back(std::move(*element));
}
return section;
}
std::unique_ptr<MultiTimeBasedDrawnSection> MultiTimeBasedDrawnSection::parseJSON(nlohmann::json const& j, std::uint32_t formatVersion) {
bool drawOnFront = j.at("flags").at("drawOnFront");
bool drawOnBack = j.at("flags").at("drawOnBack");
bool clearBeforeDrawing = j.at("flags").at("clearBeforeDrawing");
std::vector<std::pair<std::int64_t, std::int64_t>> timeRanges = j.at("timeRanges");
auto section = std::make_unique<MultiTimeBasedDrawnSection>();
section->setDrawOnFront(drawOnFront);
section->setDrawOnBack(drawOnBack);
section->setClearBeforeDrawing(clearBeforeDrawing);
section->setTimeRanges(std::move(timeRanges));
std::size_t const elementCount = j.at("elements").size();
for (std::size_t i = 0; i < elementCount; ++i) {
nlohmann::json const& jElement = j.at("elements").at(i);
ElementType type = jElement.at("type");
std::unique_ptr<Element> element;
switch (type) {
case ElementType::Image: element = ImageElement::parseJSON(jElement, formatVersion); break;
case ElementType::Animation: element = AnimationElement::parseJSON(jElement, formatVersion); break;
case ElementType::HScrollImage: element = HScrollImageElement::parseJSON(jElement, formatVersion); break;
case ElementType::VScrollImage: element = VScrollImageElement::parseJSON(jElement, formatVersion); break;
case ElementType::Line: element = LineElement::parseJSON(jElement, formatVersion); break;
case ElementType::Box: element = BoxElement::parseJSON(jElement, formatVersion); break;
case ElementType::ClippedText: element = ClippedTextElement::parseJSON(jElement, formatVersion); break;
case ElementType::HScrollText: element = HScrollTextElement::parseJSON(jElement, formatVersion); break;
case ElementType::CurrentTime: element = CurrentTimeElement::parseJSON(jElement, formatVersion); break;
default: throw std::runtime_error(std::format("Unsupported element type {:s} encountered while parsing a section",
jElement.at("type").get<std::string>()));
}
section->m_elements.push_back(std::move(element));
}
return section;
}
ExpiryDateSection::~ExpiryDateSection() {
}
std::int64_t ExpiryDateSection::expiryDate() const noexcept {
return m_expiryDate;
}
void ExpiryDateSection::setExpiryDate(std::int64_t value) noexcept {
m_expiryDate = value;
}
SectionType ExpiryDateSection::sectionType() const {
return SectionType::CustomFont;
}
std::uint32_t ExpiryDateSection::minimumFormatVersion() const {
return 2;
}
std::size_t ExpiryDateSection::serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const {
std::ignore = formatVersion;
std::size_t pos = 0;
pos = writeU8LE(target, pos, static_cast<std::uint16_t>(SectionType::ExpiryDate));
// Will be replaced later
pos = writeU24LE(target, pos, 0);
pos = writeU64LE(target, pos, std::bit_cast<std::uint64_t>(m_expiryDate));
pos = alignNextWrite(target, pos, 4);
std::ignore = writeU24LE(target, 1, pos);
return pos;
}
nlohmann::json ExpiryDateSection::serializeJSON(std::uint32_t formatVersion) const {
std::ignore = formatVersion;
nlohmann::json result;
result["type"] = "ExpiryDate";
result["expiryDate"] = m_expiryDate;
return result;
}
std::expected<std::unique_ptr<ExpiryDateSection>, ParseError> ExpiryDateSection::parse(std::span<std::byte const>& buffer, std::uint32_t formatVersion) {
std::ignore = formatVersion;
auto type = readU8LE(buffer);
if (!type) {
return std::unexpected(type.error());
}
if (*type != static_cast<std::uint8_t>(SectionType::CustomFont)) {
return std::unexpected(ParseError::InvalidValue);
}
auto size = readU24LE(buffer);
if (!size) {
return std::unexpected(size.error());
}
if (*size < 8) {
return std::unexpected(ParseError::InvalidValue);
}
auto expiryDate = readU64LE(buffer);
if (!expiryDate) {
return std::unexpected(expiryDate.error());
}
auto section = std::make_unique<ExpiryDateSection>();
section->setExpiryDate(std::bit_cast<std::int64_t>(*expiryDate));
return section;
}
std::unique_ptr<ExpiryDateSection> ExpiryDateSection::parseJSON(nlohmann::json const& j, std::uint32_t formatVersion) {
std::ignore = formatVersion;
std::int64_t expiryDate = j.at("expiryDate");
auto section = std::make_unique<ExpiryDateSection>();
section->setExpiryDate(expiryDate);
return section;
}
CustomFontSection::~CustomFontSection() {
}
std::span<std::byte const> CustomFontSection::fontData() const {
return std::span{m_fontData};
}
void CustomFontSection::setFontData(std::span<std::byte const> fontData) {
m_fontData.resize(fontData.size());
std::copy(fontData.begin(), fontData.end(), m_fontData.begin());
}
SectionType CustomFontSection::sectionType() const {
return SectionType::CustomFont;
}
std::uint32_t CustomFontSection::minimumFormatVersion() const {
return 1;
}
std::size_t CustomFontSection::serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const {
std::ignore = formatVersion;
std::size_t pos = 0;
pos = writeU8LE(target, pos, static_cast<std::uint16_t>(SectionType::CustomFont));
// Will be replaced later
pos = writeU24LE(target, pos, 0);
pos = writeU24LE(target, pos, m_fontData.size());
pos = writeU8LE(target, pos, 0);
pos = writeBuffer(target, pos, m_fontData);
pos = alignNextWrite(target, pos, 4);
std::ignore = writeU24LE(target, 1, pos);
return pos;
}
nlohmann::json CustomFontSection::serializeJSON(std::uint32_t formatVersion) const {
std::ignore = formatVersion;
nlohmann::json result;
result["type"] = "CustomFont";
result["data"] = base64Encode(m_fontData);
return result;
}
std::expected<std::unique_ptr<CustomFontSection>, ParseError> CustomFontSection::parse(std::span<std::byte const>& buffer, std::uint32_t formatVersion) {
std::ignore = formatVersion;
auto type = readU8LE(buffer);
if (!type) {
return std::unexpected(type.error());
}
if (*type != static_cast<std::uint8_t>(SectionType::CustomFont)) {
return std::unexpected(ParseError::InvalidValue);
}
auto size = readU24LE(buffer);
if (!size) {
return std::unexpected(size.error());
}
if (*size < 8) {
return std::unexpected(ParseError::InvalidValue);
}
auto actualSize = readU24LE(buffer);
if (!actualSize) {
return std::unexpected(actualSize.error());
}
auto reserved_ = readU8LE(buffer);
if (!reserved_) {
return std::unexpected(reserved_.error());
}
auto fontData = buffer.subspan(0, *size - 8);
buffer = buffer.subspan(*size - 8);
auto actualFontData = readBuffer(fontData, *actualSize);
if (!actualFontData) {
return std::unexpected(actualFontData.error());
}
auto section = std::make_unique<CustomFontSection>();
section->setFontData(*actualFontData);
return section;
}
std::unique_ptr<CustomFontSection> CustomFontSection::parseJSON(nlohmann::json const& j, std::uint32_t formatVersion) {
std::ignore = formatVersion;
auto fontData = base64Decode(j.at("data").get<std::string>());
auto section = std::make_unique<CustomFontSection>();
section->setFontData(fontData);
return section;
}
std::size_t serializeFile(std::span<std::byte>& buffer, File const& file) {
std::size_t pos = 0;
std::uint32_t formatVersion = 1;
for (auto const& section : file.sections) {
formatVersion = std::max(formatVersion, section->minimumFormatVersion());
}
pos = writeU8LE(buffer, pos, 0xAF);
pos = writeU8LE(buffer, pos, 0x7E);
pos = writeU8LE(buffer, pos, 0x2B);
pos = writeU8LE(buffer, pos, 0x63);
pos = writeU32LE(buffer, pos, formatVersion);
pos = writeU16LE(buffer, pos, file.sections.size());
pos = writeU16LE(buffer, pos, 0);
pos = alignNextWrite(buffer, pos, 4);
for (auto const& section : file.sections) {
if (pos < buffer.size()) {
pos += section->serializeTo(buffer.subspan(pos), formatVersion);
} else {
pos += section->serializeTo({}, formatVersion);
}
}
return pos;
}
nlohmann::json serializeFileJSON(File const& file) {
std::uint32_t formatVersion = 1;
for (auto const& section : file.sections) {
formatVersion = std::max(formatVersion, section->minimumFormatVersion());
}
nlohmann::json result;
result["version"] = formatVersion;
result["sections"] = nlohmann::json::array();
for (auto const& section : file.sections) {
result["sections"].push_back(section->serializeJSON(formatVersion));
}
return result;
}
std::expected<File, ParseError> parseFile(std::span<std::byte const> data) {
auto magic = readU32LE(data);
if (!magic) {
return std::unexpected(magic.error());
}
if (*magic != UINT32_C(0x632B7EAF)) {
return std::unexpected(ParseError::InvalidValue);
}
auto version = readU32LE(data);
if (!version) {
return std::unexpected(version.error());
}
if (*version < 1 || *version > 2) {
return std::unexpected(ParseError::InvalidValue);
}
auto sectionCount = readU16LE(data);
if (!sectionCount) {
return std::unexpected(sectionCount.error());
}
auto reserved_ = readU16LE(data);
if (!reserved_) {
return std::unexpected(reserved_.error());
}
File result;
result.sections.reserve(*sectionCount);
for (std::size_t i = 0; i < *sectionCount; ++i) {
auto sectionType = peekU8LE(data);
if (!sectionType) {
return std::unexpected(sectionType.error());
}
std::expected<std::unique_ptr<Section>, ParseError> section;
switch (static_cast<SectionType>(*sectionType)) {
case SectionType::AlwaysDrawn: section = AlwaysDrawnSection::parse(data, *version); break;
case SectionType::TimeBasedDrawn: section = TimeBasedDrawnSection::parse(data, *version); break;
case SectionType::MultiTimeBasedDrawn: section = MultiTimeBasedDrawnSection::parse(data, *version); break;
case SectionType::ExpiryDate: section = ExpiryDateSection::parse(data, *version); break;
case SectionType::CustomFont: section = CustomFontSection::parse(data, *version); break;
default: return std::unexpected(ParseError::InvalidValue);
}
if (!section) {
return std::unexpected(section.error());
}
result.sections.push_back(std::move(*section));
}
return result;
}
File parseFileJSON(nlohmann::json const& j) {
File result;
std::uint32_t version = j.at("version");
if (version < 1 || version > 2) {
throw std::runtime_error(std::format("Unsupported file format version {:d} encountered while parsing a file", version));
}
std::size_t const sectionCount = j.at("sections").size();
for (std::size_t i = 0; i < sectionCount; ++i) {
nlohmann::json const& jSection = j.at("sections").at(i);
SectionType sectionType = jSection.at("type");
std::unique_ptr<Section> section;
switch (sectionType) {
case SectionType::AlwaysDrawn: section = AlwaysDrawnSection::parseJSON(jSection, version); break;
case SectionType::TimeBasedDrawn: section = TimeBasedDrawnSection::parseJSON(jSection, version); break;
case SectionType::MultiTimeBasedDrawn: section = MultiTimeBasedDrawnSection::parseJSON(jSection, version); break;
case SectionType::ExpiryDate: section = ExpiryDateSection::parseJSON(jSection, version); break;
case SectionType::CustomFont: section = CustomFontSection::parseJSON(jSection, version); break;
default: throw std::runtime_error(std::format("Unsupported section type {:s} encountered while parsing a file",
jSection.at("type").get<std::string>()));
}
result.sections.push_back(std::move(section));
}
return result;
}
constexpr static char const* const Base64Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
constexpr static std::int8_t const Base64ParseLookup[256] = {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1};
std::string base64Encode(std::span<std::byte const> buffer) {
std::string result;
uint8_t const* data = reinterpret_cast<uint8_t const*>(buffer.data());
std::size_t numberOfBlocks = (buffer.size() + 2) / 3;
result.reserve(numberOfBlocks);
for (std::size_t i = 0; i < numberOfBlocks; ++i) {
std::size_t ii = i * 3;
std::size_t ni = std::min(ii + 3, buffer.size()) - ii;
int c0 = (((data[ii + 0] >> 2) & 0x3f) << 0);
if (ni == 3) {
int c1 = (((data[ii + 0] >> 0) & 0x03) << 4)
| (((data[ii + 1] >> 4) & 0x0f) << 0);
int c2 = (((data[ii + 1] >> 0) & 0x0f) << 2)
| (((data[ii + 2] >> 6) & 0x03) << 0);
int c3 = (((data[ii + 2] >> 0) & 0x3f) << 0);
result.push_back(Base64Alphabet[c0]);
result.push_back(Base64Alphabet[c1]);
result.push_back(Base64Alphabet[c2]);
result.push_back(Base64Alphabet[c3]);
} else if (ni == 2) {
int c1 = (((data[ii + 0] >> 0) & 0x03) << 4)
| (((data[ii + 1] >> 4) & 0x0f) << 0);
int c2 = (((data[ii + 1] >> 0) & 0x0f) << 2);
result.push_back(Base64Alphabet[c0]);
result.push_back(Base64Alphabet[c1]);
result.push_back(Base64Alphabet[c2]);
} else if (ni == 1) {
int c1 = (((data[ii + 0] >> 0) & 0x03) << 4);
result.push_back(Base64Alphabet[c0]);
result.push_back(Base64Alphabet[c1]);
}
}
return result;
}
std::vector<std::byte> base64Decode(std::string_view data) {
std::vector<std::byte> result;
// This is just an initial estimate
std::size_t numberOfBlocks = (data.size() + 3) / 4;
result.reserve(numberOfBlocks * 3);
int state = 0;
int currentValue = 0;
for (std::size_t i = 0; i < data.size(); ++i) {
int value = Base64ParseLookup[static_cast<std::uint8_t>(data[i])];
if (value == -2) {
// '=': padding at the end
break;
} else if (value == -1) {
// any invalid character (especially whitespace): ignore
continue;
}
switch (state) {
case 0:
default:
currentValue = value << 2;
break;
case 1:
currentValue |= (value >> 4) & 0x03;
result.push_back(static_cast<std::byte>(currentValue));
currentValue = (value & 0x0f) << 4;
break;
case 2:
currentValue |= (value >> 2) & 0x0f;
result.push_back(static_cast<std::byte>(currentValue));
currentValue = (value & 0x03) << 6;
break;
case 3:
currentValue |= value & 0x3f;
result.push_back(static_cast<std::byte>(currentValue));
currentValue = 0;
break;
}
state = (state + 1) % 4;
}
// Ignore any errors here
return result;
}
} // namespace monoformat