C++/Specification: introduce new format version 2 with some additions

- We try to write format version 1 if possible to stay as
   compatible as possible (unless newer features are actually
   used)

 - Format version 2 has new section types:
     - MultiTimeBasedDrawn: with multiple timestamp ranges
     - ExpiryDate: to indicate this file has expired

 - An additional Box element type that fills a box is available

 - Also there are lots of flags to draw things in dark are now
   available - to draw dark pixels on a light background
backup
Christian Seiler 2 weeks ago
parent 8557f7a401
commit c83b4425be
  1. 142
      Specification.rst
  2. 307
      cpp/src/monoformat_parseonly.cpp
  3. 2
      cpp/src/monoformat_parseonly.hpp
  4. 46
      cpp/src/monoformat_schema.hpp
  5. 685
      cpp/src/monoformat_structured.cpp
  6. 99
      cpp/src/monoformat_structured.hpp

@ -77,9 +77,11 @@ The following possible values for the version field exist:
+===========+============================================+
| ``0`` | Illegal |
+-----------+--------------------------------------------+
| ``1`` | The current version of this specification. |
| ``1`` | A previous version of this specification. |
+-----------+--------------------------------------------+
| ``2`` .. | Reserved for future use |
| ``2`` | The current version of this specification. |
+-----------+--------------------------------------------+
| ``3`` .. | Reserved for future use |
+-----------+--------------------------------------------+
A value of ``0`` is illegal for the version field and indivates a problem with the file. The current specification version is ``1``. Writers that conform to this specification **MUST** use this value.
@ -118,8 +120,12 @@ The section type specifies what kind of section this is. The following section t
+-----------+---------------------------------------------------+
| ``2`` | List of drawn elements (timespan) |
+-----------+---------------------------------------------------+
| ``3`` .. | Reserved for future use |
| ``31`` | |
| ``3`` | List of drawn elements (list of timespans) |
+-----------+---------------------------------------------------+
| ``4`` .. | Reserved for future use |
| ``30`` | |
+-----------+---------------------------------------------------+
| ``31`` | Expiry Date |
+-----------+---------------------------------------------------+
| ``32`` | Custom Font |
+-----------+---------------------------------------------------+
@ -190,6 +196,52 @@ This section is similar to the "List of drawn elements (always)" section, but th
The additional fields are two 64bit POSIX timestamps containing the start and end time during which this section is to be drawn. If the current timestamp is larger than or equal to the start timestamp, and the current timestamp is smaller than the end timestamp, the section will be shown, otherwise it will be skipped.
List of drawn elements (list of timespans)
------------------------------------------
This section is similar to the "List of drawn elements (timespan)" section, but the header contains a list of timestamp ranges, not just a single range:
0 1 2 3
+------------------------+------------------------+------------------------+------------------------+
0 | Flags | Number of Elements |
+------------------------+------------------------+------------------------+------------------------+
4 | Number of Timestamps | Reserved |
+------------------------+------------------------+------------------------+------------------------+
8 | First Start Timestamp (Low) |
+------------------------+------------------------+------------------------+------------------------+
12 | First Start Timestamp (High) |
+------------------------+------------------------+------------------------+------------------------+
16 | First End Timestamp (Low) |
+------------------------+------------------------+------------------------+------------------------+
20 | First End Timestamp (High) |
+------------------------+------------------------+------------------------+------------------------+
24 | Second Start Timestamp (Low) |
+------------------------+------------------------+------------------------+------------------------+
28 | Second Start Timestamp (High) |
+------------------------+------------------------+------------------------+------------------------+
32 | Second End Timestamp (Low) |
+------------------------+------------------------+------------------------+------------------------+
36 | Second End Timestamp (High) |
+------------------------+------------------------+------------------------+------------------------+
40.. | Additional timestamps .... |
+------------------------+------------------------+------------------------+------------------------+
After the last timestamp the list of elements follows (as with the "List of drawn elements (always)" and "List of drawn elements (timespan)" elements).
File Global Expiry Date
-----------------------
If this section is present it contains the timestamp that describes the date after which the file is no longer valid. This allows the user to generate
files that will be discarded if the current date is already past the expiry timestamp, and will automatically stop the file from being displayed
once the expiry timestamp is reached.
0 1 2 3
+------------------------+------------------------+------------------------+------------------------+
0 | Expiry Date Timestamp (Low) |
+------------------------+------------------------+------------------------+------------------------+
4 | Expiry Date Timestamp (High) |
+------------------------+------------------------+------------------------+------------------------+
Custom Font
-----------
@ -331,13 +383,61 @@ Line
8 | Y Target | Line Style | Flags |
+------------------------+------------------------+-------------------------------------------------+
Line Style
``````````
+-----------+---------------------------------------------------+
| Bit | Description |
+===========+===================================================+
| ``0`` | Solid line |
+-----------+---------------------------------------------------+
| ``1`` .. | Reserved for future use |
| ``255`` | |
+-----------+---------------------------------------------------+
Flags
`````
+-----------+---------------------------------------------------+
| Bit | Description |
+===========+===================================================+
| ``0`` | Invert Pixel Values |
| ``0`` | Line color is dark, not white |
+-----------+---------------------------------------------------+
| ``1`` .. | Reserved for future use |
| ``7`` | |
+-----------+---------------------------------------------------+
Box
---
0 1 2 3
+------------------------+------------------------+------------------------+------------------------+
0 | Type: 6 | X |
+------------------------+------------------------+------------------------+------------------------+
4 | Y | Width |
+------------------------+------------------------+------------------------+------------------------+
8 | Height | Fill Pattern | Flags |
+------------------------+------------------------+-------------------------------------------------+
Fill Pattern
````````````
+-----------+---------------------------------------------------+
| Bit | Description |
+===========+===================================================+
| ``0`` | Solid |
+-----------+---------------------------------------------------+
| ``1`` .. | Reserved for future use |
| ``255`` | |
+-----------+---------------------------------------------------+
Flags
`````
+-----------+---------------------------------------------------+
| Bit | Description |
+===========+===================================================+
| ``0`` | Fill color is dark, not white |
+-----------+---------------------------------------------------+
| ``1`` .. | Reserved for future use |
| ``7`` | |
@ -352,13 +452,25 @@ Clipped Text
+------------------------+------------------------+------------------------+------------------------+
4 | Y Offset | Width |
+------------------------+------------------------+------------------------+------------------------+
8 | Height | Font Index |
8 | Height | Text Flags | Reserved |
+------------------------+------------------------+------------------------+------------------------+
12 | Text Length | Text ... |
12 | Font Index | Text Length |
+------------------------+------------------------+------------------------+------------------------+
... | ... Text | Padding (if required) |
16.. | Text ... | Padding (if required) |
+------------------------+------------------------+------------------------+------------------------+
Text Flags
``````````
+-----------+---------------------------------------------------+
| Bit | Description |
+===========+===================================================+
| ``0`` | Text is dark, background is white |
+-----------+---------------------------------------------------+
| ``1`` .. | Reserved for future use |
| ``7`` | |
+-----------+---------------------------------------------------+
Font Index
``````````
@ -377,11 +489,13 @@ Horizontally Scrolling Text
+------------------------+------------------------+------------------------+------------------------+
4 | Y Offset | Width |
+------------------------+------------------------+------------------------+------------------------+
8 | Height | Flags | Scroll Speed |
8 | Height | Text Flags | Reserved |
+------------------------+------------------------+------------------------+------------------------+
12 | Font Index | Text Length |
12 | Flags | Scroll Speed | Reserved |
+------------------------+------------------------+------------------------+------------------------+
... | ... Text | Padding (if required) |
16 | Font Index | Text Length |
+------------------------+------------------------+------------------------+------------------------+
20.. | ... Text | Padding (if required) |
+------------------------+------------------------+------------------------+------------------------+
Current Time
@ -393,9 +507,11 @@ Current Time
+------------------------+------------------------+------------------------+------------------------+
4 | Y Offset | Width |
+------------------------+------------------------+------------------------+------------------------+
8 | Height | Font Index |
8 | Height | Text Flags | Reserved |
+------------------------+------------------------+------------------------+------------------------+
12 | Font Index | UTC Offset (Minutes) |
+------------------------+------------------------+------------------------+------------------------+
12 | UTC Offset (Minutes) | Flags |
16 | Flags | Reserved |
+------------------------+------------------------+------------------------+------------------------+
UTC Offset

