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_parseonly.cpp

1055 lines
37 KiB

#include "monoformat_parseonly.hpp"
#include "monoformat_parsehelpers.hpp"
#include "monoformat_fontreader.hpp"
#include <string_view>
#include <string>
#include <cstdio>
#include <iostream>
namespace monoformat {
struct AlwaysDrawnSectionMetaData {
bool drawOnFront{};
bool drawOnBack{};
bool clearBeforeDrawing{};
std::span<std::byte const> elementData;
};
struct TimeBasedDrawnSectionMetaData {
AlwaysDrawnSectionMetaData base;
std::int64_t startTimestamp{};
std::int64_t endTimestamp{};
};
static std::expected<void, ParseError> handleImage(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts);
static std::expected<void, ParseError> handleAnimation(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts);
static std::expected<void, ParseError> handleHScrollImage(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts);
static std::expected<void, ParseError> handleVScrollImage(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts);
static std::expected<void, ParseError> handleLine(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts);
static std::expected<void, ParseError> handleClippedText(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts);
static std::expected<void, ParseError> handleHScrollText(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts);
static std::expected<void, ParseError> handleCurrentTime(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts);
static std::expected<AlwaysDrawnSectionMetaData, ParseError> parseAlwaysDrawnSection(std::span<std::byte const>& data) {
auto type = readU8LE(data);
if (!type) {
return std::unexpected(type.error());
}
if (*type != static_cast<std::uint8_t>(SectionType::AlwaysDrawn)) {
return std::unexpected(ParseError::InvalidValue);
}
auto size = readU24LE(data);
if (!size) {
return std::unexpected(size.error());
}
if (*size < 8) {
return std::unexpected(ParseError::InvalidValue);
}
auto flags = readU16LE(data);
if (!flags) {
return std::unexpected(flags.error());
}
auto elementCount = readU16LE(data);
if (!elementCount) {
return std::unexpected(elementCount.error());
}
auto sectionData = data.subspan(0, *size - 8);
data = data.subspan(*size - 8);
AlwaysDrawnSectionMetaData result;
result.drawOnFront = (((*flags) >> 0u) & 0x01u) == 0x01u;
result.drawOnBack = (((*flags) >> 0u) & 0x02u) == 0x02u;
result.clearBeforeDrawing = (((*flags) >> 0u) & 0x04u) == 0x04u;
result.elementData = sectionData;
return result;
}
static std::expected<TimeBasedDrawnSectionMetaData, ParseError> parseTimeBasedDrawnSection(std::span<std::byte const>& data) {
auto type = readU8LE(data);
if (!type) {
return std::unexpected(type.error());
}
if (*type != static_cast<std::uint8_t>(SectionType::AlwaysDrawn)) {
return std::unexpected(ParseError::InvalidValue);
}
auto size = readU24LE(data);
if (!size) {
return std::unexpected(size.error());
}
if (*size < 8) {
return std::unexpected(ParseError::InvalidValue);
}
auto flags = readU16LE(data);
if (!flags) {
return std::unexpected(flags.error());
}
auto elementCount = readU16LE(data);
if (!elementCount) {
return std::unexpected(elementCount.error());
}
auto startTimestamp = readU64LE(data);
if (!startTimestamp) {
return std::unexpected(startTimestamp.error());
}
auto endTimestamp = readU64LE(data);
if (!endTimestamp) {
return std::unexpected(endTimestamp.error());
}
auto sectionData = data.subspan(0, *size - 24);
data = data.subspan(*size - 24);
TimeBasedDrawnSectionMetaData result;
result.base.drawOnFront = (((*flags) >> 0u) & 0x01u) == 0x01u;
result.base.drawOnBack = (((*flags) >> 0u) & 0x02u) == 0x02u;
result.base.clearBeforeDrawing = (((*flags) >> 0u) & 0x04u) == 0x04u;
result.base.elementData = sectionData;
result.startTimestamp = std::bit_cast<std::int64_t>(*startTimestamp);
result.endTimestamp = std::bit_cast<std::int64_t>(*endTimestamp);
return result;
}
static std::expected<std::span<std::byte const>, ParseError> parseCustomFontSection(std::span<std::byte const>& data) {
auto type = readU8LE(data);
if (!type) {
return std::unexpected(type.error());
}
if (*type != static_cast<std::uint8_t>(SectionType::CustomFont)) {
return std::unexpected(ParseError::InvalidValue);
}
auto size = readU24LE(data);
if (!size) {
return std::unexpected(size.error());
}
if (*size < 8) {
return std::unexpected(ParseError::InvalidValue);
}
auto actualSize = readU24LE(data);
if (!actualSize) {
return std::unexpected(actualSize.error());
}
auto reserved_ = readU8LE(data);
if (!reserved_) {
return std::unexpected(reserved_.error());
}
auto fontData = data.subspan(0, *size - 8);
data = data.subspan(*size - 8);
return readBuffer(fontData, *actualSize);
}
std::expected<void, ParseError> parseAndApply(std::span<std::byte const> data, OneBitBufferInterface* screen,
std::uint16_t screenWidth, std::uint16_t screenHeight,
bool isFront, std::size_t animationTick, std::int64_t currentTimestamp) {
std::array<std::span<std::byte const>, 16> customFonts;
std::size_t customFontIndex{};
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) {
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());
}
for (std::size_t i = 0; i < *sectionCount; ++i) {
auto sectionType = peekU8LE(data);
if (!sectionType) {
return std::unexpected(sectionType.error());
}
auto sectionSize = peekU24LE(data.subspan(1));
if (!sectionSize) {
return std::unexpected(sectionSize.error());
}
auto rawSectionData = readBuffer(data, *sectionSize);
if (!rawSectionData) {
return std::unexpected(rawSectionData.error());
}
if (static_cast<SectionType>(*sectionType) == SectionType::CustomFont) {
auto fontData = parseCustomFontSection(*rawSectionData);
if (!fontData) {
return std::unexpected(fontData.error());
}
if (customFontIndex < 16) {
customFonts[customFontIndex++] = *fontData;
}
continue;
}
AlwaysDrawnSectionMetaData section;
if (static_cast<SectionType>(*sectionType) == SectionType::AlwaysDrawn) {
auto metaData = parseAlwaysDrawnSection(*rawSectionData);
if (!metaData) {
return std::unexpected(metaData.error());
}
section = *metaData;
} else if (static_cast<SectionType>(*sectionType) == SectionType::TimeBasedDrawn) {
auto metaData = parseTimeBasedDrawnSection(*rawSectionData);
if (!metaData) {
return std::unexpected(metaData.error());
}
if (currentTimestamp < metaData->startTimestamp || currentTimestamp >= metaData->endTimestamp) {
continue;
}
section = metaData->base;
} else {
continue;
}
if ((!section.drawOnFront && isFront)
|| (!section.drawOnBack && !isFront)) {
continue;
}
if (section.clearBeforeDrawing) {
for (std::uint16_t y = 0; y < screenHeight; ++y) {
for (std::uint16_t x = 0; x < screenWidth; ++x) {
screen->setPixel(x, y, false);
}
}
}
while (section.elementData.size() > 0) {
auto type = peekU16LE(section.elementData);
if (!type) {
return std::unexpected(type.error());
}
auto eType = static_cast<ElementType>(*type);
std::expected<void, ParseError> parseResult;
switch (eType) {
case ElementType::Image: parseResult = handleImage(section.elementData, screen, animationTick, currentTimestamp, std::span{customFonts}.subspan(0, customFontIndex)); break;
case ElementType::Animation: parseResult = handleAnimation(section.elementData, screen, animationTick, currentTimestamp, std::span{customFonts}.subspan(0, customFontIndex)); break;
case ElementType::HScrollImage: parseResult = handleHScrollImage(section.elementData, screen, animationTick, currentTimestamp, std::span{customFonts}.subspan(0, customFontIndex)); break;
case ElementType::VScrollImage: parseResult = handleVScrollImage(section.elementData, screen, animationTick, currentTimestamp, std::span{customFonts}.subspan(0, customFontIndex)); break;
case ElementType::Line: parseResult = handleLine(section.elementData, screen, animationTick, currentTimestamp, std::span{customFonts}.subspan(0, customFontIndex)); break;
case ElementType::ClippedText: parseResult = handleClippedText(section.elementData, screen, animationTick, currentTimestamp, std::span{customFonts}.subspan(0, customFontIndex)); break;
case ElementType::HScrollText: parseResult = handleHScrollText(section.elementData, screen, animationTick, currentTimestamp, std::span{customFonts}.subspan(0, customFontIndex)); break;
case ElementType::CurrentTime: parseResult = handleCurrentTime(section.elementData, screen, animationTick, currentTimestamp, std::span{customFonts}.subspan(0, customFontIndex)); break;
default: parseResult = std::unexpected(ParseError::InvalidValue);
}
if (!parseResult) {
return std::unexpected(parseResult.error());
}
}
}
return {};
}
std::expected<void, ParseError> handleImage(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts) {
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);
}
std::ignore = animationTick;
std::ignore = currentTimestamp;
std::ignore = customFonts;
auto ownImage = ConstMemoryOneBitBuffer{*imageData, *width, *height};
for (std::uint16_t dy = 0; dy < *height; ++dy) {
for (std::uint16_t dx = 0; dx < *width; ++dx) {
screen->setPixel(*x + dx, *y + dy, ownImage.isPixelSet(dx, dy));
}
}
return {};
}
std::expected<void, ParseError> handleAnimation(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts) {
std::ignore = currentTimestamp;
std::ignore = customFonts;
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);
}
if (*numberOfFrames == 0) {
return {};
}
std::size_t const tick = animationTick / (static_cast<std::size_t>(*updateInterval) + 1);
std::size_t const frameIndex = tick % *numberOfFrames;
auto ownImage = ConstMemoryOneBitBuffer{std::span{*imageData}.subspan(frameIndex * imageSize, imageSize), *width, *height};
for (std::uint16_t dy = 0; dy < *height; ++dy) {
for (std::uint16_t dx = 0; dx < *width; ++dx) {
screen->setPixel(*x + dx, *y + dy, ownImage.isPixelSet(dx, dy));
}
}
return {};
}
std::expected<void, ParseError> handleHScrollImage(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts) {
std::ignore = currentTimestamp;
std::ignore = customFonts;
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 ownImage = ConstMemoryOneBitBuffer{*imageData, *contentWidth, *height};
ClippedImage target{screen, *x, *y, *width, *height};
if (*contentWidth == 0) {
return {};
}
bool restarting = ((*flags) & 0x01) == 0x00;
bool invert = ((*flags) & 0x02) == 0x02;
bool padBefore = ((*flags) & 0x04) == 0x04;
bool padAfter = ((*flags) & 0x08) == 0x08;
if (!padBefore && !padAfter && *contentWidth < *width) {
std::int16_t offset = invert ? (*width - *contentWidth) : 0;
for (std::size_t dy = 0; dy < *height; ++dy) {
for (std::size_t dx = 0; dx < *contentWidth; ++dx) {
target.setPixel(dx + offset, dy, ownImage.isPixelSet(dx, dy));
}
}
return {};
}
std::int32_t totalSize = *contentWidth;
if (padBefore) {
totalSize += *width;
}
if (padAfter) {
totalSize += *width;
}
// FIXME: handle overflow here...
std::int32_t offset = static_cast<std::int32_t>(animationTick * (*scrollSpeed + 1) / 8) % totalSize;
if (invert) {
offset = totalSize - offset - 1;
}
if (restarting) {
offset = std::min(offset, totalSize - *width);
}
std::int32_t partOffset = 0;
if (padBefore) {
for (std::int32_t dy = 0; dy < *height; ++dy) {
for (std::int32_t dx = 0; dx < *width; ++dx) {
std::int32_t ddx = dx + partOffset;
if ((ddx - offset) < 0) {
continue;
}
target.setPixel(ddx - offset, dy, false);
}
}
partOffset += *width;
}
for (std::int32_t dy = 0; dy < *height; ++dy) {
for (std::int32_t dx = 0; dx < *contentWidth; ++dx) {
std::int32_t ddx = dx + partOffset;
if ((ddx - offset) < 0) {
continue;
}
target.setPixel(ddx - offset, dy, ownImage.isPixelSet(dx, dy));
}
partOffset += *contentWidth;
}
if (padAfter) {
for (std::int32_t dy = 0; dy < *height; ++dy) {
for (std::int32_t dx = 0; dx < *width; ++dx) {
std::int32_t ddx = dx + partOffset;
if ((ddx - offset) < 0) {
continue;
}
target.setPixel(ddx - offset, dy, false);
}
}
partOffset += *width;
}
if (!restarting) {
if (padBefore) {
for (std::int32_t dy = 0; dy < *height; ++dy) {
for (std::int32_t dx = 0; dx < *width; ++dx) {
std::int32_t ddx = dx + partOffset;
if ((ddx - offset) < 0) {
continue;
}
target.setPixel(ddx - offset, dy, false);
}
}
partOffset += *width;
}
for (std::int32_t dy = 0; dy < *height; ++dy) {
for (std::int32_t dx = 0; dx < *contentWidth; ++dx) {
std::int32_t ddx = dx + partOffset;
if ((ddx - offset) < 0) {
continue;
}
target.setPixel(ddx - offset, dy, ownImage.isPixelSet(dx, dy));
}
}
partOffset += *contentWidth;
}
return {};
}
std::expected<void, ParseError> handleVScrollImage(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts) {
std::ignore = currentTimestamp;
std::ignore = customFonts;
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 ownImage = ConstMemoryOneBitBuffer{*imageData, *width, *contentHeight};
ClippedImage target{screen, *x, *y, *width, *height};
if (*contentHeight == 0) {
return {};
}
bool restarting = ((*flags) & 0x01) == 0x00;
bool invert = ((*flags) & 0x02) == 0x02;
bool padBefore = ((*flags) & 0x04) == 0x04;
bool padAfter = ((*flags) & 0x08) == 0x08;
if (!padBefore && !padAfter && *contentHeight < *height) {
std::int16_t offset = invert ? (*height - *contentHeight) : 0;
for (std::size_t dy = 0; dy < *contentHeight; ++dy) {
for (std::size_t dx = 0; dx < *width; ++dx) {
target.setPixel(dx, dy + offset, ownImage.isPixelSet(dx, dy));
}
}
return {};
}
std::int32_t totalSize = *contentHeight;
if (padBefore) {
totalSize += *height;
}
if (padAfter) {
totalSize += *height;
}
// FIXME: handle overflow here...
std::int32_t offset = static_cast<std::int32_t>(animationTick * (*scrollSpeed + 1) / 8) % totalSize;
if (invert) {
offset = totalSize - offset - 1;
}
if (restarting) {
offset = std::min(offset, totalSize - *height);
}
std::int32_t partOffset = 0;
if (padBefore) {
for (std::int32_t dy = 0; dy < *height; ++dy) {
std::int32_t ddy = dy + partOffset;
if ((ddy - offset) < 0) {
continue;
}
for (std::int32_t dx = 0; dx < *width; ++dx) {
target.setPixel(dx, ddy - offset, false);
}
}
partOffset += *height;
}
for (std::int32_t dy = 0; dy < *contentHeight; ++dy) {
std::int32_t ddy = dy + partOffset;
if ((ddy - offset) < 0) {
continue;
}
for (std::int32_t dx = 0; dx < *width; ++dx) {
target.setPixel(dx, ddy - offset, ownImage.isPixelSet(dx, dy));
}
partOffset += *contentHeight;
}
if (padAfter) {
for (std::int32_t dy = 0; dy < *height; ++dy) {
std::int32_t ddy = dy + partOffset;
if ((ddy - offset) < 0) {
continue;
}
for (std::int32_t dx = 0; dx < *width; ++dx) {
target.setPixel(dx, ddy - offset, false);
}
}
partOffset += *height;
}
if (!restarting) {
if (padBefore) {
for (std::int32_t dy = 0; dy < *height; ++dy) {
std::int32_t ddy = dy + partOffset;
if ((ddy - offset) < 0) {
continue;
}
for (std::int32_t dx = 0; dx < *width; ++dx) {
target.setPixel(dx, ddy - offset, false);
}
}
partOffset += *height;
}
for (std::int32_t dy = 0; dy < *contentHeight; ++dy) {
std::int32_t ddy = dy + partOffset;
if ((ddy - offset) < 0) {
continue;
}
for (std::int32_t dx = 0; dx < *width; ++dx) {
target.setPixel(dx, ddy - offset, ownImage.isPixelSet(dx, dy));
}
}
partOffset += *contentHeight;
}
return {};
}
std::expected<void, ParseError> handleLine(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts) {
std::ignore = animationTick;
std::ignore = currentTimestamp;
std::ignore = customFonts;
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());
}
std::int32_t dx = static_cast<std::int32_t>(*targetX) - static_cast<std::int32_t>(*originX);
std::int32_t dy = static_cast<std::int32_t>(*targetY) - static_cast<std::int32_t>(*originY);
bool value = true;
if (dx == 0) {
for (std::int32_t i = 0; i <= dy; ++i) {
screen->setPixel(*originX, *originY + i, value);
}
} else if (dy == 0) {
for (std::int32_t i = 0; i <= dx; ++i) {
screen->setPixel(*originX + i, *originY, value);
}
} else {
for (std::int32_t i = 0; i <= dx; ++i) {
std::int32_t x = *originX + i;
std::int32_t y = i * dy / dx + *originY;
screen->setPixel(static_cast<std::uint16_t>(x), static_cast<std::uint16_t>(y), value);
}
}
return {};
}
std::expected<void, ParseError> handleClippedText(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts) {
std::ignore = animationTick;
std::ignore = currentTimestamp;
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());
}
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};
std::span<std::byte const> fontData;
if ((*fontIndex) & 0x8000u) {
fontIndex = (*fontIndex) & ~0x8000u;
if (*fontIndex >= customFonts.size()) {
return {};
}
fontData = customFonts[*fontIndex];
} else {
fontData = findEmbeddedFont(*fontIndex);
}
if (fontData.size() < 23) {
return {};
}
ClippedImage target{screen, *x, *y, *width, *height};
auto renderer = FontRenderer{fontData}.withIgnoreUnknownChars(true);
renderer.render(text, Point{0, static_cast<std::int32_t>(renderer.lineHeight() - 1)}, target, true, false);
return {};
}
std::expected<void, ParseError> handleHScrollText(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts) {
std::ignore = currentTimestamp;
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());
}
auto flags = readU8LE(buffer);
if (!flags) {
return std::unexpected(flags.error());
}
auto scrollSpeed = readU8LE(buffer);
if (!scrollSpeed) {
return std::unexpected(scrollSpeed.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};
std::span<std::byte const> fontData;
if ((*fontIndex) & 0x8000u) {
fontIndex = (*fontIndex) & ~0x8000u;
if (*fontIndex >= customFonts.size()) {
return {};
}
fontData = customFonts[*fontIndex];
} else {
fontData = findEmbeddedFont(*fontIndex);
}
if (fontData.size() < 23) {
return {};
}
ClippedImage target{screen, *x, *y, *width, *height};
auto renderer = FontRenderer{fontData}.withIgnoreUnknownChars(true);
auto dimensions = renderer.getRenderedDimensions(text, {0, static_cast<std::int32_t>(renderer.lineHeight() - 1)});
if (!dimensions || !dimensions->boundingBox) {
return {};
}
std::uint16_t contentWidth = dimensions->boundingBox->size.width;
if (contentWidth == 0) {
return {};
}
bool restarting = ((*flags) & 0x01) == 0x00;
bool invert = ((*flags) & 0x02) == 0x02;
bool padBefore = ((*flags) & 0x04) == 0x04;
bool padAfter = ((*flags) & 0x08) == 0x08;
if (!padBefore && !padAfter && contentWidth < *width) {
std::uint16_t offset = invert ? (*width - contentWidth) : 0;
renderer.render(text, Point{offset, static_cast<std::int32_t>(renderer.lineHeight() - 1)}, target, true, false);
return {};
}
std::int32_t totalSize = contentWidth;
if (padBefore) {
totalSize += *width;
}
if (padAfter) {
totalSize += *width;
}
// FIXME: handle overflow here...
std::int32_t offset = static_cast<std::int32_t>(animationTick * (*scrollSpeed + 1) / 8) % totalSize;
if (invert) {
offset = totalSize - offset - 1;
}
if (restarting) {
offset = std::min(offset, totalSize - *width);
}
std::int32_t partOffset = 0;
if (padBefore) {
for (std::int32_t dy = 0; dy < *height; ++dy) {
for (std::int32_t dx = 0; dx < *width; ++dx) {
std::int32_t ddx = dx + partOffset;
if ((ddx - offset) < 0) {
continue;
}
target.setPixel(ddx - offset, dy, false);
}
}
partOffset += *width;
}
renderer.render(text, Point{partOffset - offset, static_cast<std::int32_t>(renderer.lineHeight() - 1)}, target, true, false);
partOffset += contentWidth;
if (padAfter) {
for (std::int32_t dy = 0; dy < *height; ++dy) {
for (std::int32_t dx = 0; dx < *width; ++dx) {
std::int32_t ddx = dx + partOffset;
if ((ddx - offset) < 0) {
continue;
}
target.setPixel(ddx - offset, dy, false);
}
}
partOffset += *width;
}
if (!restarting) {
if (padBefore) {
for (std::int32_t dy = 0; dy < *height; ++dy) {
for (std::int32_t dx = 0; dx < *width; ++dx) {
std::int32_t ddx = dx + partOffset;
if ((ddx - offset) < 0) {
continue;
}
target.setPixel(ddx - offset, dy, false);
}
}
partOffset += *width;
}
renderer.render(text, Point{partOffset - offset, static_cast<std::int32_t>(renderer.lineHeight() - 1)}, target, true, false);
}
return {};
}
std::expected<void, ParseError> handleCurrentTime(std::span<std::byte const>& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span<std::span<std::byte const>> customFonts) {
std::ignore = animationTick;
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());
}
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());
}
std::span<std::byte const> fontData;
if ((*fontIndex) & 0x8000u) {
fontIndex = (*fontIndex) & ~0x8000u;
if (*fontIndex >= customFonts.size()) {
return {};
}
fontData = customFonts[*fontIndex];
} else {
fontData = findEmbeddedFont(*fontIndex);
}
if (fontData.size() < 23) {
return {};
}
ClippedImage target{screen, *x, *y, *width, *height};
bool use12h = ((*flags) & 0x01u) == 0x01u;
bool showHours = ((*flags) & 0x02u) == 0x02u;
bool showMinutes = ((*flags) & 0x04u) == 0x04u;
bool showSeconds = ((*flags) & 0x08u) == 0x08u;
if (showHours && showSeconds) {
showMinutes = true;
}
auto renderer = FontRenderer{fontData}.withIgnoreUnknownChars(true);
std::uint32_t seconds = (currentTimestamp + static_cast<std::int64_t>(*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;
}
}
char text[32] = {};
if (showHours && showMinutes && showSeconds) {
std::snprintf(text, sizeof(text), "%02d:%02d:%02d", int(hours), int(minutes), int(seconds));
} else if (showHours && showMinutes) {
std::snprintf(text, sizeof(text), "%02d:%02d", int(hours), int(minutes));
} else if (showHours) {
std::snprintf(text, sizeof(text), "%02d", int(hours));
} else if (showMinutes && showSeconds) {
std::snprintf(text, sizeof(text), "%02d:%02d", int(minutes), int(seconds));
} else if (showSeconds) {
std::snprintf(text, sizeof(text), "%02d", int(seconds));
} else if (showMinutes) {
std::snprintf(text, sizeof(text), "%02d", int(minutes));
} else {
return {};
}
renderer.render(text, Point{0, static_cast<std::int32_t>(renderer.lineHeight() - 1)}, target, true, false);
return {};
}
} // namespace monoformat