From b9ff412df5fa5ab64b86bbb6755fdb11f7a7f31d Mon Sep 17 00:00:00 2001 From: Christian Seiler Date: Fri, 15 May 2026 10:20:07 +0200 Subject: [PATCH] C++: implement (mostly) non-allocating parser that immediately renders --- cpp/clivis/main.cpp | 149 +++- cpp/src/CMakeLists.txt | 4 + cpp/src/monoformat_fontreader.cpp | 6 +- cpp/src/monoformat_fontreader.hpp | 2 +- cpp/src/monoformat_parsehelpers.hpp | 4 +- cpp/src/monoformat_parseonly.cpp | 1053 +++++++++++++++++++++++++++ cpp/src/monoformat_parseonly.hpp | 15 + cpp/src/monoformat_schema.cpp | 103 +++ cpp/src/monoformat_schema.hpp | 77 ++ cpp/src/monoformat_structured.cpp | 128 +--- cpp/src/monoformat_structured.hpp | 66 +- 11 files changed, 1384 insertions(+), 223 deletions(-) create mode 100644 cpp/src/monoformat_parseonly.cpp create mode 100644 cpp/src/monoformat_parseonly.hpp create mode 100644 cpp/src/monoformat_schema.cpp create mode 100644 cpp/src/monoformat_schema.hpp diff --git a/cpp/clivis/main.cpp b/cpp/clivis/main.cpp index 224c86b..1ed8838 100644 --- a/cpp/clivis/main.cpp +++ b/cpp/clivis/main.cpp @@ -1,47 +1,52 @@ #include #include +#include -#include #include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include using namespace monoformat; constexpr static std::uint16_t const ScreenWidth = 120; constexpr static std::uint16_t const ScreenHeight = 60; -int main() { - auto fontData = findEmbeddedFont("NokiaSmallPlain_tf"); - if (!fontData.size()) { - std::cerr << "Error loading font!\n" << std::flush; - return 1; - } - auto fontData2 = findEmbeddedFont("5x7_mf"); - if (!fontData2.size()) { - std::cerr << "Error loading font!\n" << std::flush; - return 1; - } +static std::atomic g_ctrlCPressed; - std::vector memory; - memory.resize((ScreenWidth * ScreenHeight + 7) / 8); - MemoryOneBitBuffer screen{memory, ScreenWidth, ScreenHeight}; +static void handleCtrlC(int) { + g_ctrlCPressed.store(true, std::memory_order_relaxed); +} - auto renderer = FontRenderer{fontData}.withIgnoreUnknownChars(true); - auto ret = renderer.render("Hallo Welt! ggg äöüß é è ê", {0, renderer.lineHeight() - 1}, screen, true, false); - if (!ret) { - std::cerr << "Error rendering.\n" << std::flush; - return 2; - } - - auto renderer2 = FontRenderer{fontData2}.withIgnoreUnknownChars(true); - ret = renderer2.render("Hallo2", {0, renderer2.lineHeight() - 1 + renderer.lineHeight()}, screen, true, false); - if (!ret) { - std::cerr << "Error rendering.\n" << std::flush; - return 2; +template +static Container readEntireFile(std::filesystem::path const& fileName) { + std::ifstream fileReader(fileName, std::ios::binary | std::ios::ate | std::ios::in); + if (!fileReader) { + std::ostringstream buf; + buf << "Could not open \"" << fileName << "\" for reading"; + throw std::runtime_error(buf.str()); } + auto fileSize = fileReader.tellg(); + fileReader.seekg(std::ios::beg); + typename Container::size_type scalarSize = sizeof(typename Container::value_type); + Container content((static_cast(fileSize) + scalarSize - 1) / scalarSize, + static_cast(0)); + fileReader.read(reinterpret_cast(&content[0]), static_cast(fileSize)); + return content; +} - std::cerr << "Bbox height: " << ret->boundingBox->size.height << std::endl; +static void clearScreen() { + std::cout << "\x1b[1J\x1b[1;1H" << std::flush; +} +static void renderScreeen(monoformat::OneBitBufferInterface const& imageBuffer) { std::uint16_t const w = ScreenWidth; std::uint16_t const h = ScreenHeight / 2; @@ -54,8 +59,8 @@ int main() { for (std::uint16_t y = 0; y < h; ++y) { buf << "\xE2\x94\x82"; for (std::uint16_t x = 0; x < w; ++x) { - bool const pxUpperSet = screen.isPixelSet(x, 2 * y + 0); - bool const pxLowerSet = screen.isPixelSet(x, 2 * y + 1); + bool const pxUpperSet = imageBuffer.isPixelSet(x, 2 * y + 0); + bool const pxLowerSet = imageBuffer.isPixelSet(x, 2 * y + 1); if (pxUpperSet && pxLowerSet) { buf << "\xE2\x96\x88"; } else if (pxUpperSet) { @@ -71,7 +76,7 @@ int main() { if (ScreenHeight % 1) { buf << "\xE2\x94\x82"; for (std::uint16_t x = 0; x < w; ++x) { - bool const pxUpperSet = screen.isPixelSet(x, ScreenHeight - 1); + bool const pxUpperSet = imageBuffer.isPixelSet(x, ScreenHeight - 1); if (pxUpperSet) { buf << "\xF0\x9F\xAC\x8E"; } else { @@ -86,6 +91,88 @@ int main() { } buf << "\xE2\x94\x98\n"; std::cout << buf.str(); +} + +static void showContents(monoformat::File const& file, std::size_t tick, std::int64_t timestamp, std::span customFonts) { + monoformat::ImageElement imageBufferElement{0, 0, ScreenWidth, ScreenHeight}; + auto imageBuffer = imageBufferElement.image(); + + for (auto const& section : file.sections) { + if (auto sec = dynamic_cast(section.get())) { + for (std::size_t i = 0; i < sec->elementCount(); ++i) { + sec->elementAt(i)->drawTo(&imageBuffer, tick, timestamp, customFonts); + } + } else if (auto sec = dynamic_cast(section.get())) { + for (std::size_t i = 0; i < sec->elementCount(); ++i) { + sec->elementAt(i)->drawTo(&imageBuffer, tick, timestamp, customFonts); + } + } + } + + renderScreeen(imageBuffer); +} +static void showContents2(std::span data, std::size_t tick, std::int64_t timestamp) { + std::vector buffer; + buffer.resize((ScreenWidth * ScreenHeight + 8 - 1) / 8); + MemoryOneBitBuffer imageBuffer{buffer, ScreenWidth, ScreenHeight}; + + auto ret = parseAndApply(data, &imageBuffer, ScreenWidth, ScreenHeight, true, tick, timestamp); + if (!ret) { + std::cerr << "Parse & apply error\n"; + } + + renderScreeen(imageBuffer); +} + +int main(int argc, char** argv) { + if (argc != 2 || !argv[1]) { + std::ostringstream buf; + buf << "Usage: " << argv[0] << " filename\n"; + std::cerr << buf.str(); + return 1; + } + if (std::string_view{argv[1]} == "--help") { + std::ostringstream buf; + buf << "Usage: " << argv[0] << " filename\n"; + std::cout << buf.str() << std::flush; + return 0; + } + + try { + auto contents = readEntireFile>(argv[1]); + auto file = monoformat::parseFile(contents); + if (!file) { + std::ostringstream buf; + buf << "Parse error while processing the file \"" << argv[1] << "\""; + throw std::runtime_error(buf.str()); + } + + std::vector customFonts; + for (auto const& section : file->sections) { + if (auto fs = dynamic_cast(section.get())) { + customFonts.push_back(fs); + } + } + + std::signal(SIGINT, &handleCtrlC); + std::signal(SIGTERM, &handleCtrlC); + std::size_t tick = 0; + while (!g_ctrlCPressed.load(std::memory_order_relaxed)) { + clearScreen(); + //showContents(*file, tick, time(nullptr), customFonts); + showContents2(contents, tick, time(nullptr)); + std::ostringstream buf; + buf << "Animation Tick: " << tick << "\n"; + std::cout << buf.str() << std::flush; + std::this_thread::sleep_for(std::chrono::milliseconds{40}); + ++tick; + } + } catch (std::exception& e) { + std::ostringstream buf; + buf << argv[0] << ": " << e.what() << "\n"; + std::cerr << buf.str(); + return 2; + } return 0; } diff --git a/cpp/src/CMakeLists.txt b/cpp/src/CMakeLists.txt index b26967e..b0ebe6c 100644 --- a/cpp/src/CMakeLists.txt +++ b/cpp/src/CMakeLists.txt @@ -1,10 +1,14 @@ set(monoformat_SOURCES + monoformat_schema.cpp monoformat_structured.cpp + monoformat_parseonly.cpp monoformat_fontreader.cpp ) set(monoformat_HEADERS + monoformat_schema.hpp monoformat_structured.hpp monoformat_parsehelpers.hpp + monoformat_parseonly.hpp monoformat_fontreader.hpp monoformat_gfx.hpp monoformat_utf8.hpp diff --git a/cpp/src/monoformat_fontreader.cpp b/cpp/src/monoformat_fontreader.cpp index fa4883b..47921ff 100644 --- a/cpp/src/monoformat_fontreader.cpp +++ b/cpp/src/monoformat_fontreader.cpp @@ -5,10 +5,10 @@ namespace monoformat { #include "u8g2_font_NokiaSmallPlain_tf.inc.cpp" #include "u8g2_font_5x7_mf.inc.cpp" -std::span findEmbeddedFont(std::string_view name) { - if (name == "NokiaSmallPlain_tf") { +std::span findEmbeddedFont(std::size_t fontIndex) { + if (fontIndex == 0) { return std::as_bytes(std::span{u8g2_font_NokiaSmallPlain_tf_u8g2font, u8g2_font_NokiaSmallPlain_tf_u8g2font_len}); - } else if (name == "5x7_mf") { + } else if (fontIndex == 1) { return std::as_bytes(std::span{u8g2_font_5x7_mf_u8g2font, u8g2_font_5x7_mf_u8g2font_len}); } else { return {}; diff --git a/cpp/src/monoformat_fontreader.hpp b/cpp/src/monoformat_fontreader.hpp index 96e3233..dcdf69a 100644 --- a/cpp/src/monoformat_fontreader.hpp +++ b/cpp/src/monoformat_fontreader.hpp @@ -652,7 +652,7 @@ inline FontRenderer::FontRenderer(std::span fontData) { } -extern std::span findEmbeddedFont(std::string_view name); +extern std::span findEmbeddedFont(std::size_t fontIndex); } // namespace monoformat diff --git a/cpp/src/monoformat_parsehelpers.hpp b/cpp/src/monoformat_parsehelpers.hpp index 5901595..06b2462 100644 --- a/cpp/src/monoformat_parsehelpers.hpp +++ b/cpp/src/monoformat_parsehelpers.hpp @@ -328,8 +328,8 @@ inline std::size_t writeU64BE(std::span& target, std::size_t pos, std inline std::size_t writeBuffer(std::span& target, std::size_t pos, std::span value) { if ((pos + value.size()) > target.size()) { - if (pos < value.size()) { - std::size_t const n = value.size() - pos; + if (pos < target.size()) { + std::size_t const n = target.size() - pos; std::copy(value.begin(), value.begin() + n, target.begin() + pos); } } else { diff --git a/cpp/src/monoformat_parseonly.cpp b/cpp/src/monoformat_parseonly.cpp new file mode 100644 index 0000000..c50c6d3 --- /dev/null +++ b/cpp/src/monoformat_parseonly.cpp @@ -0,0 +1,1053 @@ +#include "monoformat_parseonly.hpp" +#include "monoformat_parsehelpers.hpp" +#include "monoformat_fontreader.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 = (((*flags) >> 0u) & 0x01u) == 0x01u; + result.drawOnBack = (((*flags) >> 0u) & 0x02u) == 0x02u; + result.clearBeforeDrawing = (((*flags) >> 0u) & 0x04u) == 0x04u; + result.elementData = sectionData; + 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 = (((*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(*startTimestamp); + result.endTimestamp = std::bit_cast(*endTimestamp); + 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); + + 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); + } + + 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); + 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(*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 (*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(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 (*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(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()); + } + + 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}; + + std::span 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(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}; + + std::span 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(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(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()); + } + + std::span 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(*utcOffset) * 60) % 86400; + std::uint32_t hours = seconds / 3600; + std::uint32_t minutes = (seconds - hours * 3600) / 60; + seconds = seconds % 60; + if (use12h) { + hours = hours % 12; + if (hours == 0) { + hours = 12; + } + } + + std::string text; + if (showHours && showMinutes && showSeconds) { + text = std::format("{:02d}:{:02d}:{:02d}", hours, minutes, seconds); + } else if (showHours && showMinutes) { + text = std::format("{:02d}:{:02d}", hours, minutes); + } else if (showHours) { + text = std::format("{:02d}", hours); + } else if (showMinutes && showSeconds) { + text = std::format("{:02d}:{:02d}", minutes, seconds); + } else if (showSeconds) { + text = std::format("{:02d}", seconds); + } else { + return {}; + } + + renderer.render(text, Point{0, static_cast(renderer.lineHeight() - 1)}, target, true, false); + + return {}; +} + +} // namespace monoformat + diff --git a/cpp/src/monoformat_parseonly.hpp b/cpp/src/monoformat_parseonly.hpp new file mode 100644 index 0000000..56ee626 --- /dev/null +++ b/cpp/src/monoformat_parseonly.hpp @@ -0,0 +1,15 @@ +#ifndef MONOFORMAT_PARSEONLY_HPP +#define MONOFORMAT_PARSEONLY_HPP + +#include "monoformat_parsehelpers.hpp" +#include "monoformat_schema.hpp" + +namespace monoformat { + +std::expected parseAndApply(std::span data, OneBitBufferInterface* buffer, + std::uint16_t screenWidth, std::uint16_t screenHeight, + bool isFront, std::size_t animationTick, std::int64_t currentTimestamp); + +} // namespace monoformat + +#endif // MONOFORMAT_PARSEONLY_HPP diff --git a/cpp/src/monoformat_schema.cpp b/cpp/src/monoformat_schema.cpp new file mode 100644 index 0000000..d1f6f42 --- /dev/null +++ b/cpp/src/monoformat_schema.cpp @@ -0,0 +1,103 @@ +#include "monoformat_schema.hpp" + +#include + +namespace monoformat { + +MemoryOneBitBuffer::MemoryOneBitBuffer(std::span buffer, std::uint16_t width, std::uint16_t height) + : m_width{width} + , m_height{height} + , m_buffer{buffer} +{ + std::size_t expectedSize = (m_width * m_height + 8 - 1) / 8; + if (m_buffer.size() < expectedSize) { + m_width = 0; + m_height = 0; + } +} + +MemoryOneBitBuffer::~MemoryOneBitBuffer() { +} + +bool MemoryOneBitBuffer::isPixelSet(std::uint16_t x, std::uint16_t y) const { + if (x >= m_width || y >= m_height) { + return false; + } + std::size_t pxIndex = static_cast(y) * m_width + x; + std::size_t byteIndex = pxIndex / 8; + std::size_t bitIndex = pxIndex % 8; + return ((static_cast(m_buffer[byteIndex]) >> bitIndex) & 0x01) == 0x01; +} + +void MemoryOneBitBuffer::setPixel(std::uint16_t x, std::uint16_t y, bool value) { + if (x >= m_width || y >= m_height) { + return; + } + std::size_t pxIndex = static_cast(y) * m_width + x; + std::size_t byteIndex = pxIndex / 8; + std::size_t bitIndex = pxIndex % 8; + if (value) { + m_buffer[byteIndex] = static_cast(static_cast(m_buffer[byteIndex]) | (1u << bitIndex)); + } else { + m_buffer[byteIndex] = static_cast(static_cast(m_buffer[byteIndex]) & ~(1u << bitIndex)); + } +} + +ConstMemoryOneBitBuffer::ConstMemoryOneBitBuffer(std::span buffer, std::uint16_t width, std::uint16_t height) + : m_width{width} + , m_height{height} + , m_buffer{buffer} +{ + std::size_t expectedSize = (m_width * m_height + 8 - 1) / 8; + if (m_buffer.size() < expectedSize) { + m_width = 0; + m_height = 0; + } +} + +ConstMemoryOneBitBuffer::~ConstMemoryOneBitBuffer() { +} + +bool ConstMemoryOneBitBuffer::isPixelSet(std::uint16_t x, std::uint16_t y) const { + if (x >= m_width || y >= m_height) { + return false; + } + std::size_t pxIndex = static_cast(y) * m_width + x; + std::size_t byteIndex = pxIndex / 8; + std::size_t bitIndex = pxIndex % 8; + return ((static_cast(m_buffer[byteIndex]) >> bitIndex) & 0x01) == 0x01; +} + +void ConstMemoryOneBitBuffer::setPixel(std::uint16_t x, std::uint16_t y, bool value) { + std::ignore = x; + std::ignore = y; + std::ignore = value; +} + +ClippedImage::ClippedImage(OneBitBufferInterface* underlying, std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height) + : m_underlying{underlying} + , m_x{x} + , m_y{y} + , m_width{width} + , m_height{height} +{ +} + +ClippedImage::~ClippedImage() { +} + +bool ClippedImage::isPixelSet(std::uint16_t x, std::uint16_t y) const { + if (x >= m_width || y >= m_height) { + return false; + } + return m_underlying->isPixelSet(x + m_x, y + m_y); +} + +void ClippedImage::setPixel(std::uint16_t x, std::uint16_t y, bool value) { + if (x >= m_width || y >= m_height) { + return; + } + m_underlying->setPixel(x + m_x, y + m_y, value); +} + +} // namespace monoformat diff --git a/cpp/src/monoformat_schema.hpp b/cpp/src/monoformat_schema.hpp new file mode 100644 index 0000000..42aba69 --- /dev/null +++ b/cpp/src/monoformat_schema.hpp @@ -0,0 +1,77 @@ +#ifndef MONOFORMAT_SCHEMA_HPP +#define MONOFORMAT_SCHEMA_HPP + +#include +#include +#include + +namespace monoformat { + +enum class SectionType { + AlwaysDrawn = 1, + TimeBasedDrawn = 2, + CustomFont = 32, +}; + +enum class ElementType { + Image = 1, + Animation = 2, + HScrollImage = 3, + VScrollImage = 4, + Line = 5, + ClippedText = 16, + HScrollText = 17, + //VScrollText = 18, + CurrentTime = 32, +}; + +enum class LineStyle : std::uint8_t { + Solid = 0, +}; + +struct OneBitBufferInterface { + using Color = bool; + + virtual ~OneBitBufferInterface() = default; + virtual bool isPixelSet(std::uint16_t x, std::uint16_t y) const = 0; + virtual void setPixel(std::uint16_t x, std::uint16_t y, bool value) = 0; +}; + +struct MemoryOneBitBuffer : public OneBitBufferInterface { + MemoryOneBitBuffer(std::span buffer, std::uint16_t width, std::uint16_t height); + virtual ~MemoryOneBitBuffer() override; + virtual bool isPixelSet(std::uint16_t x, std::uint16_t y) const override; + virtual void setPixel(std::uint16_t x, std::uint16_t y, bool value) override; +private: + std::uint16_t m_width{}; + std::uint16_t m_height{}; + std::span m_buffer; +}; + +struct ConstMemoryOneBitBuffer : public OneBitBufferInterface { + ConstMemoryOneBitBuffer(std::span buffer, std::uint16_t width, std::uint16_t height); + virtual ~ConstMemoryOneBitBuffer() override; + virtual bool isPixelSet(std::uint16_t x, std::uint16_t y) const override; + virtual void setPixel(std::uint16_t x, std::uint16_t y, bool value) override; +private: + std::uint16_t m_width{}; + std::uint16_t m_height{}; + std::span m_buffer; +}; + +struct ClippedImage : public OneBitBufferInterface { + ClippedImage(OneBitBufferInterface* underlying, std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height); + virtual ~ClippedImage() override; + virtual bool isPixelSet(std::uint16_t x, std::uint16_t y) const override; + virtual void setPixel(std::uint16_t x, std::uint16_t y, bool value) override; +private: + OneBitBufferInterface* m_underlying{}; + std::uint16_t m_x{}; + std::uint16_t m_y{}; + std::uint16_t m_width{}; + std::uint16_t m_height{}; +}; + +} // namespace monoformat + +#endif // MONOFORMAT_SCHEMA_HPP diff --git a/cpp/src/monoformat_structured.cpp b/cpp/src/monoformat_structured.cpp index 0bd3f36..c56a765 100644 --- a/cpp/src/monoformat_structured.cpp +++ b/cpp/src/monoformat_structured.cpp @@ -5,102 +5,6 @@ namespace monoformat { -MemoryOneBitBuffer::MemoryOneBitBuffer(std::span buffer, std::uint16_t width, std::uint16_t height) - : m_width{width} - , m_height{height} - , m_buffer{buffer} -{ - std::size_t expectedSize = (m_width * m_height + 8 - 1) / 8; - if (m_buffer.size() < expectedSize) { - m_width = 0; - m_height = 0; - } -} - -MemoryOneBitBuffer::~MemoryOneBitBuffer() { -} - -bool MemoryOneBitBuffer::isPixelSet(std::uint16_t x, std::uint16_t y) const { - if (x >= m_width || y >= m_height) { - return false; - } - std::size_t pxIndex = static_cast(y) * m_width + x; - std::size_t byteIndex = pxIndex / 8; - std::size_t bitIndex = pxIndex % 8; - return ((static_cast(m_buffer[byteIndex]) >> bitIndex) & 0x01) == 0x01; -} - -void MemoryOneBitBuffer::setPixel(std::uint16_t x, std::uint16_t y, bool value) { - if (x >= m_width || y >= m_height) { - return; - } - std::size_t pxIndex = static_cast(y) * m_width + x; - std::size_t byteIndex = pxIndex / 8; - std::size_t bitIndex = pxIndex % 8; - if (value) { - m_buffer[byteIndex] = static_cast(static_cast(m_buffer[byteIndex]) | (1u << bitIndex)); - } else { - m_buffer[byteIndex] = static_cast(static_cast(m_buffer[byteIndex]) & ~(1u << bitIndex)); - } -} - -ConstMemoryOneBitBuffer::ConstMemoryOneBitBuffer(std::span buffer, std::uint16_t width, std::uint16_t height) - : m_width{width} - , m_height{height} - , m_buffer{buffer} -{ - std::size_t expectedSize = (m_width * m_height + 8 - 1) / 8; - if (m_buffer.size() < expectedSize) { - m_width = 0; - m_height = 0; - } -} - -ConstMemoryOneBitBuffer::~ConstMemoryOneBitBuffer() { -} - -bool ConstMemoryOneBitBuffer::isPixelSet(std::uint16_t x, std::uint16_t y) const { - if (x >= m_width || y >= m_height) { - return false; - } - std::size_t pxIndex = static_cast(y) * m_width + x; - std::size_t byteIndex = pxIndex / 8; - std::size_t bitIndex = pxIndex % 8; - return ((static_cast(m_buffer[byteIndex]) >> bitIndex) & 0x01) == 0x01; -} - -void ConstMemoryOneBitBuffer::setPixel(std::uint16_t x, std::uint16_t y, bool value) { - std::ignore = x; - std::ignore = y; - std::ignore = value; -} - -ClippedImage::ClippedImage(OneBitBufferInterface* underlying, std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height) - : m_underlying{underlying} - , m_x{x} - , m_y{y} - , m_width{width} - , m_height{height} -{ -} - -ClippedImage::~ClippedImage() { -} - -bool ClippedImage::isPixelSet(std::uint16_t x, std::uint16_t y) const { - if (x >= m_width || y >= m_height) { - return false; - } - return m_underlying->isPixelSet(x + m_x, y + m_y); -} - -void ClippedImage::setPixel(std::uint16_t x, std::uint16_t y, bool value) { - if (x >= m_width || y >= m_height) { - return; - } - m_underlying->setPixel(x + m_x, y + m_y, value); -} - ImageElement::ImageElement(std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height) : m_x{x} , m_y{y} @@ -1038,7 +942,7 @@ ElementType ClippedTextElement::elementType() const { std::size_t ClippedTextElement::serializeTo(std::span target) const { std::size_t pos = 0; - pos = writeU16LE(target, pos, static_cast(ElementType::HScrollImage)); + pos = writeU16LE(target, pos, static_cast(ElementType::ClippedText)); pos = writeU16LE(target, pos, m_x); pos = writeU16LE(target, pos, m_y); pos = writeU16LE(target, pos, m_width); @@ -1061,13 +965,7 @@ void ClippedTextElement::drawTo(OneBitBufferInterface* imageBuffer, std::size_t } fontData = customFonts[fontIndex]->fontData(); } else { - if (m_fontIndex == 0) { - fontData = findEmbeddedFont("NokiaSmallPlain_tf"); - } else if (m_fontIndex == 1) { - fontData = findEmbeddedFont("5x7_mf"); - } else { - return; - } + fontData = findEmbeddedFont(m_fontIndex); } if (fontData.size() < 23) { return; @@ -1174,7 +1072,7 @@ ElementType HScrollTextElement::elementType() const { std::size_t HScrollTextElement::serializeTo(std::span target) const { std::size_t pos = 0; - pos = writeU16LE(target, pos, static_cast(ElementType::HScrollImage)); + pos = writeU16LE(target, pos, static_cast(ElementType::HScrollText)); pos = writeU16LE(target, pos, m_x); pos = writeU16LE(target, pos, m_y); pos = writeU16LE(target, pos, m_width); @@ -1198,13 +1096,7 @@ void HScrollTextElement::drawTo(OneBitBufferInterface* imageBuffer, std::size_t } fontData = customFonts[fontIndex]->fontData(); } else { - if (m_fontIndex == 0) { - fontData = findEmbeddedFont("NokiaSmallPlain_tf"); - } else if (m_fontIndex == 1) { - fontData = findEmbeddedFont("5x7_mf"); - } else { - return; - } + fontData = findEmbeddedFont(m_fontIndex); } if (fontData.size() < 23) { return; @@ -1395,7 +1287,7 @@ ElementType CurrentTimeElement::elementType() const { std::size_t CurrentTimeElement::serializeTo(std::span target) const { std::size_t pos = 0; - pos = writeU16LE(target, pos, static_cast(ElementType::HScrollImage)); + pos = writeU16LE(target, pos, static_cast(ElementType::CurrentTime)); pos = writeU16LE(target, pos, m_x); pos = writeU16LE(target, pos, m_y); pos = writeU16LE(target, pos, m_width); @@ -1417,13 +1309,7 @@ void CurrentTimeElement::drawTo(OneBitBufferInterface* imageBuffer, std::size_t } fontData = customFonts[fontIndex]->fontData(); } else { - if (m_fontIndex == 0) { - fontData = findEmbeddedFont("NokiaSmallPlain_tf"); - } else if (m_fontIndex == 1) { - fontData = findEmbeddedFont("5x7_mf"); - } else { - return; - } + fontData = findEmbeddedFont(m_fontIndex); } if (fontData.size() < 23) { return; @@ -1923,7 +1809,7 @@ std::size_t serializeFile(std::span& buffer, File const& file) { pos = writeU8LE(buffer, pos, 0xAF); pos = writeU8LE(buffer, pos, 0x7E); pos = writeU8LE(buffer, pos, 0x2B); - pos = writeU8LE(buffer, pos, 0x64); + pos = writeU8LE(buffer, pos, 0x63); pos = writeU32LE(buffer, pos, 1); pos = writeU16LE(buffer, pos, file.sections.size()); pos = writeU16LE(buffer, pos, 0); diff --git a/cpp/src/monoformat_structured.hpp b/cpp/src/monoformat_structured.hpp index 6de27b5..7d593be 100644 --- a/cpp/src/monoformat_structured.hpp +++ b/cpp/src/monoformat_structured.hpp @@ -2,6 +2,7 @@ #define MONOFORMAT_STRUCTURED_HPP #include "monoformat_parsehelpers.hpp" +#include "monoformat_schema.hpp" #include #include @@ -11,12 +12,6 @@ namespace monoformat { -enum class SectionType { - AlwaysDrawn = 1, - TimeBasedDrawn = 2, - CustomFont = 32, -}; - struct Section { virtual ~Section() = default; virtual SectionType sectionType() const = 0; @@ -28,26 +23,6 @@ protected: class CustomFontSection; -enum class ElementType { - Image = 1, - Animation = 2, - HScrollImage = 3, - VScrollImage = 4, - Line = 5, - ClippedText = 16, - HScrollText = 17, - //VScrollText = 18, - CurrentTime = 32, -}; - -struct OneBitBufferInterface { - using Color = bool; - - virtual ~OneBitBufferInterface() = default; - virtual bool isPixelSet(std::uint16_t x, std::uint16_t y) const = 0; - virtual void setPixel(std::uint16_t x, std::uint16_t y, bool value) = 0; -}; - struct Element { virtual ~Element() = default; virtual ElementType elementType() const = 0; @@ -58,41 +33,6 @@ protected: Element() = default; }; -struct MemoryOneBitBuffer : public OneBitBufferInterface { - MemoryOneBitBuffer(std::span buffer, std::uint16_t width, std::uint16_t height); - virtual ~MemoryOneBitBuffer() override; - virtual bool isPixelSet(std::uint16_t x, std::uint16_t y) const override; - virtual void setPixel(std::uint16_t x, std::uint16_t y, bool value) override; -private: - std::uint16_t m_width{}; - std::uint16_t m_height{}; - std::span m_buffer; -}; - -struct ConstMemoryOneBitBuffer : public OneBitBufferInterface { - ConstMemoryOneBitBuffer(std::span buffer, std::uint16_t width, std::uint16_t height); - virtual ~ConstMemoryOneBitBuffer() override; - virtual bool isPixelSet(std::uint16_t x, std::uint16_t y) const override; - virtual void setPixel(std::uint16_t x, std::uint16_t y, bool value) override; -private: - std::uint16_t m_width{}; - std::uint16_t m_height{}; - std::span m_buffer; -}; - -struct ClippedImage : public OneBitBufferInterface { - ClippedImage(OneBitBufferInterface* underlying, std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height); - virtual ~ClippedImage() override; - virtual bool isPixelSet(std::uint16_t x, std::uint16_t y) const override; - virtual void setPixel(std::uint16_t x, std::uint16_t y, bool value) override; -private: - OneBitBufferInterface* m_underlying{}; - std::uint16_t m_x{}; - std::uint16_t m_y{}; - std::uint16_t m_width{}; - std::uint16_t m_height{}; -}; - struct ImageElement : public Element { ImageElement(std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height); @@ -223,10 +163,6 @@ private: std::vector m_buffer; }; -enum class LineStyle : std::uint8_t { - Solid = 0, -}; - struct LineElement : public Element { LineElement(std::uint16_t originX, std::uint16_t originY, std::uint16_t targetX, std::uint16_t targetY, LineStyle lineStyle, std::uint8_t flags);