#include "monoformat_parseonly.hpp" #include "monoformat_parsehelpers.hpp" #include "monoformat_fontreader.hpp" #include "monoformat_bithelpers.hpp" #include #include #include #include namespace monoformat { struct AlwaysDrawnSectionMetaData { bool drawOnFront{}; bool drawOnBack{}; bool clearBeforeDrawing{}; std::span elementData; }; struct TimeBasedDrawnSectionMetaData { AlwaysDrawnSectionMetaData base; std::int64_t startTimestamp{}; std::int64_t endTimestamp{}; }; static std::expected handleImage(std::span& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span> customFonts); static std::expected handleAnimation(std::span& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span> customFonts); static std::expected handleHScrollImage(std::span& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span> customFonts); static std::expected handleVScrollImage(std::span& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span> customFonts); static std::expected handleLine(std::span& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span> customFonts); static std::expected handleClippedText(std::span& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span> customFonts); static std::expected handleHScrollText(std::span& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span> customFonts); static std::expected handleCurrentTime(std::span& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span> customFonts); static std::expected parseAlwaysDrawnSection(std::span& data) { auto type = readU8LE(data); if (!type) { return std::unexpected(type.error()); } if (*type != static_cast(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 parseTimeBasedDrawnSection(std::span& data) { auto type = readU8LE(data); if (!type) { return std::unexpected(type.error()); } if (*type != static_cast(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(*startTimestamp); result.endTimestamp = std::bit_cast(*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, ParseError> parseCustomFontSection(std::span& data) { auto type = readU8LE(data); if (!type) { return std::unexpected(type.error()); } if (*type != static_cast(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 parseAndApply(std::span data, OneBitBufferInterface* screen, std::uint16_t screenWidth, std::uint16_t screenHeight, bool isFront, std::size_t animationTick, std::int64_t currentTimestamp) { std::array, 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::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::AlwaysDrawn) { auto metaData = parseAlwaysDrawnSection(*rawSectionData); if (!metaData) { return std::unexpected(metaData.error()); } section = *metaData; } else if (static_cast(*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(*type); std::expected 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 handleImage(std::span& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span> customFonts) { auto type = readU16LE(buffer); if (!type) { return std::unexpected(type.error()); } if (*type != static_cast(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 handleAnimation(std::span& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span> customFonts) { std::ignore = currentTimestamp; std::ignore = customFonts; auto type = readU16LE(buffer); if (!type) { return std::unexpected(type.error()); } if (*type != static_cast(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(*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 handleHScrollImage(std::span& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span> customFonts) { std::ignore = currentTimestamp; std::ignore = customFonts; auto type = readU16LE(buffer); if (!type) { return std::unexpected(type.error()); } if (*type != static_cast(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(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 handleVScrollImage(std::span& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span> customFonts) { std::ignore = currentTimestamp; std::ignore = customFonts; auto type = readU16LE(buffer); if (!type) { return std::unexpected(type.error()); } if (*type != static_cast(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(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 handleLine(std::span& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span> 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(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(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(*targetX) - static_cast(*originX); std::int32_t dy = static_cast(*targetY) - static_cast(*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(x), static_cast(y), value); } } return {}; } std::expected handleClippedText(std::span& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span> customFonts) { std::ignore = animationTick; std::ignore = currentTimestamp; auto type = readU16LE(buffer); if (!type) { return std::unexpected(type.error()); } if (*type != static_cast(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(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 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(renderer.lineHeight() - 1)}, target, true, false); return {}; } std::expected handleHScrollText(std::span& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span> customFonts) { std::ignore = currentTimestamp; auto type = readU16LE(buffer); if (!type) { return std::unexpected(type.error()); } if (*type != static_cast(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(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 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(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(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(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(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(renderer.lineHeight() - 1)}, target, true, false); } return {}; } std::expected handleCurrentTime(std::span& buffer, OneBitBufferInterface* screen, std::size_t animationTick, std::int64_t currentTimestamp, std::span> customFonts) { std::ignore = animationTick; auto type = readU16LE(buffer); if (!type) { return std::unexpected(type.error()); } if (*type != static_cast(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 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(*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(renderer.lineHeight() - 1)}, target, true, false); return {}; } } // namespace monoformat