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.
1138 lines
40 KiB
1138 lines
40 KiB
#include "monoformat_parseonly.hpp"
|
|
#include "monoformat_parsehelpers.hpp"
|
|
#include "monoformat_fontreader.hpp"
|
|
#include "monoformat_bithelpers.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 = isBitSet(*flags, 0);
|
|
result.drawOnBack = isBitSet(*flags, 1);
|
|
result.clearBeforeDrawing = isBitSet(*flags, 2);
|
|
result.elementData = sectionData;
|
|
|
|
#if defined(DEBUG)
|
|
std::cerr << "[Debug] AlwaysDrawnSection, drawOnFront = " << result.drawOnFront << ", drawOnBack = " << result.drawOnBack << ", clearBeforeDrawing = " << result.clearBeforeDrawing << ", element data size = " << result.elementData.size() << " bytes" << std::endl;
|
|
#endif
|
|
|
|
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 = isBitSet(*flags, 0);
|
|
result.base.drawOnBack = isBitSet(*flags, 1);
|
|
result.base.clearBeforeDrawing = isBitSet(*flags, 2);
|
|
result.base.elementData = sectionData;
|
|
result.startTimestamp = std::bit_cast<std::int64_t>(*startTimestamp);
|
|
result.endTimestamp = std::bit_cast<std::int64_t>(*endTimestamp);
|
|
|
|
#if defined(DEBUG)
|
|
std::cerr << "[Debug] TimeBasedDrawnSection, drawOnFront = " << result.base.drawOnFront << ", drawOnBack = " << result.base.drawOnBack << ", clearBeforeDrawing = " << result.base.clearBeforeDrawing << ", element data size = " << result.base.elementData.size() << " bytes, timestamp between " << result.startTimestamp << " and " << result.endTimestamp << std::endl;
|
|
#endif
|
|
|
|
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);
|
|
|
|
#if defined(DEBUG)
|
|
std::cerr << "[Debug] CustomFontSection, font data size = " << *actualSize << " bytes" << std::endl;
|
|
#endif
|
|
|
|
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);
|
|
}
|
|
|
|
#if defined(DEBUG)
|
|
std::cerr << "[Debug] - Image, x = " << (*x) << ", y = " << (*y) << ", width = " << (*width) << ", height = " << (*height) << std::endl;
|
|
#endif
|
|
|
|
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 * (*numberOfFrames));
|
|
if (!imageData) {
|
|
return std::unexpected(reserved_.error());
|
|
}
|
|
std::size_t rounded = (imageSize * (*numberOfFrames) + 4 - 1) / 4 * 4;
|
|
if (imageSize * (*numberOfFrames) < rounded) {
|
|
buffer = buffer.subspan(rounded - imageSize * (*numberOfFrames));
|
|
}
|
|
|
|
#if defined(DEBUG)
|
|
std::cerr << "[Debug] - Animation, x = " << (*x) << ", y = " << (*y) << ", width = " << (*width) << ", height = " << (*height)
|
|
<< ", numberOfFrames = " << (*numberOfFrames)
|
|
<< ", updateInterval = " << (*updateInterval)
|
|
<< std::endl;
|
|
#endif
|
|
|
|
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 defined(DEBUG)
|
|
std::cerr << "[Debug] - HScrollImage, x = " << (*x) << ", y = " << (*y) << ", width = " << (*width) << ", height = " << (*height)
|
|
<< ", contentWidth = " << (*contentWidth)
|
|
<< ", scrollSpeed = " << int(*scrollSpeed)
|
|
<< ", flags = " << int(*flags)
|
|
<< std::endl;
|
|
#endif
|
|
|
|
if (*contentWidth == 0) {
|
|
return {};
|
|
}
|
|
|
|
ScrollFlags scrollFlags{*flags};
|
|
|
|
bool restarting = !scrollFlags.endless;
|
|
bool invert = scrollFlags.invertDirection;
|
|
bool padBefore = scrollFlags.padBefore;
|
|
bool padAfter = scrollFlags.padAfter;
|
|
|
|
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 defined(DEBUG)
|
|
std::cerr << "[Debug] - VScrollImage, x = " << (*x) << ", y = " << (*y) << ", width = " << (*width) << ", height = " << (*height)
|
|
<< ", contentHeight = " << (*contentHeight)
|
|
<< ", scrollSpeed = " << int(*scrollSpeed)
|
|
<< ", flags = " << int(*flags)
|
|
<< std::endl;
|
|
#endif
|
|
|
|
if (*contentHeight == 0) {
|
|
return {};
|
|
}
|
|
|
|
ScrollFlags scrollFlags{*flags};
|
|
|
|
bool restarting = !scrollFlags.endless;
|
|
bool invert = scrollFlags.invertDirection;
|
|
bool padBefore = scrollFlags.padBefore;
|
|
bool padAfter = scrollFlags.padAfter;
|
|
|
|
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());
|
|
}
|
|
|
|
#if defined(DEBUG)
|
|
std::cerr << "[Debug] - Line, originX = " << (*originX) << ", originY = " << (*originY) << ", targetX = " << (*targetX) << ", targetY = " << (*targetY)
|
|
<< ", lineStyle = " << int(*lineStyle)
|
|
<< std::endl;
|
|
#endif
|
|
|
|
LineFlags lineFlags{*flags};
|
|
std::ignore = lineFlags;
|
|
|
|
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};
|
|
|
|
#if defined(DEBUG)
|
|
std::cerr << "[Debug] - ClippedText, x = " << (*x) << ", y = " << (*y) << ", width = " << (*width) << ", height = " << (*height)
|
|
<< ", fontIndex = " << int(*fontIndex)
|
|
<< ", text = \"" << text << "\""
|
|
<< std::endl;
|
|
#endif
|
|
|
|
std::span<std::byte const> fontData;
|
|
if (isBitSet(*fontIndex, 15)) {
|
|
unsetBit(*fontIndex, 15);
|
|
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};
|
|
|
|
#if defined(DEBUG)
|
|
std::cerr << "[Debug] - HScrollText, x = " << (*x) << ", y = " << (*y) << ", width = " << (*width) << ", height = " << (*height)
|
|
<< ", scrollSpeed = " << int(*scrollSpeed)
|
|
<< ", flags = " << int(*flags)
|
|
<< ", fontIndex = " << int(*fontIndex)
|
|
<< ", text = \"" << text << "\""
|
|
<< std::endl;
|
|
#endif
|
|
|
|
std::span<std::byte const> fontData;
|
|
if (isBitSet(*fontIndex, 15)) {
|
|
unsetBit(*fontIndex, 15);
|
|
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 {};
|
|
}
|
|
|
|
ScrollFlags scrollFlags{*flags};
|
|
|
|
bool restarting = !scrollFlags.endless;
|
|
bool invert = scrollFlags.invertDirection;
|
|
bool padBefore = scrollFlags.padBefore;
|
|
bool padAfter = scrollFlags.padAfter;
|
|
|
|
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());
|
|
}
|
|
|
|
#if defined(DEBUG)
|
|
std::cerr << "[Debug] - CurrentTime, x = " << (*x) << ", y = " << (*y) << ", width = " << (*width) << ", height = " << (*height)
|
|
<< ", fontIndex = " << int(*fontIndex)
|
|
<< ", utcOffset = " << int(*utcOffset)
|
|
<< ", flags = " << int(*flags)
|
|
<< std::endl;
|
|
#endif
|
|
|
|
std::span<std::byte const> fontData;
|
|
if (isBitSet(*fontIndex, 15)) {
|
|
unsetBit(*fontIndex, 15);
|
|
if (*fontIndex >= customFonts.size()) {
|
|
return {};
|
|
}
|
|
fontData = customFonts[*fontIndex];
|
|
} else {
|
|
fontData = findEmbeddedFont(*fontIndex);
|
|
}
|
|
if (fontData.size() < 23) {
|
|
return {};
|
|
}
|
|
ClippedImage target{screen, *x, *y, *width, *height};
|
|
|
|
TimeDisplayFlags timeDisplayFlags{*flags};
|
|
|
|
bool use12h = timeDisplayFlags.use12h;
|
|
bool showHours = timeDisplayFlags.showHours;
|
|
bool showMinutes = timeDisplayFlags.showMinutes;
|
|
bool showSeconds = timeDisplayFlags.showSeconds;
|
|
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
|
|
|
|
|