@ -23,11 +23,18 @@ struct TimeBasedDrawnSectionMetaData {
std::int64_t endTimestamp{};
};
struct MultiTimeBasedDrawnSectionMetaData {
AlwaysDrawnSectionMetaData base;
std::size_t rangeCount{};
std::span<std::byte const> rangeData;
};
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, std::uint32_t formatVersion);
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, std::uint32_t formatVersion);
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, std::uint32_t formatVersion);
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, std::uint32_t formatVersion);
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, std::uint32_t formatVersion);
static std::expected<void, ParseError> handleBox(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::uint32_t formatVersion);
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, std::uint32_t formatVersion);
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, std::uint32_t formatVersion);
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, std::uint32_t formatVersion);
@ -124,6 +131,62 @@ static std::expected<TimeBasedDrawnSectionMetaData, ParseError> parseTimeBasedDr
return result;
}
static std::expected<MultiTimeBasedDrawnSectionMetaData, ParseError> parseMultiTimeBasedDrawnSection(std::span<std::byte const>& data, std::uint32_t formatVersion) {
std::ignore = formatVersion;
auto type = readU8LE(data);
if (!type) {
return std::unexpected(type.error());
}
if (*type != static_cast<std::uint8_t>(SectionType::MultiTimeBasedDrawn)) {
return std::unexpected(ParseError::InvalidValue);
}
auto size = readU24LE(data);
if (!size) {
return std::unexpected(size.error());
}
if (*size < 12) {
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 rangeCount = readU16LE(data);
if (!rangeCount) {
return std::unexpected(rangeCount.error());
}
if (((*rangeCount) * 16u + 12u) > *size) {
return std::unexpected(ParseError::InvalidValue);
}
auto reserved1 = readU16LE(data);
if (!reserved1) {
return std::unexpected(reserved1.error());
}
auto sectionData = data.subspan(0, *size - 12 - 16 * (*rangeCount));
auto rangeData = data.subspan(12, 16 * (*rangeCount));
data = data.subspan(*size - 12 - 16 * (*rangeCount));
MultiTimeBasedDrawnSectionMetaData result;
result.base.drawOnFront = isBitSet(*flags, 0);
result.base.drawOnBack = isBitSet(*flags, 1);
result.base.clearBeforeDrawing = isBitSet(*flags, 2);
result.base.elementData = sectionData;
result.rangeCount = *rangeCount;
result.rangeData = rangeData;
#if defined(DEBUG)
std::cerr << "[Debug] MultiTimeBasedDrawnSection, drawOnFront = " << result.base.drawOnFront << ", drawOnBack = " << result.base.drawOnBack << ", clearBeforeDrawing = " << result.base.clearBeforeDrawing << ", element data size = " << result.base.elementData.size() << " bytes, # timestamps = " << result.rangeCount << std::endl;
#endif
return result;
}
static std::expected<std::span<std::byte const>, ParseError> parseCustomFontSection(std::span<std::byte const>& data, std::uint32_t formatVersion) {
std::ignore = formatVersion;
@ -176,7 +239,7 @@ std::expected<void, ParseError> parseAndApply(std::span<std::byte const> data, O
if (!version) {
return std::unexpected(version.error());
}
if (*version != 1) {
if (*version < 1 || *version > 2) {
return std::unexpected(ParseError::InvalidValue);
}
auto sectionCount = readU16LE(data);
@ -211,6 +274,9 @@ std::expected<void, ParseError> parseAndApply(std::span<std::byte const> data, O
customFonts[customFontIndex++] = *fontData;
}
continue;
} else if (static_cast<SectionType>(*sectionType) == SectionType::ExpiryDate) {
// Ignore expiry date sections here
continue;
}
AlwaysDrawnSectionMetaData section;
@ -229,6 +295,30 @@ std::expected<void, ParseError> parseAndApply(std::span<std::byte const> data, O
continue;
}
section = metaData->base;
} else if (static_cast<SectionType>(*sectionType) == SectionType::MultiTimeBasedDrawn) {
auto metaData = parseMultiTimeBasedDrawnSection(*rawSectionData, *version);
if (!metaData) {
return std::unexpected(metaData.error());
}
bool inRange = false;
for (std::size_t i = 0; i < metaData->rangeCount; ++i) {
auto startTimestamp = readU64LE(metaData->rangeData);
if (!startTimestamp) {
return std::unexpected(startTimestamp.error());
}
auto endTimestamp = readU64LE(metaData->rangeData);
if (!endTimestamp) {
return std::unexpected(endTimestamp.error());
}
if (currentTimestamp >= std::bit_cast<std::int64_t>(*startTimestamp) && currentTimestamp < std::bit_cast<std::int64_t>(*endTimestamp)) {
inRange = true;
break;
}
}
if (!inRange) {
continue;
}
section = metaData->base;
} else {
continue;
}
@ -260,6 +350,7 @@ std::expected<void, ParseError> parseAndApply(std::span<std::byte const> data, O
case ElementType::HScrollImage: parseResult = handleHScrollImage(section.elementData, screen, animationTick, currentTimestamp, std::span{customFonts}.subspan(0, customFontIndex), *version); break;
case ElementType::VScrollImage: parseResult = handleVScrollImage(section.elementData, screen, animationTick, currentTimestamp, std::span{customFonts}.subspan(0, customFontIndex), *version); break;
case ElementType::Line: parseResult = handleLine(section.elementData, screen, animationTick, currentTimestamp, std::span{customFonts}.subspan(0, customFontIndex), *version); break;
case ElementType::Box: parseResult = handleBox(section.elementData, screen, animationTick, currentTimestamp, std::span{customFonts}.subspan(0, customFontIndex), *version); break;
case ElementType::ClippedText: parseResult = handleClippedText(section.elementData, screen, animationTick, currentTimestamp, std::span{customFonts}.subspan(0, customFontIndex), *version); break;
case ElementType::HScrollText: parseResult = handleHScrollText(section.elementData, screen, animationTick, currentTimestamp, std::span{customFonts}.subspan(0, customFontIndex), *version); break;
case ElementType::CurrentTime: parseResult = handleCurrentTime(section.elementData, screen, animationTick, currentTimestamp, std::span{customFonts}.subspan(0, customFontIndex), *version); break;
@ -275,6 +366,70 @@ std::expected<void, ParseError> parseAndApply(std::span<std::byte const> data, O
return {};
}
std::expected<bool, ParseError> parseAndCheckExpiry(std::span<std::byte const> data, std::int64_t currentTimestamp) {
auto magic = readU32LE(data);
if (!magic) {
return std::unexpected(magic.error());
}
if (*magic != UINT32_C(0x632B7EAF)) {
return std::unexpected(ParseError::InvalidValue);
}
auto version = readU32LE(data);
if (!version) {
return std::unexpected(version.error());
}
if (*version == 1) {
// Version 1 doesn't ever expire
return false;
}
if (*version < 1 || *version > 2) {
return std::unexpected(ParseError::InvalidValue);
}
auto sectionCount = readU16LE(data);
if (!sectionCount) {
return std::unexpected(sectionCount.error());
}
auto reserved_ = readU16LE(data);
if (!reserved_) {
return std::unexpected(reserved_.error());
}
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::ExpiryDate) {
// Ignore expiry date sections here
continue;
}
auto headerData = rawSectionData->subspan(4);
auto expiryDate = readU64LE(headerData);
if (!expiryDate) {
return std::unexpected(expiryDate.error());
}
if (currentTimestamp >= std::bit_cast<std::int64_t>(*expiryDate)) {
return true;
}
// Don't abort here, there could be multiple expiry dates in the file
}
// The file isn't expired
return false;
}
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, std::uint32_t formatVersion) {
std::ignore = formatVersion;
@ -785,11 +940,10 @@ std::expected<void, ParseError> handleLine(std::span<std::byte const>& buffer, O
#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;
bool value = !lineFlags.dark;
if (dx == 0) {
for (std::int32_t i = 0; i <= dy; ++i) {
@ -810,6 +964,66 @@ std::expected<void, ParseError> handleLine(std::span<std::byte const>& buffer, O
return {};
}
std::expected<void, ParseError> handleBox(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::uint32_t formatVersion) {
std::ignore = formatVersion;
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::Box)) {
return std::unexpected(ParseError::InvalidValue);
}
auto x = readU16LE(buffer);
if (!x) {
return std::unexpected(x.error());
}
auto y = readU16LE(buffer);
if (!y) {
return std::unexpected(y.error());
}
auto width = readU16LE(buffer);
if (!width) {
return std::unexpected(width.error());
}
auto height = readU16LE(buffer);
if (!height) {
return std::unexpected(height.error());
}
auto fillPattern = readU8LE(buffer);
if (!fillPattern) {
return std::unexpected(fillPattern.error());
}
if (*fillPattern != static_cast<std::uint8_t>(FillPattern::Solid)) {
return std::unexpected(ParseError::InvalidValue);
}
auto flags = readU8LE(buffer);
if (!flags) {
return std::unexpected(flags.error());
}
#if defined(DEBUG)
std::cerr << "[Debug] - Box, x = " << (*x) << ", y = " << (*y) << ", width = " << (*width) << ", height = " << (*height)
<< ", fillPattern = " << int(*fillPattern)
<< std::endl;
#endif
FillFlags fillFlags{*flags};
bool value = !fillFlags.dark;
for (std::int32_t dy = 0; dy < *height; ++dy) {
for (std::int32_t dx = 0; dx < *width; ++dx) {
screen->setPixel(dx + *x, dy + *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::uint32_t formatVersion) {
std::ignore = formatVersion;
std::ignore = animationTick;
@ -838,6 +1052,18 @@ std::expected<void, ParseError> handleClippedText(std::span<std::byte const>& bu
if (!height) {
return std::unexpected(height.error());
}
std::expected<std::uint8_t, ParseError> textFlags_ = 0;
if (formatVersion >= 2) {
textFlags_ = readU8LE(buffer);
if (!textFlags_) {
return std::unexpected(textFlags_.error());
}
auto reserved1 = readU8LE(buffer);
if (!reserved1) {
return std::unexpected(reserved1.error());
}
}
TextFlags textFlags{*textFlags_};
auto fontIndex = readU16LE(buffer);
if (!fontIndex) {
return std::unexpected(fontIndex.error());
@ -847,6 +1073,9 @@ std::expected<void, ParseError> handleClippedText(std::span<std::byte const>& bu
return std::unexpected(textLength.error());
}
std::size_t alignedRoundedUp = (*textLength + 2 + 4 - 1) / 4 * 4 - 2;
if (formatVersion >= 2) {
alignedRoundedUp = (*textLength + 4 - 1) / 4 * 4;
}
auto textData = readBuffer(buffer, alignedRoundedUp);
if (!textData) {
return std::unexpected(textData.error());
@ -873,9 +1102,14 @@ std::expected<void, ParseError> handleClippedText(std::span<std::byte const>& bu
if (fontData.size() < 23) {
return {};
}
std::optional<bool> bgColor;
bgColor = textFlags.dark;
bool fgColor = !textFlags.dark;
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);
renderer.render(text, Point{0, static_cast<std::int32_t>(renderer.lineHeight() - 1)}, target, fgColor, bgColor);
return {};
}
@ -907,6 +1141,18 @@ std::expected<void, ParseError> handleHScrollText(std::span<std::byte const>& bu
if (!height) {
return std::unexpected(height.error());
}
std::expected<std::uint8_t, ParseError> textFlags_ = 0;
if (formatVersion >= 2) {
textFlags_ = readU8LE(buffer);
if (!textFlags_) {
return std::unexpected(textFlags_.error());
}
auto reserved1 = readU8LE(buffer);
if (!reserved1) {
return std::unexpected(reserved1.error());
}
}
TextFlags textFlags{*textFlags_};
auto flags = readU8LE(buffer);
if (!flags) {
return std::unexpected(flags.error());
@ -915,6 +1161,12 @@ std::expected<void, ParseError> handleHScrollText(std::span<std::byte const>& bu
if (!scrollSpeed) {
return std::unexpected(scrollSpeed.error());
}
if (formatVersion >= 2) {
auto reserved2 = readU16LE(buffer);
if (!reserved2) {
return std::unexpected(reserved2.error());
}
}
auto fontIndex = readU16LE(buffer);
if (!fontIndex) {
return std::unexpected(fontIndex.error());
@ -967,6 +1219,10 @@ std::expected<void, ParseError> handleHScrollText(std::span<std::byte const>& bu
ScrollFlags scrollFlags{*flags};
std::optional<bool> bgColor;
bgColor = textFlags.dark;
bool fgColor = !textFlags.dark;
bool restarting = !scrollFlags.endless;
bool invert = scrollFlags.invertDirection;
bool padBefore = scrollFlags.padBefore;
@ -974,7 +1230,7 @@ std::expected<void, ParseError> handleHScrollText(std::span<std::byte const>& bu
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);
renderer.render(text, Point{offset, static_cast<std::int32_t>(renderer.lineHeight() - 1)}, target, fgColor, bgColor);
return {};
}
@ -998,45 +1254,51 @@ std::expected<void, ParseError> handleHScrollText(std::span<std::byte const>& bu
std::int32_t partOffset = 0;
if (padBefore) {
if (bgColor) {
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);
target.setPixel(ddx - offset, dy, *bgColor);
}
}
}
partOffset += *width;
}
renderer.render(text, Point{partOffset - offset, static_cast<std::int32_t>(renderer.lineHeight() - 1)}, target, true, false);
renderer.render(text, Point{partOffset - offset, static_cast<std::int32_t>(renderer.lineHeight() - 1)}, target, fgColor, bgColor);
partOffset += contentWidth;
if (padAfter) {
if (bgColor) {
for (std::int32_t dy = 0; dy < *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);
target.setPixel(ddx - offset, dy, *bgColor);
}
}
}
partOffset += *width;
}
if (!restarting) {
if (padBefore) {
if (bgColor) {
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);
target.setPixel(ddx - offset, dy, *bgColor);
}
}
}
partOffset += *width;
}
renderer.render(text, Point{partOffset - offset, static_cast<std::int32_t>(renderer.lineHeight() - 1)}, target, true, false);
renderer.render(text, Point{partOffset - offset, static_cast<std::int32_t>(renderer.lineHeight() - 1)}, target, fgColor, bgColor);
}
return {};
@ -1069,6 +1331,18 @@ std::expected<void, ParseError> handleCurrentTime(std::span<std::byte const>& bu
if (!height) {
return std::unexpected(height.error());
}
std::expected<std::uint8_t, ParseError> textFlags_ = 0;
if (formatVersion >= 2) {
textFlags_ = readU8LE(buffer);
if (!textFlags_) {
return std::unexpected(textFlags_.error());
}
auto reserved1 = readU8LE(buffer);
if (!reserved1) {
return std::unexpected(reserved1.error());
}
}
TextFlags textFlags{*textFlags_};
auto fontIndex = readU16LE(buffer);
if (!fontIndex) {
return std::unexpected(fontIndex.error());
@ -1081,6 +1355,12 @@ std::expected<void, ParseError> handleCurrentTime(std::span<std::byte const>& bu
if (!flags) {
return std::unexpected(flags.error());
}
if (formatVersion >= 2) {
auto reserved2 = readU16LE(buffer);
if (!reserved2) {
return std::unexpected(reserved2.error());
}
}
#if defined(DEBUG)
std::cerr << "[Debug] - CurrentTime, x = " << (*x) << ", y = " << (*y) << ", width = " << (*width) << ", height = " << (*height)
@ -1103,6 +1383,11 @@ std::expected<void, ParseError> handleCurrentTime(std::span<std::byte const>& bu
if (fontData.size() < 23) {
return {};
}
std::optional<bool> bgColor;
bgColor = textFlags.dark;
bool fgColor = !textFlags.dark;
ClippedImage target{screen, *x, *y, *width, *height};
TimeDisplayFlags timeDisplayFlags{*flags};
@ -1144,7 +1429,7 @@ std::expected<void, ParseError> handleCurrentTime(std::span<std::byte const>& bu
return {};
}
renderer.render(text, Point{0, static_cast<std::int32_t>(renderer.lineHeight() - 1)}, target, true, false);
renderer.render(text, Point{0, static_cast<std::int32_t>(renderer.lineHeight() - 1)}, target, fgColor, bgColor);
return {};
}

@ -10,6 +10,8 @@ std::expected<void, ParseError> parseAndApply(std::span<std::byte const> data, O
std::uint16_t screenWidth, std::uint16_t screenHeight,
bool isFront, std::size_t animationTick, std::int64_t currentTimestamp);
std::expected<bool, ParseError> parseAndCheckExpiry(std::span<std::byte const> data, std::int64_t currentTimestamp);
} // namespace monoformat
#endif // MONOFORMAT_PARSEONLY_HPP

@ -13,6 +13,8 @@ namespace monoformat {
enum class SectionType {
AlwaysDrawn = 1,
TimeBasedDrawn = 2,
MultiTimeBasedDrawn = 3,
ExpiryDate = 31,
CustomFont = 32,
};
@ -22,6 +24,7 @@ enum class ElementType {
HScrollImage = 3,
VScrollImage = 4,
Line = 5,
Box = 6,
ClippedText = 16,
HScrollText = 17,
//VScrollText = 18,
@ -59,15 +62,56 @@ struct ScrollFlags {
};
struct LineFlags {
bool dark{};
LineFlags() = default;
explicit LineFlags(std::uint8_t flags) noexcept
: dark{isBitSet(flags, 0)}
{
}
explicit operator std::uint8_t() const noexcept {
std::uint8_t result{};
maybeSetBit(result, 0, dark);
return result;
}
};
enum class FillPattern : std::uint8_t {
Solid = 0,
};
struct FillFlags {
bool dark{};
FillFlags() = default;
explicit FillFlags(std::uint8_t flags) noexcept
: dark{isBitSet(flags, 0)}
{
}
explicit operator std::uint8_t() const noexcept {
std::uint8_t result{};
maybeSetBit(result, 0, dark);
return result;
}
};
struct TextFlags {
bool dark{};
TextFlags() = default;
explicit TextFlags(std::uint8_t flags) noexcept
: dark{isBitSet(flags, 0)}
{
std::ignore = flags;
}
explicit operator std::uint8_t() const noexcept {
std::uint8_t result{};
maybeSetBit(result, 0, dark);
return result;
}
};

File diff suppressed because it is too large Load Diff

@ -210,13 +210,43 @@ private:
LineFlags m_flags;
};
struct BoxElement : public Element {
BoxElement(std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height, FillPattern fillPattern, FillFlags flags);
std::uint16_t x() const noexcept;
std::uint16_t y() const noexcept;
std::uint16_t width() const noexcept;
std::uint16_t height() const noexcept;
FillPattern fillPattern() const noexcept;
FillFlags flags() const noexcept;
virtual ~BoxElement() override;
virtual ElementType elementType() const override;
virtual std::uint32_t minimumFormatVersion() const override;
virtual std::size_t serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const override;
virtual nlohmann::json serializeJSON(std::uint32_t formatVersion) const override;
virtual void drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span<CustomFontSection const*> customFonts) override;
static std::expected<std::unique_ptr<BoxElement>, ParseError> parse(std::span<std::byte const>& buffer, std::uint32_t formatVersion);
static std::unique_ptr<BoxElement> parseJSON(nlohmann::json const& j, std::uint32_t formatVersion);
private:
std::uint16_t m_x{};
std::uint16_t m_y{};
std::uint16_t m_width{};
std::uint16_t m_height{};
FillPattern m_fillPattern{};
FillFlags m_flags;
};
struct ClippedTextElement : public Element {
ClippedTextElement(std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height, std::uint16_t fontIndex, std::string text);
ClippedTextElement(std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height, TextFlags textFlags, std::uint16_t fontIndex, std::string text);
std::uint16_t x() const noexcept;
std::uint16_t y() const noexcept;
std::uint16_t width() const noexcept;
std::uint16_t height() const noexcept;
TextFlags textFlags() const noexcept;
std::uint16_t fontIndex() const noexcept;
std::string text() const;
@ -235,17 +265,19 @@ private:
std::uint16_t m_y{};
std::uint16_t m_width{};
std::uint16_t m_height{};
TextFlags m_textFlags;
std::uint16_t m_fontIndex{};
std::string m_text;
};
struct HScrollTextElement : public Element {
HScrollTextElement(std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height, ScrollFlags flags, std::uint8_t scrollSpeed, std::uint16_t fontIndex, std::string text);
HScrollTextElement(std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height, TextFlags textFlags, ScrollFlags flags, std::uint8_t scrollSpeed, std::uint16_t fontIndex, std::string text);
std::uint16_t x() const noexcept;
std::uint16_t y() const noexcept;
std::uint16_t width() const noexcept;
std::uint16_t height() const noexcept;
TextFlags textFlags() const noexcept;
ScrollFlags flags() const noexcept;
std::uint8_t scrollSpeed() const noexcept;
std::uint16_t fontIndex() const noexcept;
@ -266,6 +298,7 @@ private:
std::uint16_t m_y{};
std::uint16_t m_width{};
std::uint16_t m_height{};
TextFlags m_textFlags;
ScrollFlags m_flags;
std::uint8_t m_scrollSpeed{};
std::uint16_t m_fontIndex{};
@ -273,12 +306,13 @@ private:
};
struct CurrentTimeElement : public Element {
CurrentTimeElement(std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height, std::uint16_t fontIndex, std::uint16_t utcOffset, TimeDisplayFlags flags);
CurrentTimeElement(std::uint16_t x, std::uint16_t y, std::uint16_t width, std::uint16_t height, TextFlags textFlags, std::uint16_t fontIndex, std::uint16_t utcOffset, TimeDisplayFlags flags);
std::uint16_t x() const noexcept;
std::uint16_t y() const noexcept;
std::uint16_t width() const noexcept;
std::uint16_t height() const noexcept;
TextFlags textFlags() const noexcept;
std::uint16_t fontIndex() const noexcept;
std::uint16_t utcOffset() const noexcept;
TimeDisplayFlags flags() const noexcept;
@ -298,6 +332,7 @@ private:
std::uint16_t m_y{};
std::uint16_t m_width{};
std::uint16_t m_height{};
TextFlags m_textFlags{};
std::uint16_t m_fontIndex{};
std::uint16_t m_utcOffset{};
TimeDisplayFlags m_flags;
@ -380,6 +415,64 @@ private:
bool m_clearBeforeDrawing{true};
};
struct MultiTimeBasedDrawnSection : public Section {
MultiTimeBasedDrawnSection() = default;
virtual ~MultiTimeBasedDrawnSection() override;
bool doesDrawOnFront() const noexcept;
bool doesDrawOnBack() const noexcept;
bool doesClearBeforeDrawing() const noexcept;
void setDrawOnFront(bool value) noexcept;
void setDrawOnBack(bool value) noexcept;
void setClearBeforeDrawing(bool value) noexcept;
std::size_t elementCount() const noexcept;
Element* elementAt(std::size_t index) const;
void appendElement(std::unique_ptr<Element> element);
void insertElement(std::size_t index, std::unique_ptr<Element> element);
std::unique_ptr<Element> replaceElement(std::size_t index, std::unique_ptr<Element> element);
std::unique_ptr<Element> eraseElement(std::size_t index);
std::vector<std::pair<std::int64_t, std::int64_t>> timeRanges() const;
void setTimeRanges(std::vector<std::pair<std::int64_t, std::int64_t>> ranges);
virtual SectionType sectionType() const override;
virtual std::uint32_t minimumFormatVersion() const override;
virtual std::size_t serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const override;
virtual nlohmann::json serializeJSON(std::uint32_t formatVersion) const override;
static std::expected<std::unique_ptr<MultiTimeBasedDrawnSection>, ParseError> parse(std::span<std::byte const>& buffer, std::uint32_t formatVersion);
static std::unique_ptr<MultiTimeBasedDrawnSection> parseJSON(nlohmann::json const& j, std::uint32_t formatVersion);
private:
std::vector<std::unique_ptr<Element>> m_elements;
std::vector<std::pair<std::int64_t, std::int64_t>> m_timeRanges;
bool m_drawOnFront{true};
bool m_drawOnBack{true};
bool m_clearBeforeDrawing{true};
};
struct ExpiryDateSection : public Section {
ExpiryDateSection() = default;
virtual ~ExpiryDateSection() override;
std::int64_t expiryDate() const noexcept;
void setExpiryDate(std::int64_t value) noexcept;
virtual SectionType sectionType() const override;
virtual std::uint32_t minimumFormatVersion() const override;
virtual std::size_t serializeTo(std::span<std::byte> target, std::uint32_t formatVersion) const override;
virtual nlohmann::json serializeJSON(std::uint32_t formatVersion) const override;
static std::expected<std::unique_ptr<ExpiryDateSection>, ParseError> parse(std::span<std::byte const>& buffer, std::uint32_t formatVersion);
static std::unique_ptr<ExpiryDateSection> parseJSON(nlohmann::json const& j, std::uint32_t formatVersion);
private:
std::int64_t m_expiryDate{};
};
struct CustomFontSection : public Section {
CustomFontSection() = default;
virtual ~CustomFontSection() override;

Loading…
Cancel
Save