18
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-14 20:20:51 +01:00

Lua script screens beside the WebAssembly ones

This commit is contained in:
2026-11-10 15:29:25 +01:00
parent a73585dca0
commit c4924f756d
34 changed files with 3777 additions and 1635 deletions

View File

@@ -0,0 +1,294 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "scripting/scriptscreendrawing.h"
#include <algorithm>
#include <charconv>
namespace script_screens
{
namespace
{
struct sequence
{
std::size_t length{1};
char32_t first_bits{0};
};
// the length of the UTF-8 sequence starting with the byte and the bits it carries; length 0 for a byte that can't
// start one
sequence utf8_sequence(std::uint8_t const Lead)
{
if (Lead < 0x80)
{
return {1, Lead};
}
if (Lead >= 0xC2 && Lead <= 0xDF)
{
return {2, static_cast<char32_t>(Lead & 0x1F)};
}
if (Lead >= 0xE0 && Lead <= 0xEF)
{
return {3, static_cast<char32_t>(Lead & 0x0F)};
}
if (Lead >= 0xF0 && Lead <= 0xF4)
{
return {4, static_cast<char32_t>(Lead & 0x07)};
}
return {0, 0};
}
bool continuation(std::uint8_t const Byte)
{
return (Byte & 0xC0) == 0x80;
}
// a pixel is lit unless it is opaque white, as led_util compares it with (255, 255, 255) and (255, 255, 255, 255)
bool lit_pixel(std::span<std::uint8_t const> const Rgba, int const Width, int const X, int const Y)
{
auto const at{(static_cast<std::size_t>(Y) * Width + X) * 4};
return false == (Rgba[at] == 255 && Rgba[at + 1] == 255 && Rgba[at + 2] == 255 && Rgba[at + 3] == 255);
}
// the columns [First, First + Count) of the image as a glyph; columns past the image's edge are unlit
glyph cut_glyph(std::span<std::uint8_t const> const Rgba, int const Width, int const Height, int const First, int const Count)
{
std::vector<bool> lit(static_cast<std::size_t>(Count) * Height, false);
for (auto column = 0; column < Count; ++column)
{
auto const x{First + column};
if (x < 0 || x >= Width)
{
continue;
}
for (auto y = 0; y < Height; ++y)
{
lit[static_cast<std::size_t>(column) * Height + y] = lit_pixel(Rgba, Width, x, y);
}
}
return {Count, Height, std::move(lit)};
}
// the lines of the text, without the line ends (\n or \r\n, as python's text files read them)
std::vector<std::string_view> lines(std::string_view Text)
{
std::vector<std::string_view> result;
while (false == Text.empty())
{
auto const end{std::min(Text.find('\n'), Text.size())};
auto line{Text.substr(0, end)};
if (false == line.empty() && line.back() == '\r')
{
line.remove_suffix(1);
}
result.push_back(line);
Text.remove_prefix(std::min(end + 1, Text.size()));
}
return result;
}
} // namespace
std::u32string decode_utf8(std::string_view const Text)
{
std::u32string result;
std::size_t idx{0};
while (idx < Text.size())
{
auto const lead{static_cast<std::uint8_t>(Text[idx])};
auto [length, code]{utf8_sequence(lead)};
auto valid{length > 0 && idx + length <= Text.size()};
for (std::size_t offset = 1; valid && offset < length; ++offset)
{
auto const byte{static_cast<std::uint8_t>(Text[idx + offset])};
valid = continuation(byte);
code = (code << 6) | (byte & 0x3F);
}
if (false == valid)
{
result.push_back(lead);
++idx;
continue;
}
result.push_back(code);
idx += length;
}
return result;
}
std::optional<bitmap_font> bitmap_font::parse(std::span<std::uint8_t const> const Rgba, int const Width, int const Height, std::string_view const Description)
{
if (Width <= 0 || Height <= 0 || Rgba.size() < static_cast<std::size_t>(Width) * Height * 4)
{
return std::nullopt;
}
bitmap_font font;
font.m_height = Height;
auto const all{lines(Description)};
auto x{0};
// the first line is a comment
for (std::size_t line = 1; line < all.size(); ++line)
{
auto const characters{decode_utf8(all[line])};
if (characters.empty())
{
continue;
}
// the character, then its width in digits
auto const lead{utf8_sequence(static_cast<std::uint8_t>(all[line][0])).length};
auto digits{all[line].substr(std::max<std::size_t>(lead, 1))};
// int() of python lets spaces around the number through
while (false == digits.empty() && (digits.front() == ' ' || digits.front() == ' '))
{
digits.remove_prefix(1);
}
while (false == digits.empty() && (digits.back() == ' ' || digits.back() == ' '))
{
digits.remove_suffix(1);
}
auto width{0};
if (std::from_chars(digits.data(), digits.data() + digits.size(), width).ec != std::errc{} || width < 0)
{
continue;
}
auto cut{cut_glyph(Rgba, Width, Height, x, width)};
if (font.m_glyphs.empty())
{
font.m_fallback = cut;
}
font.m_glyphs.insert_or_assign(characters.front(), std::move(cut));
x += width + 1;
}
if (font.m_glyphs.empty())
{
return std::nullopt;
}
return font;
}
glyph bitmap_font::sprite(std::span<std::uint8_t const> const Rgba, int const Width, int const Height)
{
if (Width <= 0 || Height <= 0 || Rgba.size() < static_cast<std::size_t>(Width) * Height * 4)
{
return {};
}
return cut_glyph(Rgba, Width, Height, 0, Width);
}
glyph const &bitmap_font::find(char32_t const Character) const
{
if (auto const lookup{m_glyphs.find(Character)}; lookup != m_glyphs.end())
{
return lookup->second;
}
return m_fallback;
}
int bitmap_font::text_width(std::string_view const Text, int const Spacing) const
{
auto width{0};
for (auto const character : decode_utf8(Text))
{
width += find(character).width() + Spacing;
}
return width;
}
canvas::canvas(int const Width, int const Height, rgb const Color)
: m_width{std::max(Width, 0)}, m_height{std::max(Height, 0)}, m_pixels(static_cast<std::size_t>(m_width) * m_height * 4, 255)
{
fill(Color);
}
canvas::canvas(int const Width, int const Height, std::vector<std::uint8_t> Pixels) : m_width{Width}, m_height{Height}, m_pixels{std::move(Pixels)}
{
m_pixels.resize(static_cast<std::size_t>(std::max(Width, 0)) * std::max(Height, 0) * 4, 255);
}
void canvas::fill(rgb const Color)
{
for (std::size_t at = 0; at < m_pixels.size(); at += 4)
{
std::ranges::copy(Color, m_pixels.begin() + static_cast<std::ptrdiff_t>(at));
m_pixels[at + 3] = 255;
}
}
void canvas::set(int const X, int const Y, rgb const Color)
{
if (X < 0 || Y < 0 || X >= m_width || Y >= m_height)
{
return;
}
auto const at{(static_cast<std::size_t>(Y) * m_width + X) * 4};
std::ranges::copy(Color, m_pixels.begin() + static_cast<std::ptrdiff_t>(at));
}
rgb canvas::get(int const X, int const Y) const
{
if (X < 0 || Y < 0 || X >= m_width || Y >= m_height)
{
return {0, 0, 0};
}
auto const at{(static_cast<std::size_t>(Y) * m_width + X) * 4};
return {m_pixels[at], m_pixels[at + 1], m_pixels[at + 2]};
}
void canvas::fill_rect(int const X, int const Y, int const Width, int const Height, rgb const Color)
{
for (auto y = std::max(Y, 0); y < std::min(Y + Height, m_height); ++y)
{
for (auto x = std::max(X, 0); x < std::min(X + Width, m_width); ++x)
{
set(x, y, Color);
}
}
}
void canvas::draw_glyph(glyph const &Glyph, int const X, int const Y, rgb const Color)
{
for (auto column = 0; column < Glyph.width(); ++column)
{
for (auto row = 0; row < Glyph.height(); ++row)
{
if (Glyph.lit(column, row))
{
set(X + column, Y + row, Color);
}
}
}
}
int canvas::draw_text(bitmap_font const &Font, int X, int const Y, std::string_view const Text, rgb const Color, int const Spacing)
{
for (auto const character : decode_utf8(Text))
{
auto const &shape{Font.find(character)};
draw_glyph(shape, X, Y, Color);
X += shape.width() + Spacing;
}
return X;
}
void canvas::draw_image(canvas const &Image, int const X, int const Y)
{
for (auto y = 0; y < Image.height(); ++y)
{
for (auto x = 0; x < Image.width(); ++x)
{
set(X + x, Y + y, Image.get(x, y));
}
}
}
} // namespace script_screens