diff --git a/backup/.htaccess b/backup/.htaccess new file mode 100644 index 0000000..50726d0 --- /dev/null +++ b/backup/.htaccess @@ -0,0 +1,3 @@ +RewriteEngine on +RewriteCond %{REQUEST_URI} \.bin$ [NC] +RewriteRule ^(.*\.bin)$ /busanzeiger-webinterface/src/show.php [L,QSA] diff --git a/backup/active.txt b/backup/active.txt new file mode 100644 index 0000000..7c5c8de --- /dev/null +++ b/backup/active.txt @@ -0,0 +1 @@ +Schildkroete.bin \ No newline at end of file diff --git a/backup/alphabet-extended.js b/backup/alphabet-extended.js new file mode 100644 index 0000000..ccad0cc --- /dev/null +++ b/backup/alphabet-extended.js @@ -0,0 +1,5097 @@ +const FONT_5x7 = { + "0": [ + " ### ", + "# ##", + "# # #", + "## #", + "# #", + " ### ", + " " + ], + "1": [ + "# ", + "# ", + "# ", + "# ", + "# ", + "# ", + " " + ], + "2": [ + " ### ", + "# #", + " # ", + " # ", + " # ", + "#####", + " " + ], + "3": [ + "#### ", + " #", + " ####", + " #", + " #", + "#### ", + " " + ], + "4": [ + "# # ", + "# # ", + "# # ", + "#### ", + " # ", + " # ", + " " + ], + "5": [ + "#### ", + "# ", + "#### ", + " # ", + " # ", + "#### ", + " " + ], + "6": [ + "# ", + "# ", + "#### ", + "# # ", + "# # ", + "#### ", + " " + ], + "7": [ + "#### ", + " # ", + " # ", + " # ", + " # ", + " # ", + " " + ], + "8": [ + " ### ", + "# #", + " ### ", + "# #", + "# #", + " ### ", + " " + ], + "9": [ + "#### ", + "# # ", + "#### ", + " # ", + " # ", + " # ", + " " + ], + " ": [ + " ", + " ", + " ", + " ", + " ", + " ", + " " + ], + // Original punctuation + "!": [ + "# ", + "# ", + "# ", + "# ", + " ", + "# ", + " " + ], + "\"": [ + "# # ", + "# # ", + " ", + " ", + " ", + " ", + " " + ], + "#": [ + " # # ", + "#####", + " # # ", + "#####", + " # # ", + " ", + " " + ], + "$": [ + " # ", + " ####", + "# ", + " ### ", + " #", + "#### ", + " # " + ], + "%": [ + "## #", + "## # ", + " # ", + " # ", + "# ##", + "# ##", + " " + ], + "&": [ + " ## ", + "# # ", + " ## ", + "# ## ", + "# # ", + " ## #", + " " + ], + "'": [ + " # ", + " # ", + " ", + " ", + " ", + " ", + " " + ], + "(": [ + " # ", + " # ", + " # ", + " # ", + " # ", + " # ", + " " + ], + ")": [ + " # ", + " # ", + " # ", + " # ", + " # ", + " # ", + " " + ], + "*": [ + " ", + " # # ", + " # ", + " # # ", + " ", + " ", + " " + ], + "+": [ + " ", + " ", + " # ", + " ### ", + " # ", + " ", + " " + ], + ",": [ + " ", + " ", + " ", + " ", + " ", + " # ", + "# " + ], + "-": [ + " ", + " ", + " ", + " ### ", + " ", + " ", + " " + ], + ".": [ + " ", + " ", + " ", + " ", + " ", + "# ", + " " + ], + "/": [ + " ", + " #", + " # ", + " # ", + " # ", + "# ", + " " + ], + ":": [ + " ", + " # ", + " ", + " ", + " # ", + " ", + " " + ], + ";": [ + " ", + " ", + " # ", + " ", + " ", + " # ", + "# " + ], + "<": [ + " ", + " # ", + " # ", + " # ", + " # ", + " # ", + " " + ], + "=": [ + " ", + " ", + " ### ", + " ", + " ### ", + " ", + " " + ], + ">": [ + " ", + " # ", + " # ", + " # ", + " # ", + " # ", + " " + ], + "?": [ + " ## ", + "# # ", + " # ", + " # ", + " ", + " # ", + " " + ], + "@": [ + " ### ", + "# #", + "# ###", + "# # #", + "# ###", + "# ", + " ####" + ], + "A": [ + " # ", + " # # ", + "# #", + "#####", + "# #", + "# #", + " " + ], + "B": [ + "#### ", + "# #", + "#### ", + "# #", + "# #", + "#### ", + " " + ], + "C": [ + " ####", + "# ", + "# ", + "# ", + "# ", + " ####", + " " + ], + "D": [ + "#### ", + "# #", + "# #", + "# #", + "# #", + "#### ", + " " + ], + "E": [ + "#### ", + "# ", + "### ", + "# ", + "# ", + "#### ", + " " + ], + "F": [ + "#### ", + "# ", + "# ", + "### ", + "# ", + "# ", + " " + ], + "G": [ + " ### ", + "# ", + "# ", + "# ###", + "# #", + " ####", + " " + ], + "H": [ + "# #", + "# #", + "# #", + "#####", + "# #", + "# #", + " " + ], + "I": [ + "#", + "#", + "#", + "#", + "#", + "#" + ], + "J": [ + " ### ", + " # ", + " # ", + " # ", + "# # ", + "#### ", + " " + ], + "K": [ + "# # ", + "# # ", + "## ", + "# # ", + "# # ", + "# # ", + " " + ], + "L": [ + "# ", + "# ", + "# ", + "# ", + "# ", + "#### ", + " " + ], + "M": [ + "# #", + "## ##", + "# # #", + "# #", + "# #", + "# #", + " " + ], + "N": [ + "# #", + "## #", + "# # #", + "# # #", + "# ##", + "# #", + " " + ], + "O": [ + " ### ", + "# #", + "# #", + "# #", + "# #", + " ### ", + " " + ], + "P": [ + "#### ", + "# #", + "# #", + "#### ", + "# ", + "# ", + " " + ], + "Q": [ + " ## ", + "# # ", + "# # ", + "# # ", + "# ## ", + " ####", + " " + ], + "R": [ + "#### ", + "# #", + "# #", + "#### ", + "# #", + "# #", + " " + ], + "S": [ + " ####", + "# ", + " ### ", + " #", + " #", + "#### ", + " " + ], + "T": [ + "#####", + " # ", + " # ", + " # ", + " # ", + " # ", + " " + ], + "U": [ + "# # ", + "# # ", + "# # ", + "# # ", + "# # ", + " ## ", + " " + ], + "V": [ + "# #", + "# #", + "# #", + " # # ", + " # # ", + " # ", + " " + ], + "W": [ + "# #", + "# #", + "# #", + "# #", + "# # #", + " # # ", + " " + ], + "X": [ + "# #", + " # # ", + " # ", + " # # ", + "# #", + "# #", + " " + ], + "Y": [ + "# #", + " # # ", + " # ", + " # ", + " # ", + " # ", + " " + ], + "Z": [ + "#####", + " #", + " ## ", + " # ", + "# ", + "#####", + " " + ], + "[": [ + " ### ", + " # ", + " # ", + " # ", + " # ", + " ### ", + " " + ], + "\\": [ + " ", + "# ", + " # ", + " # ", + " # ", + " #", + " " + ], + "]": [ + " ### ", + " # ", + " # ", + " # ", + " # ", + " ### ", + " " + ], + "^": [ + " # ", + " # # ", + " ", + " ", + " ", + " ", + " " + ], + "_": [ + " ", + " ", + " ", + " ", + " ", + " ### ", + " " + ], + "`": [ + " # ", + " # ", + " ", + " ", + " ", + " ", + " " + ], + // Lowercase letters + "a": [ + " ", + " ", + " ### ", + "# #", + "#####", + "# #", + " " + ], + "b": [ + "# ", + "# ", + "#### ", + "# #", + "# #", + "#### ", + " " + ], + "c": [ + " ", + " ", + " ### ", + "# ", + "# ", + " ### ", + " " + ], + "d": [ + " #", + " #", + " ####", + "# #", + "# #", + " ####", + " " + ], + "e": [ + " ", + " ", + "#### ", + "### ", + "# ", + "#### ", + " " + ], + "f": [ + " ##", + " # ", + " ### ", + " # ", + " # ", + " # ", + " " + ], + "g": [ + " ", + " ", + " ####", + "# #", + " ####", + " #", + " ### " + ], + "h": [ + "# ", + "# ", + "#### ", + "# #", + "# #", + "# #", + " " + ], + "i": [ + " # ", + " ", + "## ", + " # ", + " # ", + "### ", + " " + ], + "j": [ + " # ", + " ", + " ## ", + " # ", + " # ", + "# # ", + " ## " + ], + "k": [ + "# ", + "# ", + "# # ", + "# # ", + "## ", + "# ## ", + " " + ], + "l": [ + "## ", + " # ", + " # ", + " # ", + " # ", + "### ", + " " + ], + "m": [ + " ", + " ", + "## # ", + "# # #", + "# # #", + "# #", + " " + ], + "n": [ + " ", + " ", + "#### ", + "# #", + "# #", + "# #", + " " + ], + "o": [ + " ", + " ", + " ### ", + "# #", + "# #", + " ### ", + " " + ], + "p": [ + " ", + " ", + "#### ", + "# #", + "#### ", + "# ", + "# " + ], + "q": [ + " ", + " ", + " ####", + "# #", + " ####", + " #", + " #" + ], + "r": [ + " ", + " ", + "# ## ", + "## ", + "# ", + "# ", + " " + ], + "s": [ + " ", + " ", + " ####", + "#### ", + " #", + "#### ", + " " + ], + "t": [ + " # ", + " # ", + "### ", + " # ", + " # ", + " ## ", + " " + ], + "u": [ + " ", + " ", + "# #", + "# #", + "# #", + " ####", + " " + ], + "v": [ + " ", + " ", + "# #", + "# #", + " # # ", + " # ", + " " + ], + "w": [ + " ", + " ", + "# #", + "# # #", + "# # #", + " # # ", + " " + ], + "x": [ + " ", + " ", + "# #", + " # # ", + " # ", + " # # ", + " " + ], + "y": [ + " ", + " ", + "# #", + "# #", + " ####", + " #", + " ### " + ], + "z": [ + " ", + " ", + "#####", + " # ", + " # ", + "#####", + " " + ], + "{": [ + " ## ", + " # ", + "## ", + " # ", + " # ", + " ## ", + " " + ], + "|": [ + " # ", + " # ", + " # ", + " # ", + " # ", + " # ", + " " + ], + "~": [ + " ", + " ", + " # #", + "# ## ", + " ", + " ", + " " + ], + // Latin-1 Supplement (U+00A0 to U+00FF) + "¡": [ + " # ", + " ", + " # ", + " # ", + " # ", + " # ", + " " + ], + "¢": [ + " # ", + " ### ", + "# ", + "# ", + " ### ", + " # ", + " " + ], + "£": [ + " ## ", + " # ", + "#### ", + " # ", + " # ", + "#####", + " " + ], + "¤": [ + " ", + "# #", + " ### ", + " # # ", + " ### ", + "# #", + " " + ], + "¥": [ + "# #", + " # # ", + "#####", + " # ", + "#####", + " # ", + " " + ], + "¦": [ + " # ", + " # ", + " ", + " # ", + " # ", + " # ", + " " + ], + "§": [ + " ### ", + "# ", + " ## ", + "# ## ", + " ## ", + " #", + " ### " + ], + "¨": [ + " # # ", + " ", + " ", + " ", + " ", + " ", + " " + ], + "©": [ + " ### ", + "# #", + "# # #", + "# # #", + "# #", + " ### ", + " " + ], + "ª": [ + " ## ", + " # ", + " ## ", + " ", + "#### ", + " ", + " " + ], + "«": [ + " ", + " ", + " # # ", + "# #", + " # # ", + " ", + " " + ], + "¬": [ + " ", + " ", + "#####", + " #", + " #", + " ", + " " + ], + "­": [ + " ", + " ", + " ", + " ### ", + " ", + " ", + " " + ], + "®": [ + " ### ", + "# #", + "# # #", + "## ##", + "# #", + " ### ", + " " + ], + "¯": [ + "#####", + " ", + " ", + " ", + " ", + " ", + " " + ], + "°": [ + " ## ", + "# # ", + " ## ", + " ", + " ", + " ", + " " + ], + "±": [ + " ", + " # ", + "#####", + " # ", + "#####", + " ", + " " + ], + "²": [ + " ## ", + " # ", + " # ", + "### ", + " ", + " ", + " " + ], + "³": [ + "## ", + " # ", + "## ", + " # ", + " ", + " ", + " " + ], + "´": [ + " # ", + " # ", + " ", + " ", + " ", + " ", + " " + ], + "µ": [ + " ", + " ", + "# #", + "# #", + "#### ", + "# ", + "# " + ], + "¶": [ + " ####", + "#### ", + "#### ", + " # # ", + " # # ", + " # # ", + " " + ], + "·": [ + " ", + " ", + " # ", + " ", + " ", + " ", + " " + ], + "¸": [ + " ", + " ", + " ", + " ", + " ", + " # ", + " # " + ], + "¹": [ + " # ", + "## ", + " # ", + "### ", + " ", + " ", + " " + ], + "º": [ + " ## ", + "# # ", + " ## ", + " ", + "#### ", + " ", + " " + ], + "»": [ + " ", + " ", + "# #", + " # # ", + "# #", + " ", + " " + ], + "¼": [ + "# #", + "# # ", + " # ", + " # # ", + "# ##", + " # #", + " " + ], + "½": [ + "# #", + "# # ", + " ## ", + " # #", + "# # #", + " ###", + " " + ], + "¾": [ + "## #", + " # # ", + "## # ", + " ## #", + "# ##", + " # #", + " " + ], + "¿": [ + " # ", + " ", + " # ", + " # ", + "# # ", + " ## ", + " " + ], + "À": [ + " # ", + " # ", + " # # ", + "# #", + "#####", + "# #", + " " + ], + "Á": [ + " # ", + " # ", + " # # ", + "# #", + "#####", + "# #", + " " + ], + "Â": [ + " # ", + " # # ", + "# #", + "#####", + "# #", + "# #", + " " + ], + "Ã": [ + " # # ", + " # ", + " # # ", + "# #", + "#####", + "# #", + " " + ], + // Umlauts - Uppercase + "Ä": [ + " # # ", + " # ", + " # # ", + "# #", + "#####", + "# #", + " " + ], + "Å": [ + " # ", + " # # ", + " # ", + " # # ", + "#####", + "# #", + " " + ], + "Æ": [ + " ####", + "# # ", + "# ###", + "####", + "# # ", + "# ###", + " " + ], + "Ç": [ + " ####", + "# ", + "# ", + "# ", + " ####", + " # ", + " # " + ], + "È": [ + " # ", + "#### ", + "# ", + "### ", + "# ", + "#### ", + " " + ], + "É": [ + " # ", + "#### ", + "# ", + "### ", + "# ", + "#### ", + " " + ], + "Ê": [ + " # ", + " # # ", + "#### ", + "### ", + "# ", + "#### ", + " " + ], + "Ë": [ + " # # ", + "#### ", + "# ", + "### ", + "# ", + "#### ", + " " + ], + "Ì": [ + " # ", + " # ", + " # ", + " # ", + " # ", + " # ", + " " + ], + "Í": [ + " # ", + " # ", + " # ", + " # ", + " # ", + " # ", + " " + ], + "Î": [ + " # ", + " # # ", + " # ", + " # ", + " # ", + " # ", + " " + ], + "Ï": [ + " # # ", + " # ", + " # ", + " # ", + " # ", + " # ", + " " + ], + "Ð": [ + " ### ", + " # #", + "#### ", + " # #", + " # #", + " ### ", + " " + ], + "Ñ": [ + " # # ", + "# #", + "## #", + "# # #", + "# ##", + "# #", + " " + ], + "Ò": [ + " # ", + " ### ", + "# #", + "# #", + "# #", + " ### ", + " " + ], + "Ó": [ + " # ", + " ### ", + "# #", + "# #", + "# #", + " ### ", + " " + ], + "Ô": [ + " # ", + " # # ", + "# #", + "# #", + "# #", + " ### ", + " " + ], + "Õ": [ + " # # ", + " ### ", + "# #", + "# #", + "# #", + " ### ", + " " + ], + "Ö": [ + " # # ", + " ### ", + "# #", + "# #", + "# #", + " ### ", + " " + ], + "×": [ + " ", + "# #", + " # # ", + " # ", + " # # ", + "# #", + " " + ], + "Ø": [ + " ### ", + "# ##", + "# # #", + "# # #", + "## #", + " ### ", + " " + ], + "Ù": [ + " # ", + "# #", + "# #", + "# #", + "# #", + " ### ", + " " + ], + "Ú": [ + " # ", + "# #", + "# #", + "# #", + "# #", + " ### ", + " " + ], + "Û": [ + " # ", + " # # ", + "# #", + "# #", + "# #", + " ### ", + " " + ], + "Ü": [ + " # # ", + "# #", + "# #", + "# #", + "# #", + " ### ", + " " + ], + "Ý": [ + " # ", + "# #", + " # # ", + " # ", + " # ", + " # ", + " " + ], + "Þ": [ + "# ", + "#### ", + "# #", + "#### ", + "# ", + "# ", + " " + ], + "ß": [ + " ### ", + "# #", + "#### ", + "# #", + "# #", + "## # ", + " " + ], + "à": [ + " # ", + " ", + " ### ", + "# #", + "#####", + "# #", + " " + ], + "á": [ + " # ", + " ", + " ### ", + "# #", + "#####", + "# #", + " " + ], + "â": [ + " # ", + " # # ", + " ### ", + "# #", + "#####", + "# #", + " " + ], + "ã": [ + " # # ", + " # ", + " ### ", + "# #", + "#####", + "# #", + " " + ], + // Umlauts - Lowercase + "ä": [ + " # # ", + " ", + " ### ", + "# #", + "#####", + "# #", + " " + ], + "å": [ + " # ", + " ### ", + " ", + " ### ", + "# #", + "#####", + "# #", + " " + ], + "æ": [ + " ", + " ", + "#### ", + " # #", + " ####", + "# # #", + " " + ], + "ç": [ + " ", + " ", + " ### ", + "# ", + " ### ", + " # ", + " # " + ], + "è": [ + " # ", + " ", + "#### ", + "### ", + "# ", + "#### ", + " " + ], + "é": [ + " # ", + " ", + "#### ", + "### ", + "# ", + "#### ", + " " + ], + "ê": [ + " # ", + " # # ", + "#### ", + "### ", + "# ", + "#### ", + " " + ], + "ë": [ + " # # ", + " ", + "#### ", + "### ", + "# ", + "#### ", + " " + ], + "ì": [ + " # ", + " ", + "## ", + " # ", + " # ", + "### ", + " " + ], + "í": [ + " # ", + " ", + "## ", + " # ", + " # ", + "### ", + " " + ], + "î": [ + " # ", + " # # ", + "## ", + " # ", + " # ", + "### ", + " " + ], + "ï": [ + " # # ", + " ", + "## ", + " # ", + " # ", + "### ", + " " + ], + "ð": [ + " ## ", + " # # ", + " # ", + " ####", + "# #", + " ### ", + " " + ], + "ñ": [ + " # # ", + " ", + "#### ", + "# #", + "# #", + "# #", + " " + ], + "ò": [ + " # ", + " ", + " ### ", + "# #", + "# #", + " ### ", + " " + ], + "ó": [ + " # ", + " ", + " ### ", + "# #", + "# #", + " ### ", + " " + ], + "ô": [ + " # ", + " # # ", + " ### ", + "# #", + "# #", + " ### ", + " " + ], + "õ": [ + " # # ", + " ", + " ### ", + "# #", + "# #", + " ### ", + " " + ], + "ö": [ + " # # ", + " ", + " ### ", + "# #", + "# #", + " ### ", + " " + ], + "÷": [ + " ", + " # ", + "#####", + " ", + " # ", + " ", + " " + ], + "ø": [ + " ", + " ", + " ####", + "# # #", + "# # #", + "#### ", + " " + ], + "ù": [ + " # ", + " ", + "# #", + "# #", + "# #", + " ####", + " " + ], + "ú": [ + " # ", + " ", + "# #", + "# #", + "# #", + " ####", + " " + ], + "û": [ + " # ", + " # # ", + "# #", + "# #", + "# #", + " ####", + " " + ], + "ü": [ + " # # ", + " ", + "# #", + "# #", + "# #", + " ####", + " " + ], + "ý": [ + " # ", + " ", + "# #", + "# #", + " ####", + " #", + " ### " + ], + "þ": [ + "# ", + "# ", + "#### ", + "# #", + "#### ", + "# ", + "# " + ], + "ÿ": [ + " # # ", + " ", + "# #", + "# #", + " ####", + " #", + " ### " + ], + "ı": [ + " ", + " ", + " # ", + " # ", + " # ", + " # ", + " " + ], + "Ĺ": [ + " # ", + "# ", + "# ", + "# ", + "# ", + "#### ", + " " + ], + "ƒ": [ + " ## ", + " # ", + "### ", + " # ", + " # ", + " # ", + "# " + ], + "Ɵ": [ + " ### ", + "# #", + "#####", + "# #", + "# #", + " ### ", + " " + ], + "ƪ": [ + " ## ", + "# # ", + " ### ", + " # ", + " ", + " ", + " " + ], + "Ʒ": [ + "#### ", + " # ", + " ## ", + " # ", + "#### ", + " ", + " " + ], + "Ƹ": [ + "#### ", + " #", + " ## ", + " #", + "#### ", + " ", + " " + ], + "ǝ": [ + " ", + " ", + " ### ", + "#####", + "# #", + " ### ", + " " + ], + "ɐ": [ + " ", + " ", + " # # ", + "#### ", + " # ", + " ### ", + " " + ], + "ɔ": [ + " ", + " ", + " ####", + "# ", + " ####", + " ", + " " + ], + "ɛ": [ + " ", + " ### ", + "# ", + " ## ", + " ### ", + " ", + " " + ], + "ɟ": [ + " ## ", + " # ", + "### ", + " # ", + " # ", + "# ", + " " + ], + "ɪ": [ + " ", + "#####", + " # ", + " # ", + "#####", + " ", + " " + ], + "ɯ": [ + " ", + " ", + "# #", + "# # #", + "# # #", + " # ##", + " " + ], + "ɴ": [ + " ", + "# #", + "## #", + "# # #", + "# ##", + " ", + " " + ], + "ʀ": [ + " ", + "#### ", + "# #", + "#### ", + "# # ", + "# # ", + " " + ], + "ʇ": [ + " ", + " ## ", + " # ", + " # ", + " ###", + " # ", + " # " + ], + "ʋ": [ + " ", + " ", + "# #", + "# #", + " # # ", + " # ", + "# " + ], + "ʔ": [ + " # ", + " ### ", + " # ", + " ", + " # ", + " ", + " " + ], + "ʕ": [ + " ", + " ## ", + " # ", + " # ", + " ## ", + " ", + " " + ], + "ʖ": [ + " ", + " ## ", + "# ", + "# # ", + " ## ", + " ", + " " + ], + "ʘ": [ + " ", + " ### ", + "# # #", + "# # #", + " ### ", + " ", + " " + ], + "ʙ": [ + " ", + "#### ", + "# #", + "#### ", + "# #", + "#### ", + " " + ], + "ʞ": [ + " ", + " # #", + " # # ", + " ## ", + " # # ", + " # #", + " " + ], + "ʟ": [ + " ", + "# ", + "# ", + "# ", + "#### ", + " ", + " " + ], + "ʸ": [ + "# # ", + " ## ", + " # ", + " # ", + " ", + " ", + " " + ], + "ˆ": [ + " # ", + " # # ", + " ", + " ", + " ", + " ", + " " + ], + "ˊ": [ + " # ", + " # ", + " ", + " ", + " ", + " ", + " " + ], + "ˋ": [ + " # ", + " # ", + " ", + " ", + " ", + " ", + " " + ], + "˘": [ + " # # ", + " # ", + " ", + " ", + " ", + " ", + " " + ], + "ˡ": [ + "# ", + "# ", + "# ", + " ", + " ", + " ", + " " + ], + "˵": [ + " ### ", + " ", + " ", + " ", + " ", + " ", + " " + ], + "˶": [ + " ### ", + " ", + " ", + " ", + " ", + " ", + " " + ], + "̀": [ + " # ", + " # ", + " ", + " ", + " ", + " ", + " " + ], + "́": [ + " # ", + " # ", + " ", + " ", + " ", + " ", + " " + ], + "̄": [ + "#####", + " ", + " ", + " ", + " ", + " ", + " " + ], + "̅": [ + "#####", + " ", + " ", + " ", + " ", + " ", + " " + ], + "̆": [ + " # # ", + " # ", + " ", + " ", + " ", + " ", + " " + ], + "̑": [ + "# # #", + " # # ", + " ", + " ", + " ", + " ", + " " + ], + "̫": [ + " ", + " ", + " ", + " ### ", + " ", + " ", + " " + ], + "̯": [ + " ", + " ", + " ", + " ### ", + " ", + " ", + " " + ], + "̲": [ + " ", + " ", + " ", + " ", + " ", + "#####", + " " + ], + "̵": [ + " ", + " ", + " # ", + " ### ", + " # ", + " ", + " " + ], + "̿": [ + "#####", + " ", + " ", + " ", + " ", + " ", + " " + ], + "͆": [ + " ### ", + " ", + " ", + " ", + " ", + " ", + " " + ], + "͇": [ + " ", + " ", + " ", + " ", + "#####", + " ", + " " + ], + "͜": [ + " ", + " ", + " ", + " # # ", + " # ", + " ", + " " + ], + "͝": [ + " # # ", + " # ", + " ", + " ", + " ", + " ", + " " + ], + "͟": [ + " ", + " ", + "#####", + " ", + " ", + " ", + " " + ], + "͠": [ + " # #", + " ## ", + " ", + " ", + " ", + " ", + " " + ], + "͡": [ + " # ", + " # # ", + " ", + " ", + " ", + " ", + " " + ], + "Δ": [ + " # ", + " # # ", + " # # ", + "# #", + "# #", + "#####", + " " + ], + "Λ": [ + " # ", + " # # ", + " # # ", + "# #", + "# #", + "# #", + " " + ], + "Σ": [ + "#####", + "# ", + " ## ", + " ## ", + "# ", + "#####", + " " + ], + "Ω": [ + " ### ", + "# #", + "# #", + "# #", + " # # ", + "## ##", + " " + ], + "α": [ + " ", + " ", + " ### ", + "# # ", + "# ## ", + " # # ", + " " + ], + "β": [ + " ## ", + "# # ", + "### ", + "# # ", + "# # ", + "### ", + "# " + ], + "ε": [ + " ", + " ", + " ####", + "# ", + " ### ", + " ####", + " " + ], + "η": [ + " ", + "# #", + "# #", + "#####", + "# #", + " #", + " " + ], + "ι": [ + " ", + " # ", + " # ", + " # ", + " # ", + " ", + " " + ], + "λ": [ + " # ", + " # ", + " # # ", + "# # ", + "# # ", + " ", + " " + ], + "ο": [ + " ", + " ### ", + "# #", + " ### ", + " ", + " ", + " " + ], + "π": [ + " ", + " ", + "#####", + " # # ", + " # # ", + " # # ", + " " + ], + "ς": [ + " ", + " ####", + "# ", + " ### ", + " #", + " ## ", + " " + ], + "τ": [ + " ", + " ", + "#####", + " # ", + " # ", + " # ", + " " + ], + "ψ": [ + " ", + "# # #", + "# # #", + " ### ", + " # ", + " # ", + " " + ], + "ω": [ + " ", + " ", + "# #", + "# #", + "# # #", + " # # ", + " " + ], + "Д": [ + " ### ", + " # # ", + " # # ", + " # # ", + "#####", + "# #", + " " + ], + "И": [ + "# #", + "# ##", + "# # #", + "## #", + "# #", + "# #", + " " + ], + "П": [ + "#####", + "# #", + "# #", + "# #", + "# #", + "# #", + " " + ], + "Ф": [ + " # ", + " ### ", + "# #", + "# #", + " ### ", + " # ", + " " + ], + "Я": [ + " ####", + "# #", + " ####", + " # #", + "# #", + "# #", + " " + ], + "в": [ + " ", + "### ", + "# # ", + "### ", + "# # ", + "### ", + " " + ], + "д": [ + " ", + " ### ", + " # # ", + "#####", + " #", + " ", + " " + ], + "з": [ + " ", + " ### ", + " # ", + " ## ", + " # ", + " ### ", + " " + ], + "м": [ + " ", + "# #", + "## ##", + "# # #", + "# #", + "# #", + " " + ], + "у": [ + " ", + "# #", + "# #", + " ####", + " #", + " ### ", + " " + ], + "я": [ + " ", + " ####", + "# #", + " ####", + " # #", + "# #", + " " + ], + "є": [ + " ", + " ### ", + "### ", + " ### ", + "### ", + " ### ", + " " + ], + "҂": [ + "# #", + " # # ", + " # ", + " # # ", + "# #", + " ", + " " + ], + "Ӝ": [ + " # # ", + "# # #", + " ### ", + " # ", + " ### ", + "# # #", + " " + ], + "ԅ": [ + " ", + " ## ", + "# # ", + " ### ", + " ", + " ", + " " + ], + "ן": [ + "# ", + "# ", + "# ", + "# ", + "# ", + "# ", + " " + ], + "סּ": [ + " ### ", + "# ", + " ### ", + " # ", + " ", + " ", + " " + ], + "ױ": [ + "# #", + "# #", + "# #", + " # # ", + " # ", + " ", + " " + ], + "ײ": [ + "# # ", + "# # ", + "# # ", + " ## #", + " ", + " ", + " " + ], + "ل": [ + " # ", + " # ", + " # ", + " # ", + " ## ", + " ", + " " + ], + "و": [ + " ", + " ## ", + "# # ", + " ## ", + " ", + " ", + " " + ], + "٩": [ + " ### ", + "# # ", + " ### ", + " #", + " ", + " ", + " " + ], + "ਊ": [ + " ## ", + "# # ", + " ## ", + " # ", + " ", + " ", + " " + ], + "੭": [ + " ### ", + " # ", + " # ", + " # ", + " ", + " ", + " " + ], + "ଘ": [ + " ### ", + "# #", + " ### ", + " ", + " ", + " ", + " " + ], + "୧": [ + " # ", + "## ", + " # ", + " # ", + " ", + " ", + " " + ], + "୨": [ + " ## ", + " # ", + " ## ", + "# ", + " ", + " ", + " " + ], + "థ": [ + " ### ", + "# #", + " ### ", + " # #", + " ", + " ", + " " + ], + "౪": [ + " # # ", + " # # ", + "#### ", + " # ", + " ", + " ", + " " + ], + "ಠ": [ + " ### ", + "# #", + "# #", + " ### ", + " # ", + " ", + " " + ], + "ಡ": [ + " ### ", + "# #", + " ### ", + " # ", + " ", + " ", + " " + ], + "ಥ": [ + " # # ", + " ### ", + "# #", + " ### ", + " ", + " ", + " " + ], + "ರ": [ + " ## ", + " # ", + " ### ", + " # ", + " ", + " ", + " " + ], + "ೃ": [ + " ## ", + " # ", + " ", + " ", + " ", + " ", + " " + ], + "ง": [ + " ## ", + " # ", + " # ", + " # ", + " ## ", + " ", + " " + ], + "๏": [ + " ### ", + "# #", + "# #", + "# #", + " ### ", + " ", + " " + ], + "๑": [ + "# ", + "# ", + "# ", + " ", + " ", + " ", + " " + ], + "ຈ": [ + " ## ", + " # ", + " ### ", + " # ", + " ", + " ", + " " + ], + "ຶ": [ + " # # ", + " ", + " ", + " ", + " ", + " ", + " " + ], + "༎": [ + " # # ", + "# #", + " ", + " ", + " ", + " ", + " " + ], + "༼": [ + " ### ", + " # ", + "# ", + " # ", + " ### ", + " ", + " " + ], + "༽": [ + " ### ", + " # ", + " #", + " # ", + " ### ", + " ", + " " + ], + "ლ": [ + " ", + " # ", + " # # ", + "# # ", + " # ", + " # ", + " " + ], + "ღ": [ + " # # ", + "## ##", + " ### ", + " # ", + " ", + " ", + " " + ], + "ᐛ": [ + " ", + " # # ", + " # ", + " # # ", + "# #", + " ", + " " + ], + "ᕕ": [ + " # ", + "# # ", + " # ", + " # ", + " # ", + " ", + " " + ], + "ᕗ": [ + " # ", + " # #", + " # ", + " # ", + " # ", + " ", + " " + ], + "ᕙ": [ + " # ", + "# ## ", + " # #", + " # ", + " # ", + " ", + " " + ], + "ᕤ": [ + " # ", + " ## #", + "# # ", + " # ", + " # ", + " ", + " " + ], + "ᕥ": [ + " # ", + " # # ", + " # ", + " # # ", + "# #", + " ", + " " + ], + "ᕦ": [ + " # ", + "## # ", + " # # ", + " # #", + " # ", + " ", + " " + ], + "ᴀ": [ + " ", + " ### ", + "# #", + "#####", + "# #", + " ", + " " + ], + "ᴇ": [ + " ", + "#### ", + "### ", + "#### ", + " ", + " ", + " " + ], + "ᴏ": [ + " ", + " ### ", + "# #", + " ### ", + " ", + " ", + " " + ], + "ᴖ": [ + " ", + " ### ", + "# #", + " ### ", + " ", + " ", + " " + ], + "ᴗ": [ + " ", + " ", + "# #", + " ### ", + " ", + " ", + " " + ], + "ᴛ": [ + " ", + "#####", + " # ", + " # ", + " ", + " ", + " " + ], + "ᴥ": [ + " ", + " # # ", + "## ##", + " ### ", + " ", + " ", + " " + ], + "ᵉ": [ + " ## ", + "# # ", + " ", + " ", + " ", + " ", + " " + ], + "ᵒ": [ + " ## ", + "# # ", + " ## ", + " ", + " ", + " ", + " " + ], + "ᵕ": [ + " ", + " # # ", + " # ", + " ", + " ", + " ", + " " + ], + "ᵘ": [ + "# # ", + " ## ", + " ", + " ", + " ", + " ", + " " + ], + "ᵛ": [ + "# # ", + " # ", + " ", + " ", + " ", + " ", + " " + ], + "ᶜ": [ + " ## ", + "# ", + " ## ", + " ", + " ", + " ", + " " + ], + "ᶤ": [ + "# ", + "# ", + " ", + " ", + " ", + " ", + " " + ], + "ᶰ": [ + " ", + "# # ", + "# # ", + " ", + " ", + " ", + " " + ], + "᷄": [ + " # ", + " # ", + " ", + " ", + " ", + " ", + " " + ], + "᷅": [ + " # ", + " # ", + " ", + " ", + " ", + " ", + " " + ], + // General Punctuation (U+2000 to U+206F) + "‐": [ + " ", + " ", + " ", + " ### ", + " ", + " ", + " " + ], + "‑": [ + " ", + " ", + " ", + " ### ", + " ", + " ", + " " + ], + "‒": [ + " ", + " ", + " ", + " ### ", + " ", + " ", + " " + ], + "–": [ + " ", + " ", + " ", + "#####", + " ", + " ", + " " + ], + "—": [ + " ", + " ", + " ", + "#####", + " ", + " ", + " " + ], + "―": [ + " ", + " ", + " ", + "#####", + " ", + " ", + " " + ], + "‖": [ + " # # ", + " # # ", + " # # ", + " # # ", + " # # ", + " # # ", + " " + ], + "‗": [ + " ", + " ", + " ", + " ", + "#####", + "#####", + " " + ], + "‘": [ + " # ", + " # ", + " ", + " ", + " ", + " ", + " " + ], + "’": [ + " # ", + " # ", + " ", + " ", + " ", + " ", + " " + ], + "‚": [ + " ", + " ", + " ", + " ", + " ", + " # ", + " # " + ], + "‛": [ + " # ", + " # ", + " ", + " ", + " ", + " ", + " " + ], + "“": [ + " # # ", + "# #", + " ", + " ", + " ", + " ", + " " + ], + "”": [ + " # # ", + "# #", + " ", + " ", + " ", + " ", + " " + ], + "„": [ + " ", + " ", + " ", + " ", + " ", + " # # ", + "# #" + ], + "‟": [ + " # # ", + "# #", + " ", + " ", + " ", + " ", + " " + ], + "†": [ + " # ", + " # ", + "#####", + " # ", + " # ", + " # ", + " " + ], + "‡": [ + " # ", + "#####", + " # ", + "#####", + " # ", + " ", + " " + ], + "•": [ + " ", + " ", + " ### ", + " ### ", + " ### ", + " ", + " " + ], + "‣": [ + " ", + " ", + " # ", + " ### ", + "#####", + " ", + " " + ], + "․": [ + " ", + " ", + " ", + " ", + " ", + "# ", + " " + ], + "‥": [ + " ", + " ", + " ", + " ", + " ", + "# #", + " " + ], + "…": [ + " ", + " ", + " ", + " ", + "# # #", + " ", + " " + ], + "‧": [ + " ", + " ", + " # ", + " ", + " ", + " ", + " " + ], + "‰": [ + "# #", + "# # #", + " # ", + " # # ", + "# #", + "# #", + " " + ], + "′": [ + " # ", + "# ", + " ", + " ", + " ", + " ", + " " + ], + "″": [ + "# # ", + "# # ", + " ", + " ", + " ", + " ", + " " + ], + "‴": [ + "# # #", + "# # #", + " ", + " ", + " ", + " ", + " " + ], + "‵": [ + " # ", + " #", + " ", + " ", + " ", + " ", + " " + ], + "‶": [ + "# # ", + "# # ", + " ", + " ", + " ", + " ", + " " + ], + "‸": [ + " ", + " ", + " # # ", + " # ", + " ", + " ", + " " + ], + "‹": [ + " ", + " ", + " # ", + " # ", + " # ", + " ", + " " + ], + "›": [ + " ", + " ", + " # ", + " # ", + " # ", + " ", + " " + ], + "※": [ + " # ", + "# # #", + " ### ", + "# # #", + " # ", + " ", + " " + ], + "‼": [ + "# # ", + "# # ", + "# # ", + "# # ", + " ", + "# # ", + " " + ], + "‽": [ + " ## ", + "# # ", + " ## ", + " # ", + " # ", + " ", + " " + ], + "‾": [ + "#####", + " ", + " ", + " ", + " ", + " ", + " " + ], + "‿": [ + " ", + " ", + " ", + " ", + " # # ", + " # ", + " " + ], + "⁀": [ + " ", + " # # ", + "# #", + " ", + " ", + " ", + " " + ], + "⁄": [ + " ", + " #", + " # ", + " # ", + " # ", + "# ", + " " + ], + "⁇": [ + " # # ", + "# # #", + " ## ", + " ## ", + " ", + " # # ", + " " + ], + "⁈": [ + " ## ", + "# # ", + " # ", + " # ", + " ", + " # #", + " " + ], + "⁉": [ + " # # ", + " # # ", + " # # ", + " # ", + " ", + " # ", + " " + ], + "⁊": [ + " ", + " #", + " ####", + "# ", + " ### ", + " ", + " " + ], + "⁋": [ + "#### ", + " ####", + " ####", + " # # ", + " # # ", + " # # ", + " " + ], + "⁌": [ + " ", + " ", + "#### ", + " ### ", + "#### ", + " ", + " " + ], + "⁍": [ + " ", + " ", + " ####", + " ### ", + " ####", + " ", + " " + ], + "⁎": [ + " ", + " ", + " ", + " ", + " # # ", + " # ", + " # # " + ], + "⁏": [ + " ", + "# ", + " # ", + " ", + " ", + " # ", + " " + ], + "⁐": [ + " ", + " ", + " # # ", + " # ", + " # # ", + " ", + " " + ], + "⁑": [ + " # # ", + " # ", + " # # ", + " ", + " # # ", + " # ", + " # # " + ], + "⁒": [ + " ", + " # ", + " ", + "#####", + " ", + " # ", + " " + ], + "⁓": [ + " ", + " ", + " # #", + " ## ", + "# # ", + " ", + " " + ], + "⁔": [ + " ", + " ", + " ", + " ", + "# #", + " # # ", + " " + ], + "⁕": [ + " ### ", + "# # #", + " ### ", + "# # #", + " ### ", + " ", + " " + ], + "⁖": [ + " ", + " # ", + " # ", + " # ", + " ", + " ", + " " + ], + "⁗": [ + "# # #", + "# # #", + "# ", + " ", + " ", + " ", + " " + ], + "⁘": [ + " ", + "# # #", + " ", + " # ", + " ", + " ", + " " + ], + "⁙": [ + " ", + "# # #", + " # ", + "# # #", + " ", + " ", + " " + ], + "⁚": [ + " ", + " # # ", + " ", + " ", + " ", + " ", + " " + ], + "⁛": [ + "# # #", + " ", + " # ", + " ", + " ", + " ", + " " + ], + "⁜": [ + " # ", + "# #", + " # ", + "# #", + " # ", + " ", + " " + ], + "⁝": [ + " # ", + " ", + " # ", + " ", + " # ", + " ", + " " + ], + "⁞": [ + " # ", + " # ", + " # ", + " # ", + " ", + " ", + " " + ], + "⁻": [ + " ", + " ### ", + " ", + " ", + " ", + " ", + " " + ], + "€": [ + " ## ", + " # ", + "#### ", + " # ", + "#### ", + " ## ", + " " + ], + "₿": [ + " # ", + " ####", + "# # ", + " ### ", + " # #", + "#### ", + " # " + ], + "™": [ + "#####", + " # ", + " # ", + " ", + " ", + " ", + " " + ], + "⅓": [ + "# # ", + " # ", + " # # ", + " # # ", + "# # ", + " ", + " " + ], + "⅔": [ + "## # ", + " ## ", + " # ", + " # # ", + "# # ", + " ", + " " + ], + "⅛": [ + "# #", + " ## ", + " # #", + "# # ", + " ###", + " ", + " " + ], + "⅜": [ + "## #", + " # # ", + "## # ", + " ## #", + "# ##", + " ", + " " + ], + "⅝": [ + "### ", + " # #", + "### ", + " ###", + " #", + " ", + " " + ], + "⅞": [ + "### #", + " ## ", + " # ", + " # #", + "# ##", + " ", + " " + ], + "←": [ + " ", + " # ", + " # ", + "#####", + " # ", + " # ", + " " + ], + "↑": [ + " # ", + " ### ", + "# # #", + " # ", + " # ", + " # ", + " " + ], + "→": [ + " ", + " # ", + " # ", + "#####", + " # ", + " # ", + " " + ], + "↓": [ + " # ", + " # ", + " # ", + "# # #", + " ### ", + " # ", + " " + ], + "↼": [ + " ", + " ", + " # ", + "#####", + " ", + " ", + " " + ], + "⇀": [ + " ", + " ", + " # ", + "#####", + " ", + " ", + " " + ], + "∀": [ + " # ", + " # # ", + "#####", + "# #", + " ", + " ", + " " + ], + "∇": [ + "#####", + " ### ", + " # ", + " ", + " ", + " ", + " " + ], + "∑": [ + "#####", + " # ", + " # ", + " # ", + "# ", + "#####", + " " + ], + "√": [ + " #", + " #", + "# #", + " # #", + " # #", + " # ", + " " + ], + "∞": [ + " ", + " ", + " # # ", + "# # #", + " # # ", + " ", + " " + ], + "∠": [ + " ", + " #", + " # ", + " # ", + "#####", + " ", + " " + ], + "∩": [ + " ### ", + "# #", + "# #", + " ", + " ", + " ", + " " + ], + "∫": [ + " ## ", + " # ", + " # ", + " # ", + " # ", + "## ", + " " + ], + "∵": [ + " ", + " # ", + "# # #", + " ", + " ", + " ", + " " + ], + "≖": [ + " # # ", + "#####", + " ", + "#####", + " ", + " ", + " " + ], + "≡": [ + " ", + "#####", + " ", + "#####", + " ", + "#####", + " " + ], + "≦": [ + " # ", + " # ", + " # ", + "#####", + "#####", + " ", + " " + ], + "≧": [ + " # ", + " # ", + " # ", + "#####", + "#####", + " ", + " " + ], + "⊂": [ + " ## ", + " # ", + " # ", + " # ", + " ## ", + " ", + " " + ], + "⊃": [ + " ## ", + " # ", + " # ", + " # ", + " ## ", + " ", + " " + ], + "⊙": [ + " ### ", + "# # #", + "# # #", + " ### ", + " ", + " ", + " " + ], + "⊹": [ + "# #", + " # # ", + " # ", + " # # ", + "# #", + " ", + " " + ], + "⌂": [ + " # ", + " ### ", + "#####", + "# # #", + "# #", + " ", + " " + ], + "⌐": [ + " ", + "#####", + "# ", + " ", + " ", + " ", + " " + ], + "⌒": [ + " # ", + " # # ", + "# #", + " ", + " ", + " ", + " " + ], + "⌓": [ + "# #", + " # # ", + " # ", + " ", + " ", + " ", + " " + ], + "⌘": [ + " # ", + "# # #", + " ### ", + " # # ", + " # ", + " ", + " " + ], + "⌣": [ + " ", + " ", + "# #", + " # # ", + " # ", + " ", + " " + ], + "⌥": [ + " ### ", + " # ", + " # # ", + "# #", + " ", + " ", + " " + ], + "─": [ + " ", + " ", + " ", + "#####", + " ", + " ", + " " + ], + "━": [ + " ", + " ", + " ", + "#####", + "#####", + " ", + " " + ], + "│": [ + " # ", + " # ", + " # ", + " # ", + " # ", + " # ", + " " + ], + "┌": [ + " ", + " ", + " ", + " ###", + " # ", + " # ", + " " + ], + "┐": [ + " ", + " ", + " ", + "### ", + " # ", + " # ", + " " + ], + "└": [ + " # ", + " # ", + " ###", + " ", + " ", + " ", + " " + ], + "┘": [ + " # ", + " # ", + "### ", + " ", + " ", + " ", + " " + ], + "├": [ + " # ", + " # ", + " ###", + " ###", + " # ", + " # ", + " " + ], + "┣": [ + " # ", + " ###", + " # ", + " ###", + " # ", + " # ", + " " + ], + "┤": [ + " # ", + " # ", + "### ", + "### ", + " # ", + " # ", + " " + ], + "┬": [ + " ", + " ", + "#####", + " # ", + " # ", + " # ", + " " + ], + "┴": [ + " # ", + " # ", + " # ", + "#####", + " ", + " ", + " " + ], + "┻": [ + " # ", + " # ", + " # ", + "#####", + "#####", + " ", + " " + ], + "═": [ + " ", + " ", + "#####", + " ", + "#####", + " ", + " " + ], + "╔": [ + " ", + " ", + " ###", + " # ", + " ###", + " # ", + " " + ], + "╗": [ + " ", + " ", + "### ", + " # ", + "### ", + " # ", + " " + ], + "╚": [ + " # ", + " ###", + " # ", + " ###", + " ", + " ", + " " + ], + "╝": [ + " # ", + "### ", + " # ", + "### ", + " ", + " ", + " " + ], + "╤": [ + " ", + "#####", + " # ", + " # ", + " # ", + " ", + " " + ], + "╥": [ + " ", + "#####", + " # ", + "#####", + " # ", + " ", + " " + ], + "╦": [ + " ", + "#####", + " # ", + "#####", + " # ", + " ", + " " + ], + "╭": [ + " ", + " ", + " ##", + " # ", + " # ", + " # ", + " " + ], + "╮": [ + " ", + " ", + "## ", + " # ", + " # ", + " # ", + " " + ], + "╯": [ + " # ", + " # ", + "## ", + " ", + " ", + " ", + " " + ], + "╰": [ + " # ", + " # ", + " ##", + " ", + " ", + " ", + " " + ], + "╹": [ + " # ", + " # ", + " # ", + " ", + " ", + " ", + " " + ], + "▀": [ + "#####", + "#####", + "#####", + " ", + " ", + " ", + " " + ], + "▇": [ + "#####", + "#####", + "#####", + "#####", + "#####", + " ", + " " + ], + "█": [ + "#####", + "#####", + "#####", + "#####", + "#####", + "#####", + " " + ], + "▒": [ + "# # #", + " # # ", + "# # #", + " # # ", + "# # #", + " # # ", + " " + ], + "■": [ + " ", + " ### ", + " ### ", + " ### ", + " ", + " ", + " " + ], + "□": [ + " ", + " ### ", + " # # ", + " ### ", + " ", + " ", + " " + ], + "▲": [ + " ", + " # ", + " ### ", + "#####", + " ", + " ", + " " + ], + "△": [ + " ", + " # ", + " # # ", + "# #", + "#####", + " ", + " " + ], + "▽": [ + " ", + "#####", + " ### ", + " # ", + " ", + " ", + " " + ], + "◉": [ + " ### ", + "## ##", + "## ##", + "## ##", + " ### ", + " ", + " " + ], + "◔": [ + " ", + " ### ", + "### #", + " # # ", + " ### ", + " ", + " " + ], + "◕": [ + " ", + " ### ", + " ### ", + " # # ", + " ### ", + " ", + " " + ], + "◞": [ + " ", + " # ", + " # ", + " # ", + " ", + " ", + " " + ], + "◟": [ + " ", + " # ", + " # ", + " # ", + " ", + " ", + " " + ], + "◠": [ + " ", + " # ", + " # # ", + " ", + " ", + " ", + " " + ], + "◡": [ + " ", + " ", + " ", + " # # ", + " # ", + " ", + " " + ], + "☀": [ + "# # #", + " ### ", + "## ##", + " ### ", + "# # #", + " ", + " " + ], + "☁": [ + " ", + " # # ", + "#####", + "#####", + " ", + " ", + " " + ], + "☃": [ + " # ", + " ### ", + " ### ", + "#####", + " # # ", + " ", + " " + ], + "★": [ + " # ", + " ### ", + "#####", + " # # ", + "# #", + " ", + " " + ], + "☆": [ + " # ", + " # # ", + "# #", + " # # ", + "# #", + " ", + " " + ], + "☉": [ + "# # #", + " ### ", + "## ##", + " ### ", + "# # #", + " ", + " " + ], + "☜": [ + " ", + " ## ", + " # ", + "#####", + " # ", + " ## ", + " " + ], + "☝": [ + " # ", + " ### ", + " # # ", + " # ", + " # ", + " ", + " " + ], + "☞": [ + " ", + " ## ", + " # ", + "#####", + " # ", + " ## ", + " " + ], + "☠": [ + " ### ", + "# # #", + " ### ", + " # ", + " # # ", + " ", + " " + ], + "☢": [ + " # ", + " # # ", + "# # #", + " ### ", + " # ", + " ", + " " + ], + "☭": [ + " ## ", + " ### ", + "# # ", + " # # ", + " ## ", + " ", + " " + ], + "☯": [ + " ### ", + "## ##", + "# # #", + "## ##", + " ### ", + " ", + " " + ], + "☺": [ + " ### ", + "# #", + "# # #", + "# #", + " ### ", + " ", + " " + ], + "♠": [ + " # ", + " ### ", + "#####", + "# # #", + " # ", + " ", + " " + ], + "♡": [ + " # # ", + "# # #", + "# #", + " # # ", + " # ", + " ", + " " + ], + "♣": [ + " # ", + " ### ", + "## ##", + " ### ", + " # ", + " ", + " " + ], + "♥": [ + " # # ", + "## ##", + "## ##", + " ### ", + " # ", + " ", + " " + ], + "♦": [ + " # ", + " ### ", + "#####", + " ### ", + " # ", + " ", + " " + ], + "♪": [ + " # ", + " ## ", + " # ", + " # ", + " ## ", + " ## ", + " " + ], + "♫": [ + " ###", + " # # ", + " # # ", + " # # ", + "## ##", + "## ##", + " " + ], + "♬": [ + " ###", + " ####", + " # # ", + " # # ", + "## ##", + " ", + " " + ], + "♭": [ + "# ", + "# ", + "### ", + "# # ", + "### ", + " ", + " " + ], + "♮": [ + " # # ", + " ### ", + "### #", + " # # ", + " ", + " ", + " " + ], + "♯": [ + " # # ", + "#####", + " # # ", + "#####", + " # # ", + " ", + " " + ], + "⚀": [ + " ### ", + "# #", + "# # #", + "# #", + " ### ", + " ", + " " + ], + "⚁": [ + " ### ", + "# ##", + "# #", + "## #", + " ### ", + " ", + " " + ], + "⚂": [ + " ### ", + "# # #", + "# #", + "# # #", + " ### ", + " ", + " " + ], + "⚃": [ + " ### ", + "# # #", + "# #", + "## ##", + " ### ", + " ", + " " + ], + "⚄": [ + " ### ", + "## ##", + "# #", + "## ##", + " ### ", + " ", + " " + ], + "⚅": [ + " ### ", + "## ##", + "# # #", + "## ##", + " ### ", + " ", + " " + ], + "⚆": [ + " ### ", + "#####", + "## ##", + "#####", + " ### ", + " ", + " " + ], + "✉": [ + "#####", + "## ##", + "# # #", + "# #", + "#####", + " ", + " " + ], + "✌": [ + " # # ", + " # # ", + " # # ", + " # ", + " ", + " ", + " " + ], + "✔": [ + " ", + " #", + " ##", + "# ## ", + " ## ", + " ", + " " + ], + "✧": [ + " # ", + " # # ", + "# #", + " # # ", + " # ", + " ", + " " + ], + "✿": [ + " ", + " # # ", + "# # #", + " # # ", + " ", + " ", + " " + ], + "❤": [ + " ## #", + "#### ", + "#### ", + " ### ", + " # ", + " ", + " " + ], + "⤏": [ + " ", + " ##", + "#####", + " ##", + " ", + " ", + " " + ], + "⤜": [ + " ", + "## ", + "#####", + "## ", + " ", + " ", + " " + ], + "⩽": [ + " # ", + " # ", + " # ", + "#####", + "#####", + " ", + " " + ], + "⩾": [ + " # ", + " # ", + " # ", + "#####", + "#####", + " ", + " " + ], + "ⱺ": [ + " ", + " ### ", + "# #", + "#####", + " ### ", + " ", + " " + ], + "⸑": [ + "# #", + " # # ", + " # ", + " ", + " ", + " ", + " " + ], + "⸫": [ + " # ", + " ", + " # ", + " ", + " # ", + " ", + " " + ], + "。": [ + " ", + " ", + " ", + " ", + " ## ", + " ## ", + " " + ], + "」": [ + " ###", + " # ", + " # ", + " # ", + " ###", + " ", + " " + ], + "っ": [ + " ", + " ## ", + " # ", + " ## ", + " ", + " ", + " " + ], + "つ": [ + " ", + " ###", + " # ", + " ###", + " ", + " ", + " " + ], + "づ": [ + " # # ", + " ###", + " # ", + " ###", + " ", + " ", + " " + ], + "゜": [ + " ## ", + "# # ", + " ## ", + " ", + " ", + " ", + " " + ], + "ェ": [ + " ## ", + "#### ", + " ## ", + " ", + " ", + " ", + " " + ], + "コ": [ + "### ", + " ", + "### ", + " ", + " ", + " ", + " " + ], + "シ": [ + " ", + "# ", + " # ", + " # ", + " # ", + " ", + " " + ], + "ツ": [ + " ", + " # #", + " # # ", + " # ", + " ", + " ", + " " + ], + "ノ": [ + " ", + " # ", + " # ", + " # ", + "# ", + " ", + " " + ], + "ヮ": [ + " ", + " ###", + " # # ", + " # ", + " ", + " ", + " " + ], + "ヽ": [ + " # ", + " # ", + "# ", + " ", + " ", + " ", + " " + ], + "ㅂ": [ + " ### ", + "# #", + " ### ", + " ", + " ", + " ", + " " + ], + "ㆆ": [ + " ### ", + "# #", + "# #", + " ### ", + " ", + " ", + " " + ], + "㇁": [ + " ", + " # ", + " # ", + " # ", + " ", + " ", + " " + ], + "人": [ + " # ", + " # # ", + "# #", + " ", + " ", + " ", + " " + ], + "卌": [ + " # # ", + "#####", + " # # ", + "#####", + " ", + " ", + " " + ], + "屮": [ + " # ", + " # ", + " ### ", + "# #", + " ", + " ", + " " + ], + "彡": [ + "# # ", + " # # ", + " ## ", + " ", + " ", + " ", + " " + ], + "由": [ + " ### ", + " # # ", + " ### ", + " # # ", + " ### ", + " ", + " " + ], + "皿": [ + "#####", + "# #", + "#####", + " ", + " ", + " ", + " " + ], + "益": [ + "#####", + " ### ", + "#####", + "# # #", + " ", + " ", + " " + ], + "ꇴ": [ + " ## ", + " # ", + " ## ", + " # #", + " ", + " ", + " " + ], + "꒡": [ + " # ", + " # # ", + " ", + " ", + " ", + " ", + " " + ], + "꒰": [ + " ##", + " # ", + " # ", + " # ", + " ##", + " ", + " " + ], + "꒱": [ + "## ", + " # ", + " # ", + " # ", + "## ", + " ", + " " + ], + "눈": [ + " # # ", + "## ##", + " ### ", + " ", + " ", + " ", + " " + ], + "︵": [ + " ", + " # ", + " # # ", + "# #", + " ", + " ", + " " + ], + "︶": [ + "# #", + " # # ", + " # ", + " ", + " ", + " ", + " " + ], + "︹": [ + " ### ", + " # ", + "# ", + " # ", + " ### ", + " ", + " " + ], + "︻": [ + " ### ", + " # ", + " # ", + " # ", + " ### ", + " ", + " " + ], + "︿": [ + " # ", + " # # ", + "# #", + " ", + " ", + " ", + " " + ], + "﹃": [ + "## ", + " # ", + " # ", + " # ", + " ", + " ", + " " + ], + "﹏": [ + " ", + " ", + " ", + " ", + " # # ", + "# #", + " " + ], + ")": [ + " # ", + " # ", + " # ", + " # ", + " # ", + " ", + " " + ], + "-": [ + " ", + " ", + "#####", + " ", + " ", + " ", + " " + ], + "/": [ + " ", + " #", + " # ", + " # ", + " # ", + "# ", + " " + ], + "\": [ + " ", + "# ", + " # ", + " # ", + " # ", + " #", + " " + ], + "^": [ + " # ", + " # # ", + " ", + " ", + " ", + " ", + " " + ], + "_": [ + " ", + " ", + " ", + " ", + " ", + "#####", + " " + ], + "`": [ + " # ", + " # ", + " ", + " ", + " ", + " ", + " " + ], + "|": [ + " # ", + " # ", + " # ", + " # ", + " # ", + " ", + " " + ], + "。": [ + " ", + " ", + " ", + " ", + " # ", + " ", + " " + ], + "・": [ + " ", + " ", + " # ", + " ", + " ", + " ", + " " + ], + "ェ": [ + " ## ", + "### ", + " ## ", + " ", + " ", + " ", + " " + ], + "ノ": [ + " ", + " # ", + " # ", + " # ", + "# ", + " ", + " " + ], + "ミ": [ + "# ", + " # ", + " # ", + " # ", + " ", + " ", + " " + ], + "゚": [ + " ## ", + "# # ", + " ## ", + " ", + " ", + " ", + " " + ], + " ̄": [ + "#####", + " ", + " ", + " ", + " ", + " ", + " " + ] +}; + +if (typeof module !== 'undefined' && module.exports) { + module.exports = { FONT_5x7 }; +} diff --git a/backup/app.js b/backup/app.js new file mode 100644 index 0000000..f303f5c --- /dev/null +++ b/backup/app.js @@ -0,0 +1,1756 @@ +let DISPLAY_WIDTH = 120; +const DISPLAY_HEIGHT = 60; +const BRIGHTNESS_THRESHOLD = 127; +const BORDER_BLACK_THRESHOLD = 64; +const BORDER_WHITE_THRESHOLD = 191; + +document.documentElement.style.setProperty('--col-count', DISPLAY_WIDTH); + + const upload = document.getElementById('upload'); + const dropzone = document.getElementById('dropzone'); + const matrix = document.getElementById('matrix'); + const exportBtn = document.getElementById('export'); + const invertBtn = document.getElementById('invert'); + const ditherBtn = document.getElementById('dither'); + const drawBtn = document.getElementById('draw'); + const eraseBtn = document.getElementById('erase'); + const resetBtn = document.getElementById('reset'); + const brushSizeSelect = document.getElementById('brushSize'); + const animationDurationInput = document.getElementById('animationDuration'); + const revertDurationBtn = document.getElementById('revertDuration'); + const textInputArea = document.getElementById('textInputArea'); + const modeRadios = document.querySelectorAll('input[name="mode"]'); + + let currentMode = "image"; + let drawMode = false; + let eraseMode = false; + let brushSize = 1; + let currentImage = null; + let currentFile = null; + let invertImageColors = false; + let useDithering = false; + let hasManualEdits = false; + let isDurationManuallyLocked = false; // Track if user manually adjusted duration + + // GIF-specific state + let isAnimatedGif = false; + let gifFrames = []; + let gifFrameEdits = []; + let currentFrameIndex = 0; + let gifReader = null; + let isPlaying = false; + let animationTimer = null; + + // Animation creation state + let isAnimationCreationMode = false; + let animationSourceFiles = []; // Store original files for multi-image animation + + // --- Helper functions --- + // Calculate default duration from frame count (0.2s per frame) + const DEFAULT_FRAME_DELAY = 0.2; // seconds per frame + + function calculateDurationFromFrames(frameCount) { + if (frameCount === 0) return 3.0; // default for empty + return Math.round(frameCount * DEFAULT_FRAME_DELAY * 10) / 10; // round to 0.1s + } + + function updateAnimationDuration() { + if (isDurationManuallyLocked) return; // Don't update if manually locked + + const frameCount = gifFrames.length; + const newDuration = calculateDurationFromFrames(frameCount); + animationDurationInput.value = newDuration.toFixed(1); + } + + // Handle drawing/erasing at specific grid coordinates with brush size + function toggleDotAtCoordinates(x, y) { + const radius = brushSize / 2; + + // Calculate the bounding box for the brush + const minX = Math.floor(x - radius); + const maxX = Math.floor(x + radius); + const minY = Math.floor(y - radius); + const maxY = Math.floor(y + radius); + + // Iterate through all dots in the bounding box + for (let cy = minY; cy <= maxY; cy++) { + for (let cx = minX; cx <= maxX; cx++) { + // Skip if outside display bounds + if (cx < 0 || cx >= DISPLAY_WIDTH || cy < 0 || cy >= DISPLAY_HEIGHT) continue; + + // Calculate distance from center + const distance = Math.sqrt((cx - x) * (cx - x) + (cy - y) * (cy - y)); + + // Only activate dots within the circular radius + if (distance <= radius) { + const dot = document.querySelector(`.dot[data-x="${cx}"][data-y="${cy}"]`); + if (!dot) continue; + + if (drawMode && !dot.classList.contains('on')) { + dot.classList.add('on'); + hasManualEdits = true; + trackDotEdit(cx, cy, 'add'); + } + if (eraseMode && dot.classList.contains('on')) { + dot.classList.remove('on'); + hasManualEdits = true; + trackDotEdit(cx, cy, 'remove'); + } + } + } + } + } + + // Calculate which dot is closest to the click position + function getDotCoordinatesFromEvent(e) { + const rect = matrix.getBoundingClientRect(); + const clickX = e.clientX - rect.left; + const clickY = e.clientY - rect.top; + + // Each dot is 10px + 1px gap = 11px total + const dotSize = 11; + + // Calculate grid coordinates + const gridX = Math.floor(clickX / dotSize); + const gridY = Math.floor(clickY / dotSize); + + return { x: gridX, y: gridY }; + } + + // Matrix-level event handlers for drawing + let isDrawing = false; + + function handleMatrixMouseDown(e) { + if (!drawMode && !eraseMode) return; + + isDrawing = true; + const coords = getDotCoordinatesFromEvent(e); + toggleDotAtCoordinates(coords.x, coords.y); + e.preventDefault(); + } + + function handleMatrixMouseMove(e) { + if (!isDrawing) return; + if (!drawMode && !eraseMode) return; + + const coords = getDotCoordinatesFromEvent(e); + toggleDotAtCoordinates(coords.x, coords.y); + e.preventDefault(); + } + + function handleMatrixMouseUp(e) { + isDrawing = false; + } + + function handleMatrixMouseLeave(e) { + isDrawing = false; + } + + function addDotInteraction(dot) { + // Individual dot interactions are now handled at the matrix level + // This function is kept for compatibility but does nothing + } + + function setButtonActive(button, active) { + button.classList.toggle('active', active); + } + + function toggleDrawEraseButtons(activeDraw) { + setButtonActive(drawBtn, activeDraw); + setButtonActive(eraseBtn, !activeDraw); + drawMode = activeDraw; + eraseMode = !activeDraw; + } + + // Helper function to track dot edits for GIF frames + function trackDotEdit(x, y, action) { + if (!isAnimatedGif) return; + + const key = `${x},${y}`; + if (action === 'add') { + gifFrameEdits[currentFrameIndex].addedDots.add(key); + gifFrameEdits[currentFrameIndex].removedDots.delete(key); + } else if (action === 'remove') { + gifFrameEdits[currentFrameIndex].removedDots.add(key); + gifFrameEdits[currentFrameIndex].addedDots.delete(key); + } + } + + // Helper function to create a frame object + function createFrameObject(sourceFile, delay = 20) { + return { + imageData: null, + delay: delay, + processed: false, + sourceFile: sourceFile + }; + } + + // Helper function to create a frame edits object + function createFrameEditsObject() { + return { + addedDots: new Set(), + removedDots: new Set() + }; + } + + // Helper function to apply a single dot edit to the DOM + function applyDotEditToDOM(key, action) { + const [x, y] = key.split(',').map(Number); + const dot = document.querySelector(`.dot[data-x="${x}"][data-y="${y}"]`); + + if (!dot) return; + + if (action === 'add' && !dot.classList.contains('on')) { + dot.classList.add('on'); + } else if (action === 'remove' && dot.classList.contains('on')) { + dot.classList.remove('on'); + } + } + + function applyDithering(data, width, height) { + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + const i = (y * width + x) * 4; + const oldGray = (data.data[i] + data.data[i+1] + data.data[i+2]) / 3; + const newGray = oldGray > BRIGHTNESS_THRESHOLD ? 255 : 0; + const error = oldGray - newGray; + + const val = invertImageColors ? 255 - newGray : newGray; + data.data[i] = data.data[i+1] = data.data[i+2] = val; + + // Distribute error to neighboring pixels + if (x + 1 < width) { + const ri = (y * width + (x + 1)) * 4; + data.data[ri] = data.data[ri] + error * 7/16; + data.data[ri+1] = data.data[ri+1] + error * 7/16; + data.data[ri+2] = data.data[ri+2] + error * 7/16; + } + if (y + 1 < height) { + if (x - 1 >= 0) { + const bli = ((y+1) * width + (x - 1)) * 4; + data.data[bli] = data.data[bli] + error * 3/16; + data.data[bli+1] = data.data[bli+1] + error * 3/16; + data.data[bli+2] = data.data[bli+2] + error * 3/16; + } + const bi = ((y+1) * width + x) * 4; + data.data[bi] = data.data[bi] + error * 5/16; + data.data[bi+1] = data.data[bi+1] + error * 5/16; + data.data[bi+2] = data.data[bi+2] + error * 5/16; + + if (x + 1 < width) { + const bri = ((y+1) * width + (x + 1)) * 4; + data.data[bri] = data.data[bri] + error * 1/16; + data.data[bri+1] = data.data[bri+1] + error * 1/16; + data.data[bri+2] = data.data[bri+2] + error * 1/16; + } + } + } + } + } + + function invertCurrentMatrix() { + const dots = document.querySelectorAll('.dot'); + dots.forEach(dot => { + dot.classList.toggle('on'); + }); + } + + async function generateJSONExport() { + if (currentMode !== "image") { + // Text mode not supported by this function + return null; + } + + if (isAnimatedGif) { + // Export all GIF frames as array of arrays + const allFrames = []; + + // First, ensure all frames are processed + for (let i = 0; i < gifFrames.length; i++) { + try { + // Use appropriate processing based on whether it's a GIF or multi-image animation + if (animationSourceFiles && animationSourceFiles.length > 0) { + // Multi-image animation + await processStaticImageFrame(i); + } else { + // GIF animation + await processGifFrameAsync(i); + } + } catch (err) { + console.error(`Error processing frame ${i}:`, err); + } + } + + // Now extract pixel data from all frames + for (let i = 0; i < gifFrames.length; i++) { + const frame = gifFrames[i]; + const edits = gifFrameEdits[i]; + const onDots = []; + + if (frame.imageData) { + const data = frame.imageData.data; + + // Extract "on" pixels from base frame + for (let y = 0; y < DISPLAY_HEIGHT; y++) { + for (let x = 0; x < DISPLAY_WIDTH; x++) { + const idx = (y * DISPLAY_WIDTH + x) * 4; + const brightness = (data[idx] + data[idx+1] + data[idx+2]) / 3; + const isOn = brightness > BRIGHTNESS_THRESHOLD; + const key = `${x},${y}`; + + // Apply manual edits + let finalState = isOn; + if (edits.addedDots.has(key)) finalState = true; + if (edits.removedDots.has(key)) finalState = false; + + if (finalState) { + onDots.push([x, y]); + } + } + } + } + + allFrames.push(onDots); + } + + return JSON.stringify(allFrames); + } else { + // Static image export + const onDots = Array.from(document.querySelectorAll('.dot.on')).map(el => [Number(el.dataset.x), Number(el.dataset.y)]); + return JSON.stringify(onDots); + } + } + + // Helper function to check if there's user content + function hasUserContent() { + // Check if there's a loaded file or manual edits or any dots turned on + if (currentFile) return true; + if (hasManualEdits) return true; + if (gifFrames.length > 0) return true; + const onDots = document.querySelectorAll('.dot.on'); + return onDots.length > 0; + } + + // --- Mode toggle --- + modeRadios.forEach(r => { + r.addEventListener('change', (e) => { + const newMode = r.value; + + // Check if switching from Image to Text mode with content + if (currentMode === "image" && newMode === "text" && hasUserContent()) { + if (!confirm('Switching to Text mode will clear all current content. Continue?')) { + // Revert the radio button selection + document.querySelector('input[name="mode"][value="image"]').checked = true; + e.preventDefault(); + return; + } + } + + stopAnimation(); + currentMode = newMode; + drawMode = false; + eraseMode = false; + hasManualEdits = false; + setButtonActive(drawBtn, false); + setButtonActive(eraseBtn, false); + + // Reset GIF state when switching modes + isAnimatedGif = false; + gifReader = null; + gifFrames = []; + gifFrameEdits = []; + animationSourceFiles = []; + currentFrameIndex = 0; + updateFrameControls(); + + if (currentMode === "image") { + dropzone.style.display = "block"; + textInputArea.style.display = "none"; + ditherBtn.style.display = "inline-block"; + drawBtn.style.display = "inline-block"; + eraseBtn.style.display = "inline-block"; + brushSizeSelect.parentElement.style.display = "inline-block"; + animationCreationCheckbox.disabled = false; + animationCreationCheckbox.parentElement.style.opacity = "1"; + animationDurationInput.parentElement.style.display = "inline-block"; + // Show revert button only if duration is locked + revertDurationBtn.style.display = isDurationManuallyLocked ? "inline-block" : "none"; + } else { + dropzone.style.display = "none"; + textInputArea.style.display = "block"; + ditherBtn.style.display = "none"; + drawBtn.style.display = "none"; + eraseBtn.style.display = "none"; + brushSizeSelect.parentElement.style.display = "none"; + animationCreationCheckbox.disabled = true; + animationCreationCheckbox.parentElement.style.opacity = "0.5"; + animationDurationInput.parentElement.style.display = "none"; + revertDurationBtn.style.display = "none"; // Always hidden in text mode + currentFile = null; // Clear file reference in text mode + emptyCanvas(); + } + }); + }); + + // --- Animation Creation checkbox --- + const animationCreationCheckbox = document.getElementById('animationCreation'); + + // Initialize controls visibility for default mode (image) + drawBtn.style.display = "inline-block"; + eraseBtn.style.display = "inline-block"; + brushSizeSelect.parentElement.style.display = "inline-block"; + animationCreationCheckbox.disabled = false; + animationCreationCheckbox.parentElement.style.opacity = "1"; + animationDurationInput.parentElement.style.display = "inline-block"; + revertDurationBtn.style.display = "none"; // Hidden by default (auto-update mode) + + // Helper function to toggle animation creation mode + function setAnimationCreationMode(enabled) { + isAnimationCreationMode = enabled; + animationCreationCheckbox.checked = enabled; + + if (enabled) { + upload.setAttribute('multiple', 'multiple'); + } else { + upload.removeAttribute('multiple'); + } + + // Show/hide revert button based on animation mode and lock state + if (enabled && isDurationManuallyLocked) { + revertDurationBtn.style.display = "inline-block"; + } else { + revertDurationBtn.style.display = "none"; + } + + updateFrameControls(); + } + + animationCreationCheckbox.addEventListener('change', () => { + setAnimationCreationMode(animationCreationCheckbox.checked); + }); + + function emptyCanvas() { + matrix.innerHTML = ''; + matrix.style.setProperty('--col-count', DISPLAY_WIDTH); + for (let y = 0; y < DISPLAY_HEIGHT; y++) { + for (let x = 0; x < DISPLAY_WIDTH; x++) { + const dot = document.createElement('div'); + dot.classList.add('dot'); + dot.dataset.x = x; + dot.dataset.y = y; + addDotInteraction(dot); + matrix.appendChild(dot); + } + } + updateFrameControls(); + } + + async function loadImage(file) { + // Check if file is a GIF + if (await isGifFile(file)) { + await loadGif(file); + } else { + // Existing static image logic + currentFile = file; + hasManualEdits = false; + isAnimatedGif = false; + gifReader = null; + gifFrames = []; + gifFrameEdits = []; + animationSourceFiles = []; + updateFrameControls(); + + const img = new Image(); + img.onload = () => { + currentImage = img; + processImageToBW(img, (bwCanvas) => { + const ctx = bwCanvas.getContext('2d'); + const imgData = ctx.getImageData(0, 0, bwCanvas.width, bwCanvas.height); + displayMatrix(imgData); + }); + }; + img.src = URL.createObjectURL(file); + } + } + + async function loadMultipleImages(files) { + currentFile = null; + gifReader = null; + + // Expand GIF files into individual frames + const expandedFiles = []; + for (const file of files) { + if (await isGifFile(file)) { + // Extract all frames from GIF + const frameBlobs = await extractGifFramesAsBlobs(file); + expandedFiles.push(...frameBlobs); + } else { + // Add non-GIF files as-is + expandedFiles.push(file); + } + } + + // New files to add (keep in upload order, don't sort) + const newFiles = expandedFiles; + + // Check if we're inserting into existing animation or starting fresh + const isInserting = animationSourceFiles.length > 0; + + if (isInserting) { + // Insert new frames after current frame + const insertPosition = currentFrameIndex + 1; + + // Split existing arrays at insertion point + const beforeFrames = animationSourceFiles.slice(0, insertPosition); + const afterFrames = animationSourceFiles.slice(insertPosition); + const beforeGifFrames = gifFrames.slice(0, insertPosition); + const afterGifFrames = gifFrames.slice(insertPosition); + const beforeEdits = gifFrameEdits.slice(0, insertPosition); + const afterEdits = gifFrameEdits.slice(insertPosition); + + // Rebuild source files array with insertion + animationSourceFiles = [...beforeFrames, ...newFiles, ...afterFrames]; + + // Initialize new frame objects + const newGifFrames = []; + const newGifFrameEdits = []; + for (let i = 0; i < newFiles.length; i++) { + newGifFrames.push(createFrameObject(newFiles[i])); + newGifFrameEdits.push(createFrameEditsObject()); + } + + // Rebuild frame arrays with insertion + gifFrames = [...beforeGifFrames, ...newGifFrames, ...afterGifFrames]; + gifFrameEdits = [...beforeEdits, ...newGifFrameEdits, ...afterEdits]; + + // Move to first newly inserted frame + currentFrameIndex = insertPosition; + + } else { + // Starting fresh - no existing animation + hasManualEdits = false; + animationSourceFiles = newFiles; + + // Initialize frame storage + const numFrames = newFiles.length; + gifFrames = new Array(numFrames); + gifFrameEdits = new Array(numFrames); + + for (let i = 0; i < numFrames; i++) { + gifFrames[i] = createFrameObject(newFiles[i]); + gifFrameEdits[i] = createFrameEditsObject(); + } + + currentFrameIndex = 0; + } + + // Set animation state + isAnimatedGif = gifFrames.length > 1; + + // Process and display current frame + await processStaticImageFrame(currentFrameIndex); + + // Update UI + updateFrameControls(); + updateFrameCounter(); + updateAnimationDuration(); + } + + async function processStaticImageFrame(frameIndex) { + if (frameIndex >= animationSourceFiles.length) return; + if (!gifFrames[frameIndex]) return; // Safety check for undefined frame + + const file = animationSourceFiles[frameIndex]; + + return new Promise((resolve, reject) => { + const img = new Image(); + img.onload = () => { + processImageToBW(img, (bwCanvas) => { + const ctx = bwCanvas.getContext('2d'); + const processedData = ctx.getImageData(0, 0, bwCanvas.width, bwCanvas.height); + + // Safety check - frame might have been cleared during async operation + if (!gifFrames[frameIndex]) { + resolve(); + return; + } + + // Store processed frame + gifFrames[frameIndex].imageData = processedData; + gifFrames[frameIndex].processed = true; + + // Display frame if it's the current one + if (frameIndex === currentFrameIndex) { + displayGifFrame(frameIndex); + } + + resolve(); + }); + }; + img.onerror = () => reject('Failed to load image'); + img.src = URL.createObjectURL(file); + }); + } + + async function isGifFile(file) { + const arrayBuffer = await file.arrayBuffer(); + const bytes = new Uint8Array(arrayBuffer); + // GIF89a magic: 0x47 0x49 0x46 0x38 0x39 0x61 + // GIF87a magic: 0x47 0x49 0x46 0x38 0x37 0x61 + return bytes.length >= 6 && + bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46 && + bytes[3] === 0x38 && (bytes[4] === 0x39 || bytes[4] === 0x37) && + bytes[5] === 0x61; + } + + async function loadGif(file) { + currentFile = file; + hasManualEdits = false; + + const arrayBuffer = await file.arrayBuffer(); + const bytes = new Uint8Array(arrayBuffer); + + try { + gifReader = new GifReader(bytes); + const numFrames = gifReader.numFrames(); + + // Initialize frame storage + gifFrames = new Array(numFrames); + gifFrameEdits = new Array(numFrames); + + for (let i = 0; i < numFrames; i++) { + const frameInfo = gifReader.frameInfo(i); + gifFrames[i] = { + imageData: null, + delay: frameInfo.delay, + processed: false + }; + gifFrameEdits[i] = { + addedDots: new Set(), + removedDots: new Set() + }; + } + + isAnimatedGif = numFrames > 1; + currentFrameIndex = 0; + + // Auto-switch to animation creation mode for animated GIFs + if (isAnimatedGif && !isAnimationCreationMode) { + setAnimationCreationMode(true); + } + + // If in animation creation mode, extract frames and use multi-image approach + if (isAnimationCreationMode && isAnimatedGif) { + const frameBlobs = await extractGifFramesAsBlobs(file); + await loadMultipleImages(frameBlobs); + return; // Exit early, loadMultipleImages handles the rest + } + + // Process and display first frame (for non-animation-creation mode) + processAndDisplayGifFrame(0); + + // Update UI + updateFrameControls(); + updateFrameCounter(); + updateAnimationDuration(); + + } catch (err) { + alert('Error loading GIF: ' + err.message); + console.error(err); + } + } + + // Helper function to extract all frames from a GIF as Blob objects + async function extractGifFramesAsBlobs(file) { + const arrayBuffer = await file.arrayBuffer(); + const bytes = new Uint8Array(arrayBuffer); + + try { + const reader = new GifReader(bytes); + const numFrames = reader.numFrames(); + const frameBlobs = []; + + for (let i = 0; i < numFrames; i++) { + // Decode frame from GIF to RGBA + const width = reader.width; + const height = reader.height; + const rgbaData = new Uint8Array(width * height * 4); + reader.decodeAndBlitFrameRGBA(i, rgbaData); + + // Create canvas with decoded frame + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext('2d'); + const imageData = ctx.createImageData(width, height); + imageData.data.set(rgbaData); + ctx.putImageData(imageData, 0, 0); + + // Convert canvas to Blob + const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/png')); + + // Create a synthetic file name + const originalName = file.name.replace(/\.[^/.]+$/, ''); // Remove extension + Object.defineProperty(blob, 'name', { + value: `${originalName}_frame${String(i + 1).padStart(3, '0')}.png`, + writable: false + }); + + frameBlobs.push(blob); + } + + return frameBlobs; + } catch (err) { + console.error('Error extracting GIF frames:', err); + return [file]; // Fallback to original file if extraction fails + } + } + + function processGifFrameAsync(frameIndex) { + return new Promise((resolve, reject) => { + if (!gifReader || frameIndex >= gifFrames.length || !gifFrames[frameIndex]) { + reject('Invalid frame index'); + return; + } + + // Check if frame already processed + if (gifFrames[frameIndex].processed) { + resolve(); + return; + } + + // Decode frame from GIF to RGBA + const width = gifReader.width; + const height = gifReader.height; + const rgbaData = new Uint8Array(width * height * 4); + gifReader.decodeAndBlitFrameRGBA(frameIndex, rgbaData); + + // Create temporary canvas with decoded frame + const tempCanvas = document.createElement('canvas'); + tempCanvas.width = width; + tempCanvas.height = height; + const tempCtx = tempCanvas.getContext('2d'); + const tempImageData = tempCtx.createImageData(width, height); + tempImageData.data.set(rgbaData); + tempCtx.putImageData(tempImageData, 0, 0); + + // Create Image object for processing + const img = new Image(); + img.onload = () => { + processImageToBW(img, (bwCanvas) => { + const ctx = bwCanvas.getContext('2d'); + const processedData = ctx.getImageData(0, 0, bwCanvas.width, bwCanvas.height); + + // Safety check - frame might have been cleared during async operation + if (!gifFrames[frameIndex]) { + resolve(); + return; + } + + // Store processed frame + gifFrames[frameIndex].imageData = processedData; + gifFrames[frameIndex].processed = true; + + resolve(); + }); + }; + img.onerror = () => reject('Failed to load frame'); + img.src = tempCanvas.toDataURL(); + }); + } + + function processAndDisplayGifFrame(frameIndex) { + if (frameIndex >= gifFrames.length || !gifFrames[frameIndex]) return; + + // Check if frame already processed with current settings + if (gifFrames[frameIndex].processed) { + displayGifFrame(frameIndex); + return; + } + + // For multi-image animations (not GIFs) + if (animationSourceFiles && animationSourceFiles.length > 0) { + processStaticImageFrame(frameIndex).then(() => { + displayGifFrame(frameIndex); + }).catch(err => { + console.error('Error processing static image frame:', err); + }); + return; + } + + // For GIF frames + if (!gifReader) return; + + // Use the async version but don't wait + processGifFrameAsync(frameIndex).then(() => { + displayGifFrame(frameIndex); + }).catch(err => { + console.error('Error processing GIF frame:', err); + }); + } + + function displayGifFrame(frameIndex) { + if (!gifFrames[frameIndex] || !gifFrames[frameIndex].imageData) return; + + currentFrameIndex = frameIndex; + const imageData = gifFrames[frameIndex].imageData; + + // Display base frame using existing function + displayMatrix(imageData); + + // Apply manual edits for this frame + const edits = gifFrameEdits[frameIndex]; + if (edits) { + edits.addedDots.forEach(key => applyDotEditToDOM(key, 'add')); + edits.removedDots.forEach(key => applyDotEditToDOM(key, 'remove')); + } + + // Update frame counter UI + updateFrameCounter(); + } + + function navigateToFrame(direction) { + if (!isAnimatedGif || gifFrames.length === 0) return; + + let newIndex = currentFrameIndex; + if (direction === 'next') { + newIndex = (currentFrameIndex + 1) % gifFrames.length; + } else if (direction === 'prev') { + newIndex = (currentFrameIndex - 1 + gifFrames.length) % gifFrames.length; + } + + processAndDisplayGifFrame(newIndex); + } + + async function deleteCurrentFrame() { + if (!isAnimatedGif || gifFrames.length <= 1) { + return; + } + + // Only allow deletion for multi-image animations (not GIFs) + if (!animationSourceFiles || animationSourceFiles.length === 0) { + return; + } + + stopAnimation(); + + // Remove frame from all arrays + animationSourceFiles.splice(currentFrameIndex, 1); + gifFrames.splice(currentFrameIndex, 1); + gifFrameEdits.splice(currentFrameIndex, 1); + + // Adjust current frame index + if (currentFrameIndex >= gifFrames.length) { + currentFrameIndex = gifFrames.length - 1; + } + + // Update animation state + isAnimatedGif = gifFrames.length > 1; + + // Display the new current frame + if (gifFrames.length > 0) { + if (animationSourceFiles.length > 0) { + await processStaticImageFrame(currentFrameIndex); + } else { + processAndDisplayGifFrame(currentFrameIndex); + } + updateFrameControls(); + updateFrameCounter(); + updateAnimationDuration(); + } else { + // No frames left, reset to empty canvas + isAnimatedGif = false; + emptyCanvas(); + updateFrameControls(); + updateFrameCounter(); + updateAnimationDuration(); + } + } + + // Helper function to capture current canvas state as a Blob + async function captureCurrentCanvasAsBlob(name = 'captured_frame') { + const canvas = document.createElement('canvas'); + canvas.width = DISPLAY_WIDTH; + canvas.height = DISPLAY_HEIGHT; + const ctx = canvas.getContext('2d'); + const imageData = ctx.createImageData(DISPLAY_WIDTH, DISPLAY_HEIGHT); + + // Read current state from DOM + for (let y = 0; y < DISPLAY_HEIGHT; y++) { + for (let x = 0; x < DISPLAY_WIDTH; x++) { + const dot = document.querySelector(`.dot[data-x="${x}"][data-y="${y}"]`); + const isOn = dot && dot.classList.contains('on'); + const idx = (y * DISPLAY_WIDTH + x) * 4; + const val = isOn ? 255 : 0; + imageData.data[idx] = val; // R + imageData.data[idx + 1] = val; // G + imageData.data[idx + 2] = val; // B + imageData.data[idx + 3] = 255; // A + } + } + + ctx.putImageData(imageData, 0, 0); + + const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/png')); + Object.defineProperty(blob, 'name', { + value: `${name}_${Date.now()}.png`, + writable: false + }); + + return blob; + } + + // Add an empty frame after the current one + async function addEmptyFrame() { + if (!isAnimationCreationMode) return; + + stopAnimation(); + + // If no frames exist, first capture the current canvas state + if (gifFrames.length === 0) { + const currentBlob = await captureCurrentCanvasAsBlob('current_frame'); + + // Add current state as frame 0 + animationSourceFiles.push(currentBlob); + gifFrames.push(createFrameObject(currentBlob)); + gifFrameEdits.push(createFrameEditsObject()); + + // Set to frame 0 + currentFrameIndex = 0; + + // Process frame 0 (this preserves the current drawing) + await processStaticImageFrame(0); + } + + // Now add the empty frame after the current frame + const insertPosition = currentFrameIndex + 1; + + // Create a blank canvas as a Blob + const canvas = document.createElement('canvas'); + canvas.width = DISPLAY_WIDTH; + canvas.height = DISPLAY_HEIGHT; + const ctx = canvas.getContext('2d'); + ctx.fillStyle = 'black'; + ctx.fillRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT); + + const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/png')); + Object.defineProperty(blob, 'name', { + value: `empty_frame_${Date.now()}.png`, + writable: false + }); + + // Insert into arrays + animationSourceFiles.splice(insertPosition, 0, blob); + gifFrames.splice(insertPosition, 0, createFrameObject(blob)); + gifFrameEdits.splice(insertPosition, 0, createFrameEditsObject()); + + // Update state and switch to new frame + isAnimatedGif = gifFrames.length > 1; + currentFrameIndex = insertPosition; + + // Process and display the new frame + await processStaticImageFrame(currentFrameIndex); + updateFrameControls(); + updateFrameCounter(); + updateAnimationDuration(); + } + + // Duplicate the current frame + async function duplicateCurrentFrame() { + if (!isAnimationCreationMode) return; + + stopAnimation(); + + // If no frames exist, first capture the current canvas state + if (gifFrames.length === 0) { + const currentBlob = await captureCurrentCanvasAsBlob('current_frame'); + + // Add current state as frame 0 + animationSourceFiles.push(currentBlob); + gifFrames.push(createFrameObject(currentBlob)); + gifFrameEdits.push(createFrameEditsObject()); + + // Set to frame 0 + currentFrameIndex = 0; + + // Process frame 0 + await processStaticImageFrame(0); + } + + // Now duplicate the current frame + const insertPosition = currentFrameIndex + 1; + + // Get current frame data + const currentFrame = gifFrames[currentFrameIndex]; + if (!currentFrame) return; // Safety check + if (!currentFrame.imageData) { + await processStaticImageFrame(currentFrameIndex); + } + + // Create a canvas with the current frame's content (including edits) + const canvas = document.createElement('canvas'); + canvas.width = DISPLAY_WIDTH; + canvas.height = DISPLAY_HEIGHT; + const ctx = canvas.getContext('2d'); + + // Draw the base image + ctx.putImageData(gifFrames[currentFrameIndex].imageData, 0, 0); + + // Apply edits to the canvas + const edits = gifFrameEdits[currentFrameIndex]; + const imageData = ctx.getImageData(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT); + + edits.addedDots.forEach(key => { + const [x, y] = key.split(',').map(Number); + const idx = (y * DISPLAY_WIDTH + x) * 4; + imageData.data[idx] = imageData.data[idx+1] = imageData.data[idx+2] = 255; + }); + + edits.removedDots.forEach(key => { + const [x, y] = key.split(',').map(Number); + const idx = (y * DISPLAY_WIDTH + x) * 4; + imageData.data[idx] = imageData.data[idx+1] = imageData.data[idx+2] = 0; + }); + + ctx.putImageData(imageData, 0, 0); + + // Convert to Blob + const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/png')); + Object.defineProperty(blob, 'name', { + value: `duplicate_frame_${Date.now()}.png`, + writable: false + }); + + // Insert into arrays + animationSourceFiles.splice(insertPosition, 0, blob); + gifFrames.splice(insertPosition, 0, createFrameObject(blob)); + gifFrameEdits.splice(insertPosition, 0, createFrameEditsObject()); + + // Update state and switch to new frame + isAnimatedGif = gifFrames.length > 1; + currentFrameIndex = insertPosition; + + // Process and display the new frame + await processStaticImageFrame(currentFrameIndex); + updateFrameControls(); + updateFrameCounter(); + updateAnimationDuration(); + } + + function updateFrameControls() { + const frameControls = document.getElementById('frameControls'); + const deleteBtn = document.getElementById('deleteFrame'); + const addEmptyBtn = document.getElementById('addEmptyFrame'); + const duplicateBtn = document.getElementById('duplicateFrame'); + const prevBtn = document.getElementById('prevFrame'); + const nextBtn = document.getElementById('nextFrame'); + const playBtn = document.getElementById('playStop'); + + // Show controls if: + // 1. Multiple frames exist (GIF or multi-image animation), OR + // 2. Animation creation mode is active (even with 0 frames, so user can add first frame) + const shouldShowControls = (gifFrames.length > 1 && (isAnimatedGif || animationSourceFiles.length > 0)) || + isAnimationCreationMode; + + if (shouldShowControls) { + frameControls.style.display = 'flex'; + + // Show navigation buttons (prev, next, play) only when there are 2+ frames + if (gifFrames.length > 1) { + prevBtn.style.display = 'inline-block'; + nextBtn.style.display = 'inline-block'; + playBtn.style.display = 'inline-block'; + } else { + prevBtn.style.display = 'none'; + nextBtn.style.display = 'none'; + playBtn.style.display = 'none'; + } + + // Show delete button when there are multiple frames and it's a multi-image animation + if (gifFrames.length > 1 && animationSourceFiles && animationSourceFiles.length > 0) { + deleteBtn.style.display = 'inline-block'; + } else { + deleteBtn.style.display = 'none'; + } + + // Show add empty frame button when in animation creation mode (even with 0 frames) + if (isAnimationCreationMode) { + addEmptyBtn.style.display = 'inline-block'; + } else { + addEmptyBtn.style.display = 'none'; + } + + // Show duplicate button when in animation creation mode (even with 0 frames) + if (isAnimationCreationMode) { + duplicateBtn.style.display = 'inline-block'; + } else { + duplicateBtn.style.display = 'none'; + } + } else { + frameControls.style.display = 'none'; + deleteBtn.style.display = 'none'; + addEmptyBtn.style.display = 'none'; + duplicateBtn.style.display = 'none'; + prevBtn.style.display = 'none'; + nextBtn.style.display = 'none'; + playBtn.style.display = 'none'; + } + } + + function updateFrameCounter() { + const counter = document.getElementById('frameCounter'); + const shouldShowCounter = (gifFrames.length > 1 && (isAnimatedGif || animationSourceFiles.length > 0)) || + (isAnimationCreationMode && gifFrames.length >= 1); + + if (shouldShowCounter) { + counter.textContent = `Frame ${currentFrameIndex + 1} of ${gifFrames.length}`; + } else { + counter.textContent = ''; + } + } + + function startAnimation() { + if (!isAnimatedGif || isPlaying) return; + + isPlaying = true; + const playStopBtn = document.getElementById('playStop'); + playStopBtn.textContent = '⏸ Stop'; + setButtonActive(playStopBtn, true); + + animationTimer = setInterval(() => { + navigateToFrame('next'); + }, 200); // 0.2 seconds + } + + function stopAnimation() { + if (!isPlaying) return; + + isPlaying = false; + const playStopBtn = document.getElementById('playStop'); + playStopBtn.textContent = '▶ Play'; + setButtonActive(playStopBtn, false); + + if (animationTimer) { + clearInterval(animationTimer); + animationTimer = null; + } + } + + function reprocessAllGifFrames() { + if (!isAnimatedGif || !gifReader) return; + + // Mark all frames as unprocessed + gifFrames.forEach(frame => { + frame.processed = false; + frame.imageData = null; + }); + + // Reprocess current frame + processAndDisplayGifFrame(currentFrameIndex); + } + + function detectBorderColor(img) { + const tmpCheck = document.createElement('canvas'); + tmpCheck.width = img.width; + tmpCheck.height = img.height; + const ctxCheck = tmpCheck.getContext('2d'); + ctxCheck.drawImage(img, 0, 0); + const checkData = ctxCheck.getImageData(0, 0, img.width, img.height); + + let blackCount = 0; + let whiteCount = 0; + let totalBorderPixels = 0; + + // Sample border pixels (top, bottom, left, right edges) + const sampleBorder = (x, y) => { + const i = (y * img.width + x) * 4; + const avg = (checkData.data[i] + checkData.data[i+1] + checkData.data[i+2]) / 3; + if (avg < BORDER_BLACK_THRESHOLD) blackCount++; + else if (avg > BORDER_WHITE_THRESHOLD) whiteCount++; + totalBorderPixels++; + }; + + // Sample top and bottom edges + for (let x = 0; x < img.width; x++) { + sampleBorder(x, 0); + sampleBorder(x, img.height - 1); + } + // Sample left and right edges (excluding corners already sampled) + for (let y = 1; y < img.height - 1; y++) { + sampleBorder(0, y); + sampleBorder(img.width - 1, y); + } + + // Determine background color based on border analysis + if (blackCount / totalBorderPixels > 0.5) { + return 'black'; + } else if (whiteCount / totalBorderPixels > 0.5) { + return 'white'; + } + return 'black'; // default + } + + function processImageToBW(img, callback) { + const tmp = document.createElement('canvas'); + tmp.width = DISPLAY_WIDTH; + tmp.height = DISPLAY_HEIGHT; + const ctx = tmp.getContext('2d'); + + // Calculate aspect ratios + const imgAspect = img.width / img.height; + const displayAspect = DISPLAY_WIDTH / DISPLAY_HEIGHT; + + let drawWidth, drawHeight, offsetX, offsetY; + + if (imgAspect > displayAspect) { + // Image is wider - fit by width, center vertically + drawWidth = DISPLAY_WIDTH; + drawHeight = DISPLAY_WIDTH / imgAspect; + offsetX = 0; + offsetY = (DISPLAY_HEIGHT - drawHeight) / 2; + } else { + // Image is taller - fit by height, center horizontally + drawHeight = DISPLAY_HEIGHT; + drawWidth = DISPLAY_HEIGHT * imgAspect; + offsetX = (DISPLAY_WIDTH - drawWidth) / 2; + offsetY = 0; + } + + // Detect and use border color for background + const bgColor = detectBorderColor(img); + ctx.fillStyle = bgColor; + ctx.fillRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT); + + ctx.drawImage(img, offsetX, offsetY, drawWidth, drawHeight); + const data = ctx.getImageData(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT); + + if (useDithering) { + applyDithering(data, DISPLAY_WIDTH, DISPLAY_HEIGHT); + } else { + // Simple threshold + for (let i = 0; i < data.data.length; i += 4) { + const avg = (data.data[i] + data.data[i+1] + data.data[i+2]) / 3; + const val = invertImageColors ? 255 - (avg > BRIGHTNESS_THRESHOLD ? 255 : 0) : (avg > BRIGHTNESS_THRESHOLD ? 255 : 0); + data.data[i] = data.data[i+1] = data.data[i+2] = val; + } + } + + ctx.putImageData(data, 0, 0); + callback(tmp); + } + + function displayMatrix(imageData) { + matrix.innerHTML = ''; + matrix.style.setProperty('--col-count', DISPLAY_WIDTH); + const d = imageData.data; + for (let y = 0; y < DISPLAY_HEIGHT; y++) { + for (let x = 0; x < DISPLAY_WIDTH; x++) { + const idx = (y * DISPLAY_WIDTH + x) * 4; + const brightness = (d[idx] + d[idx+1] + d[idx+2]) / 3; + const isOn = brightness > BRIGHTNESS_THRESHOLD; + const dot = document.createElement('div'); + dot.classList.add('dot'); + if (isOn) dot.classList.add('on'); + dot.dataset.x = x; + dot.dataset.y = y; + addDotInteraction(dot); + matrix.appendChild(dot); + } + } + } + + function renderCustomTextToMatrix(text, font, charWidth = 8, charHeight = 12, spacing = 1, lineSpacing = 2) { + const lines = text.split("\n"); + const widestLine = Math.max(...lines.map(line => line.length)); + const contentWidth = widestLine * (charWidth + spacing); + + // expand display width + DISPLAY_WIDTH = Math.max(60, contentWidth + 2); + document.documentElement.style.setProperty('--col-count', DISPLAY_WIDTH); + + const c = document.createElement("canvas"); + c.width = DISPLAY_WIDTH; + c.height = DISPLAY_HEIGHT; + const ctx = c.getContext("2d"); + + ctx.fillStyle = "black"; + ctx.fillRect(0, 0, c.width, c.height); + ctx.fillStyle = "white"; + let currentXWriteIndex = 0; + let startY = 1; + for (let l = 0; l < lines.length; l++) { + const line = lines[l]; + + for (let i = 0; i < line.length; i++) { + let ch = line[i]; + if(!font[ch]) { + ch = ch.toUpperCase(); + } + const glyph = font[ch] || font[" "]; + if (!glyph) continue; + for (let y = 0; y < glyph.length; y++) { + const row = glyph[y]; + for (let x = 0; x < row.length; x++) { + if (row.charAt(x) === "#") { + ctx.fillRect(1 + currentXWriteIndex + x, startY + y, 1, 1); + } + } + + } + currentXWriteIndex += glyph[0].length + spacing; + } + currentXWriteIndex = spacing; + startY += charHeight + lineSpacing; + } + + return { imageData: ctx.getImageData(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT), contentWidth }; + } + + // Auto-update text preview with debouncing (1 second after last keystroke) + let textUpdateTimer = null; + + function updateTextPreview() { + const text = document.getElementById("scrollText").value.trim(); + if (!text) { + emptyCanvas(); + document.getElementById('scrolls_output').value = ''; + return; + } + + currentFile = null; // Clear file reference for text content + hasManualEdits = false; + const { imageData, contentWidth } = renderCustomTextToMatrix(text, FONT_5x7, 5, 7, 1); + displayMatrix(imageData); + + // also update scrolls_output for server + const speed = Number(document.getElementById('scrollSpeed').value); + const directionRight = document.getElementById('scrollRight').checked; + const wrap = document.getElementById('scrollWrap').checked; + const scrollObj = [{ + x: 0, y: 0, + width: DISPLAY_WIDTH, + height: 8, + contentWidth, + speed, directionRight, wrap, + pixelsOn: [] + }]; + document.getElementById('scrolls_output').value = JSON.stringify(scrollObj); + } + + document.getElementById("scrollText").addEventListener("input", () => { + // Clear previous timer + if (textUpdateTimer) { + clearTimeout(textUpdateTimer); + } + + // Set new timer for 1 second + textUpdateTimer = setTimeout(() => { + updateTextPreview(); + }, 1000); + }); + + upload.addEventListener('change', e => { + const files = e.target.files; + if (!files || files.length === 0) return; + + // Auto-switch to animation creation mode if multiple files selected + if (files.length > 1 && !isAnimationCreationMode) { + setAnimationCreationMode(true); + } + + if (isAnimationCreationMode) { + // Animation creation mode - treat as animation (even single file) + loadMultipleImages(files); + } else { + // Single file - load normally + loadImage(files[0]); + } + }); + dropzone.addEventListener('click', () => upload.click()); + + // Drag-and-drop handlers + dropzone.addEventListener('dragover', (e) => { + e.preventDefault(); + e.stopPropagation(); + dropzone.style.borderColor = '#888'; + }); + + dropzone.addEventListener('dragleave', (e) => { + e.preventDefault(); + e.stopPropagation(); + dropzone.style.borderColor = '#555'; + }); + + dropzone.addEventListener('drop', (e) => { + e.preventDefault(); + e.stopPropagation(); + dropzone.style.borderColor = '#555'; + + const files = e.dataTransfer.files; + if (!files || files.length === 0) return; + + // Filter to only image files + const imageFiles = Array.from(files).filter(f => f.type.startsWith('image/')); + if (imageFiles.length === 0) return; + + // Auto-switch to animation creation mode if multiple files dropped + if (imageFiles.length > 1 && !isAnimationCreationMode) { + setAnimationCreationMode(true); + } + + if (isAnimationCreationMode) { + // Animation creation mode - treat as animation (even single file) + loadMultipleImages(imageFiles); + } else { + // Single file - load normally + loadImage(imageFiles[0]); + } + }); + + // Matrix drawing event handlers + matrix.addEventListener('mousedown', handleMatrixMouseDown); + matrix.addEventListener('mousemove', handleMatrixMouseMove); + matrix.addEventListener('mouseup', handleMatrixMouseUp); + matrix.addEventListener('mouseleave', handleMatrixMouseLeave); + + invertBtn.addEventListener('click', () => { + invertImageColors = !invertImageColors; + setButtonActive(invertBtn, invertImageColors); + + if (isAnimatedGif) { + // For GIFs: reprocess all frames with new invert setting + if (hasManualEdits && !confirm('Toggling invert will reprocess all frames. Manual edits will be preserved. Continue?')) { + invertImageColors = !invertImageColors; // Revert + setButtonActive(invertBtn, invertImageColors); + return; + } + reprocessAllGifFrames(); + } else { + // For static images: just invert current matrix (preserves edits) + invertCurrentMatrix(); + } + }); + + ditherBtn.addEventListener('click', () => { + if (hasManualEdits && !confirm('Toggling dither will reload the image and your manual edits may be lost. Continue?')) { + return; + } + useDithering = !useDithering; + ditherBtn.textContent = useDithering ? 'Dither: ON' : 'Dither: OFF'; + setButtonActive(ditherBtn, useDithering); + + if (isAnimatedGif) { + // For multi-image animations or GIFs + if (animationSourceFiles && animationSourceFiles.length > 0) { + // Multi-image animation - reprocess all frames + gifFrames.forEach(frame => { + frame.processed = false; + frame.imageData = null; + }); + processStaticImageFrame(currentFrameIndex); + } else { + // GIF animation - reprocess all frames + reprocessAllGifFrames(); + } + } else if (currentFile) { + loadImage(currentFile); + hasManualEdits = false; + } + }); + + drawBtn.addEventListener('click', () => { + toggleDrawEraseButtons(true); + }); + + eraseBtn.addEventListener('click', () => { + toggleDrawEraseButtons(false); + }); + + brushSizeSelect.addEventListener('change', (e) => { + brushSize = parseInt(e.target.value, 10); + }); + + // Lock duration when manually changed + animationDurationInput.addEventListener('input', () => { + isDurationManuallyLocked = true; + revertDurationBtn.style.display = 'inline-block'; + revertDurationBtn.style.fontWeight = 'bold'; + revertDurationBtn.style.color = '#f60'; + }); + + // Revert button to unlock and recalculate duration + revertDurationBtn.addEventListener('click', () => { + isDurationManuallyLocked = false; + revertDurationBtn.style.display = 'none'; + revertDurationBtn.style.fontWeight = 'normal'; + revertDurationBtn.style.color = ''; + updateAnimationDuration(); + }); + + resetBtn.addEventListener('click', () => { + stopAnimation(); + drawMode = false; + eraseMode = false; + setButtonActive(drawBtn, false); + setButtonActive(eraseBtn, false); + currentFile = null; // Clear file reference on reset + hasManualEdits = false; + + // Reset GIF state + isAnimatedGif = false; + gifReader = null; + gifFrames = []; + gifFrameEdits = []; + animationSourceFiles = []; + currentFrameIndex = 0; + + // Reset duration lock and UI + isDurationManuallyLocked = false; + revertDurationBtn.style.display = 'none'; + revertDurationBtn.style.fontWeight = 'normal'; + revertDurationBtn.style.color = ''; + + updateFrameControls(); + updateFrameCounter(); + updateAnimationDuration(); + + emptyCanvas(); + }); + + exportBtn.addEventListener('click', async () => { + if (currentMode === "image") { + const jsonExport = await generateJSONExport(); + document.getElementById('array_output').value = jsonExport; + document.getElementById('scrolls_output').value = ''; + } else { + const text = document.getElementById('scrollText').value.trim(); + if (!text) return alert("Enter some text first!"); + const speed = Number(document.getElementById('scrollSpeed').value); + const directionRight = document.getElementById('scrollRight').checked; + const wrap = document.getElementById('scrollWrap').checked; + + const { contentWidth } = renderCustomTextToMatrix(text, FONT_5x7, 5, 7, 1); + const onDots = Array.from(document.querySelectorAll('.dot.on')).map(el => [Number(el.dataset.x), Number(el.dataset.y)]); + document.getElementById('array_output').value = JSON.stringify(onDots); + + const scrollObj = [{ + x: 0, y: 0, + width: DISPLAY_WIDTH, + height: 8, + contentWidth, + speed, directionRight, wrap, + pixelsOn: [] + }]; + + document.getElementById('scrolls_output').value = JSON.stringify(scrollObj); + } + }); + + // Helper function to try frame reduction for binary export + // Calculate updateInterval from duration and frame count + // duration: total animation duration in seconds + // frameCount: number of frames + // returns: updateInterval in 10ms units (for binary format) + function calculateUpdateInterval(duration, frameCount) { + if (frameCount === 0) return 3; // default fallback + const frameDelay = duration / frameCount; // seconds per frame + const updateInterval = Math.round(frameDelay * 100); // convert to 10ms units + return Math.max(1, Math.min(255, updateInterval)); // clamp to 1-255 (1 byte range) + } + + // Reduce frames to target count by keeping frames uniformly + // Maintains the same total animation duration + function reduceFramesToTarget(frames, targetFrameCount, duration) { + if (frames.length <= targetFrameCount) { + const updateInterval = calculateUpdateInterval(duration, frames.length); + return { frames, updateInterval, skipPattern: 1 }; + } + + // Calculate how many frames to skip between kept frames + const skipPattern = Math.floor(frames.length / targetFrameCount); + const reducedFrames = []; + + // Keep every Nth frame + for (let i = 0; i < frames.length; i += skipPattern) { + reducedFrames.push(frames[i]); + if (reducedFrames.length >= targetFrameCount) break; + } + + // Calculate new updateInterval to maintain same total duration + const updateInterval = calculateUpdateInterval(duration, reducedFrames.length); + + return { + frames: reducedFrames, + updateInterval, + skipPattern + }; + } + + // Download Binary button - converts JSON export to binary format + document.getElementById('downloadBinary').addEventListener('click', async () => { + if (currentMode === "image") { + try { + const MAX_BINARY_SIZE = 16 * 1024; // 16KB flash limit + + // Get JSON export using shared function + let jsonExport = await generateJSONExport(); + + // For single images, wrap in array for parseJSONFrames compatibility + if (!isAnimatedGif) { + const singleFrame = JSON.parse(jsonExport); + jsonExport = JSON.stringify([singleFrame]); + } + + // Always use 120x60 for image mode (DISPLAY_WIDTH can change in text mode) + const imageWidth = 120; + const imageHeight = 60; + + // Parse frames first to get actual count + let frames = JSON.parse(jsonExport); + const originalFrameCount = frames.length; + + // Get animation duration from input field + const animationDuration = parseFloat(document.getElementById('animationDuration').value) || 3.0; + + // Calculate updateInterval based on duration and frame count + const initialUpdateInterval = calculateUpdateInterval(animationDuration, originalFrameCount); + + const binaryOptions = { + startX: 0, + startY: 0, + updateInterval: initialUpdateInterval, + width: imageWidth, + height: imageHeight, + maxFrames: originalFrameCount // Use actual frame count, not arbitrary limit + }; + + // Generate initial binary + let binaryData = jsonToBinary(jsonExport, binaryOptions); + const originalSize = binaryData.length; + const TARGET_FRAME_COUNT = 17; + + // Check if we need to reduce frames due to size limit + if (binaryData.length > MAX_BINARY_SIZE && frames.length > 1) { + // Try reducing to target frame count (maintains duration) + const reduction = reduceFramesToTarget(frames, TARGET_FRAME_COUNT, animationDuration); + + // Update options with new updateInterval + const reducedOptions = { + ...binaryOptions, + maxFrames: reduction.frames.length, + updateInterval: reduction.updateInterval + }; + + // Generate binary with reduced frames + const reducedBinary = jsonToBinary(JSON.stringify(reduction.frames), reducedOptions); + + if (reducedBinary.length <= MAX_BINARY_SIZE) { + // Success - frames fit within size limit + const droppedCount = originalFrameCount - reduction.frames.length; + const originalFrameDelay = (animationDuration / originalFrameCount * 1000).toFixed(1); + const newFrameDelay = (animationDuration / reduction.frames.length * 1000).toFixed(1); + alert( + `Binary size reduction applied:\n\n` + + `Original: ${originalSize} bytes (${originalFrameCount} frames)\n` + + `Reduced: ${reducedBinary.length} bytes (${reduction.frames.length} frames)\n` + + `Maximum: ${MAX_BINARY_SIZE} bytes\n\n` + + `Kept every ${reduction.skipPattern}${reduction.skipPattern === 1 ? 'st' : reduction.skipPattern === 2 ? 'nd' : reduction.skipPattern === 3 ? 'rd' : 'th'} frame (${droppedCount} frames removed)\n` + + `Duration maintained: ${animationDuration}s\n` + + `Frame delay adjusted: ${originalFrameDelay}ms → ${newFrameDelay}ms\n` + + `Update interval: ${binaryOptions.updateInterval} → ${reduction.updateInterval}` + ); + frames = reduction.frames; + binaryData = reducedBinary; + binaryOptions.updateInterval = reduction.updateInterval; + binaryOptions.maxFrames = reduction.frames.length; + } else { + // Even with reduction to target, still too large + alert( + `Binary file is too large to flash!\n\n` + + `Size: ${reducedBinary.length} bytes\n` + + `Maximum: ${MAX_BINARY_SIZE} bytes\n\n` + + `Even after reducing to ${TARGET_FRAME_COUNT} frames, the file exceeds the limit.\n` + + `Try reducing image complexity.` + ); + return; + } + } else if (binaryData.length > MAX_BINARY_SIZE && frames.length === 1) { + // Single frame too large + alert( + `Binary file is too large to flash!\n\n` + + `Size: ${binaryData.length} bytes\n` + + `Maximum: ${MAX_BINARY_SIZE} bytes\n\n` + + `This is a single frame. Try reducing image complexity.` + ); + return; + } + + // Create blob and trigger download + const blob = new Blob([binaryData], { type: 'application/octet-stream' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'animation.bin'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + + console.log(`Binary file created: ${binaryData.length} bytes (${frames.length} frames)`); + } catch (err) { + alert('Error creating binary file: ' + err.message); + console.error(err); + } + } else { + alert('Binary export is only available in Image Mode.'); + } + }); + + // Frame navigation event listeners + document.getElementById('prevFrame').addEventListener('click', () => { + stopAnimation(); + navigateToFrame('prev'); + }); + + document.getElementById('playStop').addEventListener('click', () => { + if (isPlaying) { + stopAnimation(); + } else { + startAnimation(); + } + }); + + document.getElementById('nextFrame').addEventListener('click', () => { + stopAnimation(); + navigateToFrame('next'); + }); + + document.getElementById('deleteFrame').addEventListener('click', () => { + deleteCurrentFrame(); + }); + + document.getElementById('addEmptyFrame').addEventListener('click', () => { + addEmptyFrame(); + }); + + document.getElementById('duplicateFrame').addEventListener('click', () => { + duplicateCurrentFrame(); + }); + + // Keyboard shortcuts for frame navigation + document.addEventListener('keydown', (e) => { + if (!isAnimatedGif) return; + + if (e.key === 'ArrowLeft' && !e.target.matches('input, textarea')) { + e.preventDefault(); + stopAnimation(); + navigateToFrame('prev'); + } else if (e.key === 'ArrowRight' && !e.target.matches('input, textarea')) { + e.preventDefault(); + stopAnimation(); + navigateToFrame('next'); + } + }); + + window.addEventListener('load', emptyCanvas); diff --git a/backup/avj2305/Artikel1.bin b/backup/avj2305/Artikel1.bin new file mode 100644 index 0000000..1104d29 Binary files /dev/null and b/backup/avj2305/Artikel1.bin differ diff --git a/backup/avj2305/Artikel1.png b/backup/avj2305/Artikel1.png new file mode 100644 index 0000000..9ebe401 Binary files /dev/null and b/backup/avj2305/Artikel1.png differ diff --git a/backup/avj2305/Artikel21.bin b/backup/avj2305/Artikel21.bin new file mode 100644 index 0000000..a52cd83 Binary files /dev/null and b/backup/avj2305/Artikel21.bin differ diff --git a/backup/avj2305/Artikel21.png b/backup/avj2305/Artikel21.png new file mode 100644 index 0000000..2e848e1 Binary files /dev/null and b/backup/avj2305/Artikel21.png differ diff --git a/backup/avj2305/Frosch161.bin b/backup/avj2305/Frosch161.bin new file mode 100644 index 0000000..9b4deed Binary files /dev/null and b/backup/avj2305/Frosch161.bin differ diff --git a/backup/avj2305/Frosch161.png b/backup/avj2305/Frosch161.png new file mode 100644 index 0000000..9e27cf2 Binary files /dev/null and b/backup/avj2305/Frosch161.png differ diff --git a/backup/avj2305/LaufSpruch1.bin b/backup/avj2305/LaufSpruch1.bin new file mode 100644 index 0000000..6bb9af7 Binary files /dev/null and b/backup/avj2305/LaufSpruch1.bin differ diff --git a/backup/avj2305/LaufSpruch1.png b/backup/avj2305/LaufSpruch1.png new file mode 100644 index 0000000..6554ad4 Binary files /dev/null and b/backup/avj2305/LaufSpruch1.png differ diff --git a/backup/avj2305/LaufSpruch2.bin b/backup/avj2305/LaufSpruch2.bin new file mode 100644 index 0000000..48ee107 Binary files /dev/null and b/backup/avj2305/LaufSpruch2.bin differ diff --git a/backup/avj2305/LaufSpruch2.png b/backup/avj2305/LaufSpruch2.png new file mode 100644 index 0000000..36b9cc0 Binary files /dev/null and b/backup/avj2305/LaufSpruch2.png differ diff --git a/backup/avj2305/LaufSpruch3.bin b/backup/avj2305/LaufSpruch3.bin new file mode 100644 index 0000000..f57ad0c Binary files /dev/null and b/backup/avj2305/LaufSpruch3.bin differ diff --git a/backup/avj2305/LaufSpruch3.png b/backup/avj2305/LaufSpruch3.png new file mode 100644 index 0000000..034b7eb Binary files /dev/null and b/backup/avj2305/LaufSpruch3.png differ diff --git a/backup/avj2305/MvAVJ.bin b/backup/avj2305/MvAVJ.bin new file mode 100644 index 0000000..d08c64d Binary files /dev/null and b/backup/avj2305/MvAVJ.bin differ diff --git a/backup/avj2305/MvAVJ.png b/backup/avj2305/MvAVJ.png new file mode 100644 index 0000000..2ace381 Binary files /dev/null and b/backup/avj2305/MvAVJ.png differ diff --git a/backup/avj2305/NotWidersetzen.bin b/backup/avj2305/NotWidersetzen.bin new file mode 100644 index 0000000..fd4389c Binary files /dev/null and b/backup/avj2305/NotWidersetzen.bin differ diff --git a/backup/avj2305/NotWidersetzen.png b/backup/avj2305/NotWidersetzen.png new file mode 100644 index 0000000..4977a05 Binary files /dev/null and b/backup/avj2305/NotWidersetzen.png differ diff --git a/backup/avj2305/Schildkroete.bin b/backup/avj2305/Schildkroete.bin new file mode 100644 index 0000000..9cfb723 Binary files /dev/null and b/backup/avj2305/Schildkroete.bin differ diff --git a/backup/avj2305/Schildkroete.png b/backup/avj2305/Schildkroete.png new file mode 100644 index 0000000..eabec85 Binary files /dev/null and b/backup/avj2305/Schildkroete.png differ diff --git a/backup/avj2305/Widersetzen.bin b/backup/avj2305/Widersetzen.bin new file mode 100644 index 0000000..1230f33 Binary files /dev/null and b/backup/avj2305/Widersetzen.bin differ diff --git a/backup/avj2305/Widersetzen.png b/backup/avj2305/Widersetzen.png new file mode 100644 index 0000000..6609f00 Binary files /dev/null and b/backup/avj2305/Widersetzen.png differ diff --git a/backup/avj2305/Widersetzen1.bin b/backup/avj2305/Widersetzen1.bin new file mode 100644 index 0000000..af4d1dd Binary files /dev/null and b/backup/avj2305/Widersetzen1.bin differ diff --git a/backup/avj2305/Widersetzen1.png b/backup/avj2305/Widersetzen1.png new file mode 100644 index 0000000..0dd9983 Binary files /dev/null and b/backup/avj2305/Widersetzen1.png differ diff --git a/backup/avj2305/Zitat1.bin b/backup/avj2305/Zitat1.bin new file mode 100644 index 0000000..2260c55 Binary files /dev/null and b/backup/avj2305/Zitat1.bin differ diff --git a/backup/avj2305/Zitat1.png b/backup/avj2305/Zitat1.png new file mode 100644 index 0000000..27d92ff Binary files /dev/null and b/backup/avj2305/Zitat1.png differ diff --git a/backup/avj2305/Zitat2.bin b/backup/avj2305/Zitat2.bin new file mode 100644 index 0000000..f43ecbf Binary files /dev/null and b/backup/avj2305/Zitat2.bin differ diff --git a/backup/avj2305/Zitat2.png b/backup/avj2305/Zitat2.png new file mode 100644 index 0000000..cdf0baa Binary files /dev/null and b/backup/avj2305/Zitat2.png differ diff --git a/backup/avj2305/Zitat3.bin b/backup/avj2305/Zitat3.bin new file mode 100644 index 0000000..18808b3 Binary files /dev/null and b/backup/avj2305/Zitat3.bin differ diff --git a/backup/avj2305/Zitat3.png b/backup/avj2305/Zitat3.png new file mode 100644 index 0000000..a35eaa8 Binary files /dev/null and b/backup/avj2305/Zitat3.png differ diff --git a/backup/binfmt.js b/backup/binfmt.js new file mode 100644 index 0000000..bc07326 --- /dev/null +++ b/backup/binfmt.js @@ -0,0 +1,165 @@ +// Binary Framebuffer Encoder +// Converts pixel data to binary format for bus display + +const MAGIC = [66, 78, 23, 238]; +const DrawEntryType = { + Image: 1, + Animation: 2, + HScroll: 3, + VScroll: 4, + Line: 5 +}; + +function bitmapSizeBytes(width, height) { + return Math.floor((width * height + 7) / 8); +} + +function setPixelInBitmap(x, y, memory, width, height, value) { + const index = y * width + x; + const byteIndex = Math.floor(index / 8); + const bitIndex = index % 8; + if (value) { + memory[byteIndex] |= 1 << bitIndex; + } else { + memory[byteIndex] &= ~(1 << bitIndex); + } +} + +class BinaryFramebufferEncoder { + entries = []; + + addImage(posX, posY, width, height, pixelData) { + const data = new Uint8Array(bitmapSizeBytes(width, height)); + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + setPixelInBitmap(x, y, data, width, height, pixelData[y][x]); + } + } + this.entries.push({ + type: DrawEntryType.Image, + header: { posX, posY }, + width, + height, + data + }); + } + + addAnimation(posX, posY, width, height, frameCount, updateInterval, frames) { + const frameSize = bitmapSizeBytes(width, height); + const data = new Uint8Array(frameSize * frameCount); + for (let f = 0; f < frameCount; f++) { + const frameData = data.subarray(f * frameSize, (f + 1) * frameSize); + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + setPixelInBitmap(x, y, frameData, width, height, frames[f][y][x]); + } + } + } + this.entries.push({ + type: DrawEntryType.Animation, + header: { posX, posY }, + width, + height, + frameCount, + updateInterval, + data + }); + } + + encode() { + let totalSize = 5; + for (const entry of this.entries) { + totalSize += 3; + switch (entry.type) { + case DrawEntryType.Image: + totalSize += 2 + entry.data.length; + break; + case DrawEntryType.Animation: + totalSize += 4 + entry.data.length; + break; + } + } + const buffer = new Uint8Array(totalSize); + let pos = 0; + buffer.set(MAGIC, pos); + pos += 4; + buffer[pos++] = this.entries.length; + for (const entry of this.entries) { + buffer[pos++] = entry.type; + buffer[pos++] = entry.header.posX; + buffer[pos++] = entry.header.posY; + switch (entry.type) { + case DrawEntryType.Image: + buffer[pos++] = entry.width; + buffer[pos++] = entry.height; + buffer.set(entry.data, pos); + pos += entry.data.length; + break; + case DrawEntryType.Animation: + buffer[pos++] = entry.width; + buffer[pos++] = entry.height; + buffer[pos++] = entry.frameCount; + buffer[pos++] = entry.updateInterval; + buffer.set(entry.data, pos); + pos += entry.data.length; + break; + } + } + return buffer; + } +} + +function parseJSONFrames(text, width = 120, height = 60, maxFrames = 50) { + const json = JSON.parse(text); + if (!Array.isArray(json)) { + throw new Error("JSON must be an array of frames"); + } + const frames = []; + for (const frame of json) { + if (!Array.isArray(frame)) { + throw new Error("Each frame must be an array of coordinates"); + } + const pixels = Array(height).fill(null).map(() => Array(width).fill(false)); + for (const change of frame) { + const [x, y] = change; + if (y < pixels.length && x < pixels[y].length) { + pixels[y][x] = true; + } + } + if (frames.length >= maxFrames) continue; + frames.push(pixels); + } + return frames; +} + +function jsonToBinary(jsonString, options = {}) { + const { + startX = 0, + startY = 0, + updateInterval = 3, + width = 120, + height = 60, + maxFrames = 50 + } = options; + const encoder = new BinaryFramebufferEncoder(); + const frames = parseJSONFrames(jsonString, width, height, maxFrames); + if (frames.length === 1) { + encoder.addImage(startX, startY, width, height, frames[0]); + } else { + encoder.addAnimation(startX, startY, width, height, frames.length, updateInterval, frames); + } + return encoder.encode(); +} + +// Helper function to drop every nth frame from a frame array +function dropEveryNthFrame(frames, n) { + if (n <= 1 || frames.length <= 1) return frames; + return frames.filter((frame, index) => (index + 1) % n !== 0); +} + +// Helper function to keep 1 frame, drop 2 frames in repeating pattern +// Pattern: keep, drop, drop, keep, drop, drop, ... +function keepOneDropTwo(frames) { + if (frames.length <= 1) return frames; + return frames.filter((frame, index) => index % 3 === 0); +} diff --git a/backup/editor/index.html b/backup/editor/index.html new file mode 100644 index 0000000..c91f6e3 --- /dev/null +++ b/backup/editor/index.html @@ -0,0 +1,112 @@ + + + + + + + MonoDisplay Editor + + + + + + +
+ MonoDisplay +
+ + + +
+ + + untitled +
+
+ +
+ +
+
+ preview · 120 × 60 +
+ +
+ demos +
+
+
+ + - +
+
+ + - +
+
+ + + + +
+
+
+ + +
+ + +
+
+
+
+
+
+ + + + + diff --git a/backup/editor/public/mono-display.js b/backup/editor/public/mono-display.js new file mode 100644 index 0000000..ea17394 --- /dev/null +++ b/backup/editor/public/mono-display.js @@ -0,0 +1,5302 @@ +var __create = Object.create; +var __getProtoOf = Object.getPrototypeOf; +var __defProp = Object.defineProperty; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +function __accessProp(key) { + return this[key]; +} +var __toESMCache_node; +var __toESMCache_esm; +var __toESM = (mod, isNodeMode, target) => { + var canCache = mod != null && typeof mod === "object"; + if (canCache) { + var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap; + var cached = cache.get(mod); + if (cached) + return cached; + } + target = mod != null ? __create(__getProtoOf(mod)) : {}; + const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target; + for (let key of __getOwnPropNames(mod)) + if (!__hasOwnProp.call(to, key)) + __defProp(to, key, { + get: __accessProp.bind(mod, key), + enumerable: true + }); + if (canCache) + cache.set(mod, to); + return to; +}; +var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports); +var __returnValue = (v) => v; +function __exportSetter(name, newValue) { + this[name] = __returnValue.bind(null, newValue); +} +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { + get: all[name], + enumerable: true, + configurable: true, + set: __exportSetter.bind(all, name) + }); +}; + +// node_modules/mithril/render/vnode.js +var require_vnode = __commonJS((exports, module) => { + function Vnode(tag, key, attrs, children, text, dom) { + return { tag, key, attrs, children, text, dom, is: undefined, domSize: undefined, state: undefined, events: undefined, instance: undefined }; + } + Vnode.normalize = function(node) { + if (Array.isArray(node)) + return Vnode("[", undefined, undefined, Vnode.normalizeChildren(node), undefined, undefined); + if (node == null || typeof node === "boolean") + return null; + if (typeof node === "object") + return node; + return Vnode("#", undefined, undefined, String(node), undefined, undefined); + }; + Vnode.normalizeChildren = function(input) { + var children = new Array(input.length); + var numKeyed = 0; + for (var i = 0;i < input.length; i++) { + children[i] = Vnode.normalize(input[i]); + if (children[i] !== null && children[i].key != null) + numKeyed++; + } + if (numKeyed !== 0 && numKeyed !== input.length) { + throw new TypeError(children.includes(null) ? "In fragments, vnodes must either all have keys or none have keys. You may wish to consider using an explicit keyed empty fragment, m.fragment({key: ...}), instead of a hole." : "In fragments, vnodes must either all have keys or none have keys."); + } + return children; + }; + module.exports = Vnode; +}); + +// node_modules/mithril/render/hyperscriptVnode.js +var require_hyperscriptVnode = __commonJS((exports, module) => { + var Vnode = require_vnode(); + module.exports = function(attrs, children) { + if (attrs == null || typeof attrs === "object" && attrs.tag == null && !Array.isArray(attrs)) { + if (children.length === 1 && Array.isArray(children[0])) + children = children[0]; + } else { + children = children.length === 0 && Array.isArray(attrs) ? attrs : [attrs, ...children]; + attrs = undefined; + } + return Vnode("", attrs && attrs.key, attrs, children); + }; +}); + +// node_modules/mithril/util/hasOwn.js +var require_hasOwn = __commonJS((exports, module) => { + module.exports = {}.hasOwnProperty; +}); + +// node_modules/mithril/render/emptyAttrs.js +var require_emptyAttrs = __commonJS((exports, module) => { + module.exports = {}; +}); + +// node_modules/mithril/render/cachedAttrsIsStaticMap.js +var require_cachedAttrsIsStaticMap = __commonJS((exports, module) => { + var emptyAttrs = require_emptyAttrs(); + module.exports = new Map([[emptyAttrs, true]]); +}); + +// node_modules/mithril/render/hyperscript.js +var require_hyperscript = __commonJS((exports, module) => { + var Vnode = require_vnode(); + var hyperscriptVnode = require_hyperscriptVnode(); + var hasOwn = require_hasOwn(); + var emptyAttrs = require_emptyAttrs(); + var cachedAttrsIsStaticMap = require_cachedAttrsIsStaticMap(); + var selectorParser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g; + var selectorCache = Object.create(null); + function isEmpty(object) { + for (var key in object) + if (hasOwn.call(object, key)) + return false; + return true; + } + function isFormAttributeKey(key) { + return key === "value" || key === "checked" || key === "selectedIndex" || key === "selected"; + } + function compileSelector(selector) { + var match, tag = "div", classes = [], attrs = {}, isStatic = true; + while (match = selectorParser.exec(selector)) { + var type = match[1], value = match[2]; + if (type === "" && value !== "") + tag = value; + else if (type === "#") + attrs.id = value; + else if (type === ".") + classes.push(value); + else if (match[3][0] === "[") { + var attrValue = match[6]; + if (attrValue) + attrValue = attrValue.replace(/\\(["'])/g, "$1").replace(/\\\\/g, "\\"); + if (match[4] === "class") + classes.push(attrValue); + else { + attrs[match[4]] = attrValue === "" ? attrValue : attrValue || true; + if (isFormAttributeKey(match[4])) + isStatic = false; + } + } + } + if (classes.length > 0) + attrs.className = classes.join(" "); + if (isEmpty(attrs)) + attrs = emptyAttrs; + else + cachedAttrsIsStaticMap.set(attrs, isStatic); + return selectorCache[selector] = { tag, attrs, is: attrs.is }; + } + function execSelector(state, vnode) { + vnode.tag = state.tag; + var attrs = vnode.attrs; + if (attrs == null) { + vnode.attrs = state.attrs; + vnode.is = state.is; + return vnode; + } + if (hasOwn.call(attrs, "class")) { + if (attrs.class != null) + attrs.className = attrs.class; + attrs.class = null; + } + if (state.attrs !== emptyAttrs) { + var className = attrs.className; + attrs = Object.assign({}, state.attrs, attrs); + if (state.attrs.className != null) + attrs.className = className != null ? String(state.attrs.className) + " " + String(className) : state.attrs.className; + } + if (state.tag === "input" && hasOwn.call(attrs, "type")) { + attrs = Object.assign({ type: attrs.type }, attrs); + } + vnode.is = attrs.is; + vnode.attrs = attrs; + return vnode; + } + function hyperscript(selector, attrs, ...children) { + if (selector == null || typeof selector !== "string" && typeof selector !== "function" && typeof selector.view !== "function") { + throw Error("The selector must be either a string or a component."); + } + var vnode = hyperscriptVnode(attrs, children); + if (typeof selector === "string") { + vnode.children = Vnode.normalizeChildren(vnode.children); + if (selector !== "[") + return execSelector(selectorCache[selector] || compileSelector(selector), vnode); + } + if (vnode.attrs == null) + vnode.attrs = {}; + vnode.tag = selector; + return vnode; + } + module.exports = hyperscript; +}); + +// node_modules/mithril/render/trust.js +var require_trust = __commonJS((exports, module) => { + var Vnode = require_vnode(); + module.exports = function(html) { + if (html == null) + html = ""; + return Vnode("<", undefined, undefined, html, undefined, undefined); + }; +}); + +// node_modules/mithril/render/fragment.js +var require_fragment = __commonJS((exports, module) => { + var Vnode = require_vnode(); + var hyperscriptVnode = require_hyperscriptVnode(); + module.exports = function(attrs, ...children) { + var vnode = hyperscriptVnode(attrs, children); + if (vnode.attrs == null) + vnode.attrs = {}; + vnode.tag = "["; + vnode.children = Vnode.normalizeChildren(vnode.children); + return vnode; + }; +}); + +// node_modules/mithril/hyperscript.js +var require_hyperscript2 = __commonJS((exports, module) => { + var hyperscript = require_hyperscript(); + hyperscript.trust = require_trust(); + hyperscript.fragment = require_fragment(); + module.exports = hyperscript; +}); + +// node_modules/mithril/render/delayedRemoval.js +var require_delayedRemoval = __commonJS((exports, module) => { + module.exports = new WeakMap; +}); + +// node_modules/mithril/render/domFor.js +var require_domFor = __commonJS((exports, module) => { + var delayedRemoval = require_delayedRemoval(); + function* domFor(vnode) { + var dom = vnode.dom; + var domSize = vnode.domSize; + var generation = delayedRemoval.get(dom); + if (dom != null) + do { + var nextSibling = dom.nextSibling; + if (delayedRemoval.get(dom) === generation) { + yield dom; + domSize--; + } + dom = nextSibling; + } while (domSize); + } + module.exports = domFor; +}); + +// node_modules/mithril/render/render.js +var require_render = __commonJS((exports, module) => { + var Vnode = require_vnode(); + var delayedRemoval = require_delayedRemoval(); + var domFor = require_domFor(); + var cachedAttrsIsStaticMap = require_cachedAttrsIsStaticMap(); + module.exports = function() { + var nameSpace = { + svg: "http://www.w3.org/2000/svg", + math: "http://www.w3.org/1998/Math/MathML" + }; + var currentRedraw; + var currentRender; + function getDocument(dom) { + return dom.ownerDocument; + } + function getNameSpace(vnode) { + return vnode.attrs && vnode.attrs.xmlns || nameSpace[vnode.tag]; + } + function checkState(vnode, original) { + if (vnode.state !== original) + throw new Error("'vnode.state' must not be modified."); + } + function callHook(vnode) { + var original = vnode.state; + try { + return this.apply(original, arguments); + } finally { + checkState(vnode, original); + } + } + function activeElement(dom) { + try { + return getDocument(dom).activeElement; + } catch (e) { + return null; + } + } + function createNodes(parent, vnodes, start, end, hooks, nextSibling, ns) { + for (var i = start;i < end; i++) { + var vnode = vnodes[i]; + if (vnode != null) { + createNode(parent, vnode, hooks, ns, nextSibling); + } + } + } + function createNode(parent, vnode, hooks, ns, nextSibling) { + var tag = vnode.tag; + if (typeof tag === "string") { + vnode.state = {}; + if (vnode.attrs != null) + initLifecycle(vnode.attrs, vnode, hooks); + switch (tag) { + case "#": + createText(parent, vnode, nextSibling); + break; + case "<": + createHTML(parent, vnode, ns, nextSibling); + break; + case "[": + createFragment(parent, vnode, hooks, ns, nextSibling); + break; + default: + createElement(parent, vnode, hooks, ns, nextSibling); + } + } else + createComponent(parent, vnode, hooks, ns, nextSibling); + } + function createText(parent, vnode, nextSibling) { + vnode.dom = getDocument(parent).createTextNode(vnode.children); + insertDOM(parent, vnode.dom, nextSibling); + } + var possibleParents = { caption: "table", thead: "table", tbody: "table", tfoot: "table", tr: "tbody", th: "tr", td: "tr", colgroup: "table", col: "colgroup" }; + function createHTML(parent, vnode, ns, nextSibling) { + var match = vnode.children.match(/^\s*?<(\w+)/im) || []; + var temp = getDocument(parent).createElement(possibleParents[match[1]] || "div"); + if (ns === "http://www.w3.org/2000/svg") { + temp.innerHTML = '' + vnode.children + ""; + temp = temp.firstChild; + } else { + temp.innerHTML = vnode.children; + } + vnode.dom = temp.firstChild; + vnode.domSize = temp.childNodes.length; + var fragment = getDocument(parent).createDocumentFragment(); + var child; + while (child = temp.firstChild) { + fragment.appendChild(child); + } + insertDOM(parent, fragment, nextSibling); + } + function createFragment(parent, vnode, hooks, ns, nextSibling) { + var fragment = getDocument(parent).createDocumentFragment(); + if (vnode.children != null) { + var children = vnode.children; + createNodes(fragment, children, 0, children.length, hooks, null, ns); + } + vnode.dom = fragment.firstChild; + vnode.domSize = fragment.childNodes.length; + insertDOM(parent, fragment, nextSibling); + } + function createElement(parent, vnode, hooks, ns, nextSibling) { + var tag = vnode.tag; + var attrs = vnode.attrs; + var is = vnode.is; + ns = getNameSpace(vnode) || ns; + var element = ns ? is ? getDocument(parent).createElementNS(ns, tag, { is }) : getDocument(parent).createElementNS(ns, tag) : is ? getDocument(parent).createElement(tag, { is }) : getDocument(parent).createElement(tag); + vnode.dom = element; + if (attrs != null) { + setAttrs(vnode, attrs, ns); + } + insertDOM(parent, element, nextSibling); + if (!maybeSetContentEditable(vnode)) { + if (vnode.children != null) { + var children = vnode.children; + createNodes(element, children, 0, children.length, hooks, null, ns); + if (vnode.tag === "select" && attrs != null) + setLateSelectAttrs(vnode, attrs); + } + } + } + function initComponent(vnode, hooks) { + var sentinel; + if (typeof vnode.tag.view === "function") { + vnode.state = Object.create(vnode.tag); + sentinel = vnode.state.view; + if (sentinel.$$reentrantLock$$ != null) + return; + sentinel.$$reentrantLock$$ = true; + } else { + vnode.state = undefined; + sentinel = vnode.tag; + if (sentinel.$$reentrantLock$$ != null) + return; + sentinel.$$reentrantLock$$ = true; + vnode.state = vnode.tag.prototype != null && typeof vnode.tag.prototype.view === "function" ? new vnode.tag(vnode) : vnode.tag(vnode); + } + initLifecycle(vnode.state, vnode, hooks); + if (vnode.attrs != null) + initLifecycle(vnode.attrs, vnode, hooks); + vnode.instance = Vnode.normalize(callHook.call(vnode.state.view, vnode)); + if (vnode.instance === vnode) + throw Error("A view cannot return the vnode it received as argument"); + sentinel.$$reentrantLock$$ = null; + } + function createComponent(parent, vnode, hooks, ns, nextSibling) { + initComponent(vnode, hooks); + if (vnode.instance != null) { + createNode(parent, vnode.instance, hooks, ns, nextSibling); + vnode.dom = vnode.instance.dom; + vnode.domSize = vnode.instance.domSize; + } else { + vnode.domSize = 0; + } + } + function updateNodes(parent, old, vnodes, hooks, nextSibling, ns) { + if (old === vnodes || old == null && vnodes == null) + return; + else if (old == null || old.length === 0) + createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, ns); + else if (vnodes == null || vnodes.length === 0) + removeNodes(parent, old, 0, old.length); + else { + var isOldKeyed = old[0] != null && old[0].key != null; + var isKeyed = vnodes[0] != null && vnodes[0].key != null; + var start = 0, oldStart = 0; + if (!isOldKeyed) + while (oldStart < old.length && old[oldStart] == null) + oldStart++; + if (!isKeyed) + while (start < vnodes.length && vnodes[start] == null) + start++; + if (isOldKeyed !== isKeyed) { + removeNodes(parent, old, oldStart, old.length); + createNodes(parent, vnodes, start, vnodes.length, hooks, nextSibling, ns); + } else if (!isKeyed) { + var commonLength = old.length < vnodes.length ? old.length : vnodes.length; + start = start < oldStart ? start : oldStart; + for (;start < commonLength; start++) { + o = old[start]; + v = vnodes[start]; + if (o === v || o == null && v == null) + continue; + else if (o == null) + createNode(parent, v, hooks, ns, getNextSibling(old, start + 1, nextSibling)); + else if (v == null) + removeNode(parent, o); + else + updateNode(parent, o, v, hooks, getNextSibling(old, start + 1, nextSibling), ns); + } + if (old.length > commonLength) + removeNodes(parent, old, start, old.length); + if (vnodes.length > commonLength) + createNodes(parent, vnodes, start, vnodes.length, hooks, nextSibling, ns); + } else { + var oldEnd = old.length - 1, end = vnodes.length - 1, map, o, v, oe, ve, topSibling; + while (oldEnd >= oldStart && end >= start) { + oe = old[oldEnd]; + ve = vnodes[end]; + if (oe.key !== ve.key) + break; + if (oe !== ve) + updateNode(parent, oe, ve, hooks, nextSibling, ns); + if (ve.dom != null) + nextSibling = ve.dom; + oldEnd--, end--; + } + while (oldEnd >= oldStart && end >= start) { + o = old[oldStart]; + v = vnodes[start]; + if (o.key !== v.key) + break; + oldStart++, start++; + if (o !== v) + updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), ns); + } + while (oldEnd >= oldStart && end >= start) { + if (start === end) + break; + if (o.key !== ve.key || oe.key !== v.key) + break; + topSibling = getNextSibling(old, oldStart, nextSibling); + moveDOM(parent, oe, topSibling); + if (oe !== v) + updateNode(parent, oe, v, hooks, topSibling, ns); + if (++start <= --end) + moveDOM(parent, o, nextSibling); + if (o !== ve) + updateNode(parent, o, ve, hooks, nextSibling, ns); + if (ve.dom != null) + nextSibling = ve.dom; + oldStart++; + oldEnd--; + oe = old[oldEnd]; + ve = vnodes[end]; + o = old[oldStart]; + v = vnodes[start]; + } + while (oldEnd >= oldStart && end >= start) { + if (oe.key !== ve.key) + break; + if (oe !== ve) + updateNode(parent, oe, ve, hooks, nextSibling, ns); + if (ve.dom != null) + nextSibling = ve.dom; + oldEnd--, end--; + oe = old[oldEnd]; + ve = vnodes[end]; + } + if (start > end) + removeNodes(parent, old, oldStart, oldEnd + 1); + else if (oldStart > oldEnd) + createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns); + else { + var originalNextSibling = nextSibling, vnodesLength = end - start + 1, oldIndices = new Array(vnodesLength), li = 0, i = 0, pos = 2147483647, matched = 0, map, lisIndices; + for (i = 0;i < vnodesLength; i++) + oldIndices[i] = -1; + for (i = end;i >= start; i--) { + if (map == null) + map = getKeyMap(old, oldStart, oldEnd + 1); + ve = vnodes[i]; + var oldIndex = map[ve.key]; + if (oldIndex != null) { + pos = oldIndex < pos ? oldIndex : -1; + oldIndices[i - start] = oldIndex; + oe = old[oldIndex]; + old[oldIndex] = null; + if (oe !== ve) + updateNode(parent, oe, ve, hooks, nextSibling, ns); + if (ve.dom != null) + nextSibling = ve.dom; + matched++; + } + } + nextSibling = originalNextSibling; + if (matched !== oldEnd - oldStart + 1) + removeNodes(parent, old, oldStart, oldEnd + 1); + if (matched === 0) + createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns); + else { + if (pos === -1) { + lisIndices = makeLisIndices(oldIndices); + li = lisIndices.length - 1; + for (i = end;i >= start; i--) { + v = vnodes[i]; + if (oldIndices[i - start] === -1) + createNode(parent, v, hooks, ns, nextSibling); + else { + if (lisIndices[li] === i - start) + li--; + else + moveDOM(parent, v, nextSibling); + } + if (v.dom != null) + nextSibling = vnodes[i].dom; + } + } else { + for (i = end;i >= start; i--) { + v = vnodes[i]; + if (oldIndices[i - start] === -1) + createNode(parent, v, hooks, ns, nextSibling); + if (v.dom != null) + nextSibling = vnodes[i].dom; + } + } + } + } + } + } + } + function updateNode(parent, old, vnode, hooks, nextSibling, ns) { + var oldTag = old.tag, tag = vnode.tag; + if (oldTag === tag && old.is === vnode.is) { + vnode.state = old.state; + vnode.events = old.events; + if (shouldNotUpdate(vnode, old)) + return; + if (typeof oldTag === "string") { + if (vnode.attrs != null) { + updateLifecycle(vnode.attrs, vnode, hooks); + } + switch (oldTag) { + case "#": + updateText(old, vnode); + break; + case "<": + updateHTML(parent, old, vnode, ns, nextSibling); + break; + case "[": + updateFragment(parent, old, vnode, hooks, nextSibling, ns); + break; + default: + updateElement(old, vnode, hooks, ns); + } + } else + updateComponent(parent, old, vnode, hooks, nextSibling, ns); + } else { + removeNode(parent, old); + createNode(parent, vnode, hooks, ns, nextSibling); + } + } + function updateText(old, vnode) { + if (old.children.toString() !== vnode.children.toString()) { + old.dom.nodeValue = vnode.children; + } + vnode.dom = old.dom; + } + function updateHTML(parent, old, vnode, ns, nextSibling) { + if (old.children !== vnode.children) { + removeDOM(parent, old); + createHTML(parent, vnode, ns, nextSibling); + } else { + vnode.dom = old.dom; + vnode.domSize = old.domSize; + } + } + function updateFragment(parent, old, vnode, hooks, nextSibling, ns) { + updateNodes(parent, old.children, vnode.children, hooks, nextSibling, ns); + var domSize = 0, children = vnode.children; + vnode.dom = null; + if (children != null) { + for (var i = 0;i < children.length; i++) { + var child = children[i]; + if (child != null && child.dom != null) { + if (vnode.dom == null) + vnode.dom = child.dom; + domSize += child.domSize || 1; + } + } + } + vnode.domSize = domSize; + } + function updateElement(old, vnode, hooks, ns) { + var element = vnode.dom = old.dom; + ns = getNameSpace(vnode) || ns; + if (old.attrs != vnode.attrs || vnode.attrs != null && !cachedAttrsIsStaticMap.get(vnode.attrs)) { + updateAttrs(vnode, old.attrs, vnode.attrs, ns); + } + if (!maybeSetContentEditable(vnode)) { + updateNodes(element, old.children, vnode.children, hooks, null, ns); + } + } + function updateComponent(parent, old, vnode, hooks, nextSibling, ns) { + vnode.instance = Vnode.normalize(callHook.call(vnode.state.view, vnode)); + if (vnode.instance === vnode) + throw Error("A view cannot return the vnode it received as argument"); + updateLifecycle(vnode.state, vnode, hooks); + if (vnode.attrs != null) + updateLifecycle(vnode.attrs, vnode, hooks); + if (vnode.instance != null) { + if (old.instance == null) + createNode(parent, vnode.instance, hooks, ns, nextSibling); + else + updateNode(parent, old.instance, vnode.instance, hooks, nextSibling, ns); + vnode.dom = vnode.instance.dom; + vnode.domSize = vnode.instance.domSize; + } else { + if (old.instance != null) + removeNode(parent, old.instance); + vnode.domSize = 0; + } + } + function getKeyMap(vnodes, start, end) { + var map = Object.create(null); + for (;start < end; start++) { + var vnode = vnodes[start]; + if (vnode != null) { + var key = vnode.key; + if (key != null) + map[key] = start; + } + } + return map; + } + var lisTemp = []; + function makeLisIndices(a) { + var result = [0]; + var u = 0, v = 0, i = 0; + var il = lisTemp.length = a.length; + for (var i = 0;i < il; i++) + lisTemp[i] = a[i]; + for (var i = 0;i < il; ++i) { + if (a[i] === -1) + continue; + var j = result[result.length - 1]; + if (a[j] < a[i]) { + lisTemp[i] = j; + result.push(i); + continue; + } + u = 0; + v = result.length - 1; + while (u < v) { + var c = (u >>> 1) + (v >>> 1) + (u & v & 1); + if (a[result[c]] < a[i]) { + u = c + 1; + } else { + v = c; + } + } + if (a[i] < a[result[u]]) { + if (u > 0) + lisTemp[i] = result[u - 1]; + result[u] = i; + } + } + u = result.length; + v = result[u - 1]; + while (u-- > 0) { + result[u] = v; + v = lisTemp[v]; + } + lisTemp.length = 0; + return result; + } + function getNextSibling(vnodes, i, nextSibling) { + for (;i < vnodes.length; i++) { + if (vnodes[i] != null && vnodes[i].dom != null) + return vnodes[i].dom; + } + return nextSibling; + } + function moveDOM(parent, vnode, nextSibling) { + if (vnode.dom != null) { + var target; + if (vnode.domSize == null || vnode.domSize === 1) { + target = vnode.dom; + } else { + target = getDocument(parent).createDocumentFragment(); + for (var dom of domFor(vnode)) + target.appendChild(dom); + } + insertDOM(parent, target, nextSibling); + } + } + function insertDOM(parent, dom, nextSibling) { + if (nextSibling != null) + parent.insertBefore(dom, nextSibling); + else + parent.appendChild(dom); + } + function maybeSetContentEditable(vnode) { + if (vnode.attrs == null || vnode.attrs.contenteditable == null && vnode.attrs.contentEditable == null) + return false; + var children = vnode.children; + if (children != null && children.length === 1 && children[0].tag === "<") { + var content = children[0].children; + if (vnode.dom.innerHTML !== content) + vnode.dom.innerHTML = content; + } else if (children != null && children.length !== 0) + throw new Error("Child node of a contenteditable must be trusted."); + return true; + } + function removeNodes(parent, vnodes, start, end) { + for (var i = start;i < end; i++) { + var vnode = vnodes[i]; + if (vnode != null) + removeNode(parent, vnode); + } + } + function tryBlockRemove(parent, vnode, source, counter) { + var original = vnode.state; + var result = callHook.call(source.onbeforeremove, vnode); + if (result == null) + return; + var generation = currentRender; + for (var dom of domFor(vnode)) + delayedRemoval.set(dom, generation); + counter.v++; + Promise.resolve(result).finally(function() { + checkState(vnode, original); + tryResumeRemove(parent, vnode, counter); + }); + } + function tryResumeRemove(parent, vnode, counter) { + if (--counter.v === 0) { + onremove(vnode); + removeDOM(parent, vnode); + } + } + function removeNode(parent, vnode) { + var counter = { v: 1 }; + if (typeof vnode.tag !== "string" && typeof vnode.state.onbeforeremove === "function") + tryBlockRemove(parent, vnode, vnode.state, counter); + if (vnode.attrs && typeof vnode.attrs.onbeforeremove === "function") + tryBlockRemove(parent, vnode, vnode.attrs, counter); + tryResumeRemove(parent, vnode, counter); + } + function removeDOM(parent, vnode) { + if (vnode.dom == null) + return; + if (vnode.domSize == null || vnode.domSize === 1) { + parent.removeChild(vnode.dom); + } else { + for (var dom of domFor(vnode)) + parent.removeChild(dom); + } + } + function onremove(vnode) { + if (typeof vnode.tag !== "string" && typeof vnode.state.onremove === "function") + callHook.call(vnode.state.onremove, vnode); + if (vnode.attrs && typeof vnode.attrs.onremove === "function") + callHook.call(vnode.attrs.onremove, vnode); + if (typeof vnode.tag !== "string") { + if (vnode.instance != null) + onremove(vnode.instance); + } else { + if (vnode.events != null) + vnode.events._ = null; + var children = vnode.children; + if (Array.isArray(children)) { + for (var i = 0;i < children.length; i++) { + var child = children[i]; + if (child != null) + onremove(child); + } + } + } + } + function setAttrs(vnode, attrs, ns) { + for (var key in attrs) { + setAttr(vnode, key, null, attrs[key], ns); + } + } + function setAttr(vnode, key, old, value, ns) { + if (key === "key" || value == null || isLifecycleMethod(key) || old === value && !isFormAttribute(vnode, key) && typeof value !== "object") + return; + if (key[0] === "o" && key[1] === "n") + return updateEvent(vnode, key, value); + if (key.slice(0, 6) === "xlink:") + vnode.dom.setAttributeNS("http://www.w3.org/1999/xlink", key.slice(6), value); + else if (key === "style") + updateStyle(vnode.dom, old, value); + else if (hasPropertyKey(vnode, key, ns)) { + if (key === "value") { + if ((vnode.tag === "input" || vnode.tag === "textarea") && vnode.dom.value === "" + value) + return; + if (vnode.tag === "select" && old !== null && vnode.dom.value === "" + value) + return; + if (vnode.tag === "option" && old !== null && vnode.dom.value === "" + value) + return; + if (vnode.tag === "input" && vnode.attrs.type === "file" && "" + value !== "") { + console.error("`value` is read-only on file inputs!"); + return; + } + } + if (vnode.tag === "input" && key === "type") + vnode.dom.setAttribute(key, value); + else + vnode.dom[key] = value; + } else { + if (typeof value === "boolean") { + if (value) + vnode.dom.setAttribute(key, ""); + else + vnode.dom.removeAttribute(key); + } else + vnode.dom.setAttribute(key === "className" ? "class" : key, value); + } + } + function removeAttr(vnode, key, old, ns) { + if (key === "key" || old == null || isLifecycleMethod(key)) + return; + if (key[0] === "o" && key[1] === "n") + updateEvent(vnode, key, undefined); + else if (key === "style") + updateStyle(vnode.dom, old, null); + else if (hasPropertyKey(vnode, key, ns) && key !== "className" && key !== "title" && !(key === "value" && (vnode.tag === "option" || vnode.tag === "select" && vnode.dom.selectedIndex === -1 && vnode.dom === activeElement(vnode.dom))) && !(vnode.tag === "input" && key === "type")) { + vnode.dom[key] = null; + } else { + var nsLastIndex = key.indexOf(":"); + if (nsLastIndex !== -1) + key = key.slice(nsLastIndex + 1); + if (old !== false) + vnode.dom.removeAttribute(key === "className" ? "class" : key); + } + } + function setLateSelectAttrs(vnode, attrs) { + if ("value" in attrs) { + if (attrs.value === null) { + if (vnode.dom.selectedIndex !== -1) + vnode.dom.value = null; + } else { + var normalized = "" + attrs.value; + if (vnode.dom.value !== normalized || vnode.dom.selectedIndex === -1) { + vnode.dom.value = normalized; + } + } + } + if ("selectedIndex" in attrs) + setAttr(vnode, "selectedIndex", null, attrs.selectedIndex, undefined); + } + function updateAttrs(vnode, old, attrs, ns) { + var val; + if (old != null) { + if (old === attrs && !cachedAttrsIsStaticMap.has(attrs)) { + console.warn("Don't reuse attrs object, use new object for every redraw, this will throw in next major"); + } + for (var key in old) { + if ((val = old[key]) != null && (attrs == null || attrs[key] == null)) { + removeAttr(vnode, key, val, ns); + } + } + } + if (attrs != null) { + for (var key in attrs) { + setAttr(vnode, key, old && old[key], attrs[key], ns); + } + } + } + function isFormAttribute(vnode, attr) { + return attr === "value" || attr === "checked" || attr === "selectedIndex" || attr === "selected" && (vnode.dom === activeElement(vnode.dom) || vnode.tag === "option" && vnode.dom.parentNode === activeElement(vnode.dom)); + } + function isLifecycleMethod(attr) { + return attr === "oninit" || attr === "oncreate" || attr === "onupdate" || attr === "onremove" || attr === "onbeforeremove" || attr === "onbeforeupdate"; + } + function hasPropertyKey(vnode, key, ns) { + return ns === undefined && (vnode.tag.indexOf("-") > -1 || vnode.is || key !== "href" && key !== "list" && key !== "form" && key !== "width" && key !== "height") && key in vnode.dom; + } + function updateStyle(element, old, style) { + if (old === style) {} else if (style == null) { + element.style = ""; + } else if (typeof style !== "object") { + element.style = style; + } else if (old == null || typeof old !== "object") { + element.style = ""; + for (var key in style) { + var value = style[key]; + if (value != null) { + if (key.includes("-")) + element.style.setProperty(key, String(value)); + else + element.style[key] = String(value); + } + } + } else { + for (var key in old) { + if (old[key] != null && style[key] == null) { + if (key.includes("-")) + element.style.removeProperty(key); + else + element.style[key] = ""; + } + } + for (var key in style) { + var value = style[key]; + if (value != null && (value = String(value)) !== String(old[key])) { + if (key.includes("-")) + element.style.setProperty(key, value); + else + element.style[key] = value; + } + } + } + } + function EventDict() { + this._ = currentRedraw; + } + EventDict.prototype = Object.create(null); + EventDict.prototype.handleEvent = function(ev) { + var handler = this["on" + ev.type]; + var result; + if (typeof handler === "function") + result = handler.call(ev.currentTarget, ev); + else if (typeof handler.handleEvent === "function") + handler.handleEvent(ev); + var self = this; + if (self._ != null) { + if (ev.redraw !== false) + (0, self._)(); + if (result != null && typeof result.then === "function") { + Promise.resolve(result).then(function() { + if (self._ != null && ev.redraw !== false) + (0, self._)(); + }); + } + } + if (result === false) { + ev.preventDefault(); + ev.stopPropagation(); + } + }; + function updateEvent(vnode, key, value) { + if (vnode.events != null) { + vnode.events._ = currentRedraw; + if (vnode.events[key] === value) + return; + if (value != null && (typeof value === "function" || typeof value === "object")) { + if (vnode.events[key] == null) + vnode.dom.addEventListener(key.slice(2), vnode.events, false); + vnode.events[key] = value; + } else { + if (vnode.events[key] != null) + vnode.dom.removeEventListener(key.slice(2), vnode.events, false); + vnode.events[key] = undefined; + } + } else if (value != null && (typeof value === "function" || typeof value === "object")) { + vnode.events = new EventDict; + vnode.dom.addEventListener(key.slice(2), vnode.events, false); + vnode.events[key] = value; + } + } + function initLifecycle(source, vnode, hooks) { + if (typeof source.oninit === "function") + callHook.call(source.oninit, vnode); + if (typeof source.oncreate === "function") + hooks.push(callHook.bind(source.oncreate, vnode)); + } + function updateLifecycle(source, vnode, hooks) { + if (typeof source.onupdate === "function") + hooks.push(callHook.bind(source.onupdate, vnode)); + } + function shouldNotUpdate(vnode, old) { + do { + if (vnode.attrs != null && typeof vnode.attrs.onbeforeupdate === "function") { + var force = callHook.call(vnode.attrs.onbeforeupdate, vnode, old); + if (force !== undefined && !force) + break; + } + if (typeof vnode.tag !== "string" && typeof vnode.state.onbeforeupdate === "function") { + var force = callHook.call(vnode.state.onbeforeupdate, vnode, old); + if (force !== undefined && !force) + break; + } + return false; + } while (false); + vnode.dom = old.dom; + vnode.domSize = old.domSize; + vnode.instance = old.instance; + vnode.attrs = old.attrs; + vnode.children = old.children; + vnode.text = old.text; + return true; + } + var currentDOM; + return function(dom, vnodes, redraw) { + if (!dom) + throw new TypeError("DOM element being rendered to does not exist."); + if (currentDOM != null && dom.contains(currentDOM)) { + throw new TypeError("Node is currently being rendered to and thus is locked."); + } + var prevRedraw = currentRedraw; + var prevDOM = currentDOM; + var hooks = []; + var active = activeElement(dom); + var namespace = dom.namespaceURI; + currentDOM = dom; + currentRedraw = typeof redraw === "function" ? redraw : undefined; + currentRender = {}; + try { + if (dom.vnodes == null) + dom.textContent = ""; + vnodes = Vnode.normalizeChildren(Array.isArray(vnodes) ? vnodes : [vnodes]); + updateNodes(dom, dom.vnodes, vnodes, hooks, null, namespace === "http://www.w3.org/1999/xhtml" ? undefined : namespace); + dom.vnodes = vnodes; + if (active != null && activeElement(dom) !== active && typeof active.focus === "function") + active.focus(); + for (var i = 0;i < hooks.length; i++) + hooks[i](); + } finally { + currentRedraw = prevRedraw; + currentDOM = prevDOM; + } + }; + }; +}); + +// node_modules/mithril/render.js +var require_render2 = __commonJS((exports, module) => { + module.exports = require_render()(); +}); + +// node_modules/mithril/api/mount-redraw.js +var require_mount_redraw = __commonJS((exports, module) => { + var Vnode = require_vnode(); + module.exports = function(render, schedule, console2) { + var subscriptions = []; + var pending = false; + var offset = -1; + function sync() { + for (offset = 0;offset < subscriptions.length; offset += 2) { + try { + render(subscriptions[offset], Vnode(subscriptions[offset + 1]), redraw); + } catch (e) { + console2.error(e); + } + } + offset = -1; + } + function redraw() { + if (!pending) { + pending = true; + schedule(function() { + pending = false; + sync(); + }); + } + } + redraw.sync = sync; + function mount(root, component) { + if (component != null && component.view == null && typeof component !== "function") { + throw new TypeError("m.mount expects a component, not a vnode."); + } + var index = subscriptions.indexOf(root); + if (index >= 0) { + subscriptions.splice(index, 2); + if (index <= offset) + offset -= 2; + render(root, []); + } + if (component != null) { + subscriptions.push(root, component); + render(root, Vnode(component), redraw); + } + } + return { mount, redraw }; + }; +}); + +// node_modules/mithril/mount-redraw.js +var require_mount_redraw2 = __commonJS((exports, module) => { + var render = require_render2(); + module.exports = require_mount_redraw()(render, typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : null, typeof console !== "undefined" ? console : null); +}); + +// node_modules/mithril/querystring/build.js +var require_build = __commonJS((exports, module) => { + module.exports = function(object) { + if (Object.prototype.toString.call(object) !== "[object Object]") + return ""; + var args = []; + for (var key in object) { + destructure(key, object[key]); + } + return args.join("&"); + function destructure(key2, value) { + if (Array.isArray(value)) { + for (var i = 0;i < value.length; i++) { + destructure(key2 + "[" + i + "]", value[i]); + } + } else if (Object.prototype.toString.call(value) === "[object Object]") { + for (var i in value) { + destructure(key2 + "[" + i + "]", value[i]); + } + } else + args.push(encodeURIComponent(key2) + (value != null && value !== "" ? "=" + encodeURIComponent(value) : "")); + } + }; +}); + +// node_modules/mithril/pathname/build.js +var require_build2 = __commonJS((exports, module) => { + var buildQueryString = require_build(); + module.exports = function(template, params) { + if (/:([^\/\.-]+)(\.{3})?:/.test(template)) { + throw new SyntaxError("Template parameter names must be separated by either a '/', '-', or '.'."); + } + if (params == null) + return template; + var queryIndex = template.indexOf("?"); + var hashIndex = template.indexOf("#"); + var queryEnd = hashIndex < 0 ? template.length : hashIndex; + var pathEnd = queryIndex < 0 ? queryEnd : queryIndex; + var path = template.slice(0, pathEnd); + var query = {}; + Object.assign(query, params); + var resolved = path.replace(/:([^\/\.-]+)(\.{3})?/g, function(m, key, variadic) { + delete query[key]; + if (params[key] == null) + return m; + return variadic ? params[key] : encodeURIComponent(String(params[key])); + }); + var newQueryIndex = resolved.indexOf("?"); + var newHashIndex = resolved.indexOf("#"); + var newQueryEnd = newHashIndex < 0 ? resolved.length : newHashIndex; + var newPathEnd = newQueryIndex < 0 ? newQueryEnd : newQueryIndex; + var result = resolved.slice(0, newPathEnd); + if (queryIndex >= 0) + result += template.slice(queryIndex, queryEnd); + if (newQueryIndex >= 0) + result += (queryIndex < 0 ? "?" : "&") + resolved.slice(newQueryIndex, newQueryEnd); + var querystring = buildQueryString(query); + if (querystring) + result += (queryIndex < 0 && newQueryIndex < 0 ? "?" : "&") + querystring; + if (hashIndex >= 0) + result += template.slice(hashIndex); + if (newHashIndex >= 0) + result += (hashIndex < 0 ? "" : "&") + resolved.slice(newHashIndex); + return result; + }; +}); + +// node_modules/mithril/request/request.js +var require_request = __commonJS((exports, module) => { + var buildPathname = require_build2(); + var hasOwn = require_hasOwn(); + module.exports = function($window, oncompletion) { + function PromiseProxy(executor) { + return new Promise(executor); + } + function makeRequest(url, args) { + return new Promise(function(resolve, reject) { + url = buildPathname(url, args.params); + var method = args.method != null ? args.method.toUpperCase() : "GET"; + var body = args.body; + var assumeJSON = (args.serialize == null || args.serialize === JSON.serialize) && !(body instanceof $window.FormData || body instanceof $window.URLSearchParams); + var responseType = args.responseType || (typeof args.extract === "function" ? "" : "json"); + var xhr = new $window.XMLHttpRequest, aborted = false, isTimeout = false; + var original = xhr, replacedAbort; + var abort = xhr.abort; + xhr.abort = function() { + aborted = true; + abort.call(this); + }; + xhr.open(method, url, args.async !== false, typeof args.user === "string" ? args.user : undefined, typeof args.password === "string" ? args.password : undefined); + if (assumeJSON && body != null && !hasHeader(args, "content-type")) { + xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8"); + } + if (typeof args.deserialize !== "function" && !hasHeader(args, "accept")) { + xhr.setRequestHeader("Accept", "application/json, text/*"); + } + if (args.withCredentials) + xhr.withCredentials = args.withCredentials; + if (args.timeout) + xhr.timeout = args.timeout; + xhr.responseType = responseType; + for (var key in args.headers) { + if (hasOwn.call(args.headers, key)) { + xhr.setRequestHeader(key, args.headers[key]); + } + } + xhr.onreadystatechange = function(ev) { + if (aborted) + return; + if (ev.target.readyState === 4) { + try { + var success = ev.target.status >= 200 && ev.target.status < 300 || ev.target.status === 304 || /^file:\/\//i.test(url); + var response = ev.target.response, message; + if (responseType === "json") { + if (!ev.target.responseType && typeof args.extract !== "function") { + try { + response = JSON.parse(ev.target.responseText); + } catch (e) { + response = null; + } + } + } else if (!responseType || responseType === "text") { + if (response == null) + response = ev.target.responseText; + } + if (typeof args.extract === "function") { + response = args.extract(ev.target, args); + success = true; + } else if (typeof args.deserialize === "function") { + response = args.deserialize(response); + } + if (success) { + if (typeof args.type === "function") { + if (Array.isArray(response)) { + for (var i = 0;i < response.length; i++) { + response[i] = new args.type(response[i]); + } + } else + response = new args.type(response); + } + resolve(response); + } else { + var completeErrorResponse = function() { + try { + message = ev.target.responseText; + } catch (e) { + message = response; + } + var error = new Error(message); + error.code = ev.target.status; + error.response = response; + reject(error); + }; + if (xhr.status === 0) { + setTimeout(function() { + if (isTimeout) + return; + completeErrorResponse(); + }); + } else + completeErrorResponse(); + } + } catch (e) { + reject(e); + } + } + }; + xhr.ontimeout = function(ev) { + isTimeout = true; + var error = new Error("Request timed out"); + error.code = ev.target.status; + reject(error); + }; + if (typeof args.config === "function") { + xhr = args.config(xhr, args, url) || xhr; + if (xhr !== original) { + replacedAbort = xhr.abort; + xhr.abort = function() { + aborted = true; + replacedAbort.call(this); + }; + } + } + if (body == null) + xhr.send(); + else if (typeof args.serialize === "function") + xhr.send(args.serialize(body)); + else if (body instanceof $window.FormData || body instanceof $window.URLSearchParams) + xhr.send(body); + else + xhr.send(JSON.stringify(body)); + }); + } + PromiseProxy.prototype = Promise.prototype; + PromiseProxy.__proto__ = Promise; + function hasHeader(args, name) { + for (var key in args.headers) { + if (hasOwn.call(args.headers, key) && key.toLowerCase() === name) + return true; + } + return false; + } + return { + request: function(url, args) { + if (typeof url !== "string") { + args = url; + url = url.url; + } else if (args == null) + args = {}; + var promise = makeRequest(url, args); + if (args.background === true) + return promise; + var count = 0; + function complete() { + if (--count === 0 && typeof oncompletion === "function") + oncompletion(); + } + return wrap(promise); + function wrap(promise2) { + var then = promise2.then; + promise2.constructor = PromiseProxy; + promise2.then = function() { + count++; + var next = then.apply(promise2, arguments); + next.then(complete, function(e) { + complete(); + if (count === 0) + throw e; + }); + return wrap(next); + }; + return promise2; + } + } + }; + }; +}); + +// node_modules/mithril/request.js +var require_request2 = __commonJS((exports, module) => { + var mountRedraw = require_mount_redraw2(); + module.exports = require_request()(typeof window !== "undefined" ? window : null, mountRedraw.redraw); +}); + +// node_modules/mithril/util/decodeURIComponentSafe.js +var require_decodeURIComponentSafe = __commonJS((exports, module) => { + var validUtf8Encodings = /%(?:[0-7]|(?!c[01]|e0%[89]|ed%[ab]|f0%8|f4%[9ab])(?:c|d|(?:e|f[0-4]%[89ab])[\da-f]%[89ab])[\da-f]%[89ab])[\da-f]/gi; + module.exports = function(str) { + return String(str).replace(validUtf8Encodings, decodeURIComponent); + }; +}); + +// node_modules/mithril/querystring/parse.js +var require_parse = __commonJS((exports, module) => { + var decodeURIComponentSafe = require_decodeURIComponentSafe(); + module.exports = function(string) { + if (string === "" || string == null) + return {}; + if (string.charAt(0) === "?") + string = string.slice(1); + var entries = string.split("&"), counters = {}, data = {}; + for (var i = 0;i < entries.length; i++) { + var entry = entries[i].split("="); + var key = decodeURIComponentSafe(entry[0]); + var value = entry.length === 2 ? decodeURIComponentSafe(entry[1]) : ""; + if (value === "true") + value = true; + else if (value === "false") + value = false; + var levels = key.split(/\]\[?|\[/); + var cursor = data; + if (key.indexOf("[") > -1) + levels.pop(); + for (var j = 0;j < levels.length; j++) { + var level = levels[j], nextLevel = levels[j + 1]; + var isNumber = nextLevel == "" || !isNaN(parseInt(nextLevel, 10)); + if (level === "") { + var key = levels.slice(0, j).join(); + if (counters[key] == null) { + counters[key] = Array.isArray(cursor) ? cursor.length : 0; + } + level = counters[key]++; + } else if (level === "__proto__") + break; + if (j === levels.length - 1) + cursor[level] = value; + else { + var desc = Object.getOwnPropertyDescriptor(cursor, level); + if (desc != null) + desc = desc.value; + if (desc == null) + cursor[level] = desc = isNumber ? [] : {}; + cursor = desc; + } + } + } + return data; + }; +}); + +// node_modules/mithril/pathname/parse.js +var require_parse2 = __commonJS((exports, module) => { + var parseQueryString = require_parse(); + module.exports = function(url) { + var queryIndex = url.indexOf("?"); + var hashIndex = url.indexOf("#"); + var queryEnd = hashIndex < 0 ? url.length : hashIndex; + var pathEnd = queryIndex < 0 ? queryEnd : queryIndex; + var path = url.slice(0, pathEnd).replace(/\/{2,}/g, "/"); + if (!path) + path = "/"; + else { + if (path[0] !== "/") + path = "/" + path; + } + return { + path, + params: queryIndex < 0 ? {} : parseQueryString(url.slice(queryIndex + 1, queryEnd)) + }; + }; +}); + +// node_modules/mithril/pathname/compileTemplate.js +var require_compileTemplate = __commonJS((exports, module) => { + var parsePathname = require_parse2(); + module.exports = function(template) { + var templateData = parsePathname(template); + var templateKeys = Object.keys(templateData.params); + var keys = []; + var regexp = new RegExp("^" + templateData.path.replace(/:([^\/.-]+)(\.{3}|\.(?!\.)|-)?|[\\^$*+.()|\[\]{}]/g, function(m, key, extra) { + if (key == null) + return "\\" + m; + keys.push({ k: key, r: extra === "..." }); + if (extra === "...") + return "(.*)"; + if (extra === ".") + return "([^/]+)\\."; + return "([^/]+)" + (extra || ""); + }) + "\\/?$"); + return function(data) { + for (var i = 0;i < templateKeys.length; i++) { + if (templateData.params[templateKeys[i]] !== data.params[templateKeys[i]]) + return false; + } + if (!keys.length) + return regexp.test(data.path); + var values = regexp.exec(data.path); + if (values == null) + return false; + for (var i = 0;i < keys.length; i++) { + data.params[keys[i].k] = keys[i].r ? values[i + 1] : decodeURIComponent(values[i + 1]); + } + return true; + }; + }; +}); + +// node_modules/mithril/util/censor.js +var require_censor = __commonJS((exports, module) => { + var hasOwn = require_hasOwn(); + var magic = /^(?:key|oninit|oncreate|onbeforeupdate|onupdate|onbeforeremove|onremove)$/; + module.exports = function(attrs, extras) { + var result = {}; + if (extras != null) { + for (var key in attrs) { + if (hasOwn.call(attrs, key) && !magic.test(key) && extras.indexOf(key) < 0) { + result[key] = attrs[key]; + } + } + } else { + for (var key in attrs) { + if (hasOwn.call(attrs, key) && !magic.test(key)) { + result[key] = attrs[key]; + } + } + } + return result; + }; +}); + +// node_modules/mithril/api/router.js +var require_router = __commonJS((exports, module) => { + var Vnode = require_vnode(); + var hyperscript = require_hyperscript(); + var decodeURIComponentSafe = require_decodeURIComponentSafe(); + var buildPathname = require_build2(); + var parsePathname = require_parse2(); + var compileTemplate = require_compileTemplate(); + var censor = require_censor(); + module.exports = function($window, mountRedraw) { + var p = Promise.resolve(); + var scheduled = false; + var ready = false; + var hasBeenResolved = false; + var dom, compiled, fallbackRoute; + var currentResolver, component, attrs, currentPath, lastUpdate; + var RouterRoot = { + onremove: function() { + ready = hasBeenResolved = false; + $window.removeEventListener("popstate", fireAsync, false); + }, + view: function() { + var vnode = Vnode(component, attrs.key, attrs); + if (currentResolver) + return currentResolver.render(vnode); + return [vnode]; + } + }; + var SKIP = route.SKIP = {}; + function resolveRoute() { + scheduled = false; + var prefix = $window.location.hash; + if (route.prefix[0] !== "#") { + prefix = $window.location.search + prefix; + if (route.prefix[0] !== "?") { + prefix = $window.location.pathname + prefix; + if (prefix[0] !== "/") + prefix = "/" + prefix; + } + } + var path = decodeURIComponentSafe(prefix).slice(route.prefix.length); + var data = parsePathname(path); + Object.assign(data.params, $window.history.state); + function reject(e) { + console.error(e); + route.set(fallbackRoute, null, { replace: true }); + } + loop(0); + function loop(i) { + for (;i < compiled.length; i++) { + if (compiled[i].check(data)) { + var payload = compiled[i].component; + var matchedRoute = compiled[i].route; + var localComp = payload; + var update = lastUpdate = function(comp) { + if (update !== lastUpdate) + return; + if (comp === SKIP) + return loop(i + 1); + component = comp != null && (typeof comp.view === "function" || typeof comp === "function") ? comp : "div"; + attrs = data.params, currentPath = path, lastUpdate = null; + currentResolver = payload.render ? payload : null; + if (hasBeenResolved) + mountRedraw.redraw(); + else { + hasBeenResolved = true; + mountRedraw.mount(dom, RouterRoot); + } + }; + if (payload.view || typeof payload === "function") { + payload = {}; + update(localComp); + } else if (payload.onmatch) { + p.then(function() { + return payload.onmatch(data.params, path, matchedRoute); + }).then(update, path === fallbackRoute ? null : reject); + } else + update(); + return; + } + } + if (path === fallbackRoute) { + throw new Error("Could not resolve default route " + fallbackRoute + "."); + } + route.set(fallbackRoute, null, { replace: true }); + } + } + function fireAsync() { + if (!scheduled) { + scheduled = true; + setTimeout(resolveRoute); + } + } + function route(root, defaultRoute, routes) { + if (!root) + throw new TypeError("DOM element being rendered to does not exist."); + compiled = Object.keys(routes).map(function(route2) { + if (route2[0] !== "/") + throw new SyntaxError("Routes must start with a '/'."); + if (/:([^\/\.-]+)(\.{3})?:/.test(route2)) { + throw new SyntaxError("Route parameter names must be separated with either '/', '.', or '-'."); + } + return { + route: route2, + component: routes[route2], + check: compileTemplate(route2) + }; + }); + fallbackRoute = defaultRoute; + if (defaultRoute != null) { + var defaultData = parsePathname(defaultRoute); + if (!compiled.some(function(i) { + return i.check(defaultData); + })) { + throw new ReferenceError("Default route doesn't match any known routes."); + } + } + dom = root; + $window.addEventListener("popstate", fireAsync, false); + ready = true; + resolveRoute(); + } + route.set = function(path, data, options) { + if (lastUpdate != null) { + options = options || {}; + options.replace = true; + } + lastUpdate = null; + path = buildPathname(path, data); + if (ready) { + fireAsync(); + var state = options ? options.state : null; + var title = options ? options.title : null; + if (options && options.replace) + $window.history.replaceState(state, title, route.prefix + path); + else + $window.history.pushState(state, title, route.prefix + path); + } else { + $window.location.href = route.prefix + path; + } + }; + route.get = function() { + return currentPath; + }; + route.prefix = "#!"; + route.Link = { + view: function(vnode) { + var child = hyperscript(vnode.attrs.selector || "a", censor(vnode.attrs, ["options", "params", "selector", "onclick"]), vnode.children); + var options, onclick, href; + if (child.attrs.disabled = Boolean(child.attrs.disabled)) { + child.attrs.href = null; + child.attrs["aria-disabled"] = "true"; + } else { + options = vnode.attrs.options; + onclick = vnode.attrs.onclick; + href = buildPathname(child.attrs.href, vnode.attrs.params); + child.attrs.href = route.prefix + href; + child.attrs.onclick = function(e) { + var result; + if (typeof onclick === "function") { + result = onclick.call(e.currentTarget, e); + } else if (onclick == null || typeof onclick !== "object") {} else if (typeof onclick.handleEvent === "function") { + onclick.handleEvent(e); + } + if (result !== false && !e.defaultPrevented && (e.button === 0 || e.which === 0 || e.which === 1) && (!e.currentTarget.target || e.currentTarget.target === "_self") && !e.ctrlKey && !e.metaKey && !e.shiftKey && !e.altKey) { + e.preventDefault(); + e.redraw = false; + route.set(href, null, options); + } + }; + } + return child; + } + }; + route.param = function(key) { + return attrs && key != null ? attrs[key] : attrs; + }; + return route; + }; +}); + +// node_modules/mithril/route.js +var require_route = __commonJS((exports, module) => { + var mountRedraw = require_mount_redraw2(); + module.exports = require_router()(typeof window !== "undefined" ? window : null, mountRedraw); +}); + +// node_modules/mithril/index.js +var require_mithril = __commonJS((exports, module) => { + var hyperscript = require_hyperscript2(); + var mountRedraw = require_mount_redraw2(); + var request = require_request2(); + var router = require_route(); + var m = function m2() { + return hyperscript.apply(this, arguments); + }; + m.m = hyperscript; + m.trust = hyperscript.trust; + m.fragment = hyperscript.fragment; + m.Fragment = "["; + m.mount = mountRedraw.mount; + m.route = router; + m.render = require_render2(); + m.redraw = mountRedraw.redraw; + m.request = request.request; + m.parseQueryString = require_parse(); + m.buildQueryString = require_build(); + m.parsePathname = require_parse2(); + m.buildPathname = require_build2(); + m.vnode = require_vnode(); + m.censor = require_censor(); + m.domFor = require_domFor(); + module.exports = m; +}); + +// src/browser.ts +var import_mithril = __toESM(require_mithril(), 1); + +// src/index.ts +var exports_src = {}; +__export(exports_src, { + unpackPixels: () => unpackPixels, + pad32: () => pad32, + packedSize: () => packedSize, + packPixels: () => packPixels, + loadBinFile: () => loadBinFile, + isCustomFont: () => isCustomFont, + customFontOrdinal: () => customFontOrdinal, + buildBinBuffer: () => buildBinBuffer, + StringToSectionType: () => StringToSectionType, + StringToElementType: () => StringToElementType, + SectionTypeToString: () => SectionTypeToString, + SectionType: () => SectionType, + MonoDisplayRenderer: () => MonoDisplayRenderer, + MonoDisplayParser: () => MonoDisplayParser, + MonoDisplayFile: () => MonoDisplayFile, + MonoDisplayDriver: () => MonoDisplayDriver, + MONOFORMAT_MAGIC_HEADER: () => MONOFORMAT_MAGIC_HEADER, + FileVersion: () => FileVersion, + ElementTypeToString: () => ElementTypeToString, + ElementType: () => ElementType, + CUSTOM_FONT_BASE: () => CUSTOM_FONT_BASE, + BinaryReader: () => BinaryReader +}); + +// src/types.ts +var FileVersion; +((FileVersion2) => { + FileVersion2[FileVersion2["Illegal"] = 0] = "Illegal"; + FileVersion2[FileVersion2["V1"] = 1] = "V1"; +})(FileVersion ||= {}); +var SectionType; +((SectionType2) => { + SectionType2[SectionType2["ElementsAlways"] = 1] = "ElementsAlways"; + SectionType2[SectionType2["ElementsTimespan"] = 2] = "ElementsTimespan"; + SectionType2[SectionType2["CustomFont"] = 32] = "CustomFont"; +})(SectionType ||= {}); +var SectionTypeToString = Object.fromEntries(Object.keys(SectionType).filter((k) => isNaN(Number(k))).map((k) => [SectionType[k], k])); +var StringToSectionType = Object.fromEntries(Object.keys(SectionType).filter((k) => isNaN(Number(k))).map((k) => [k, SectionType[k]])); +var ElementType; +((ElementType2) => { + ElementType2[ElementType2["Image2D"] = 1] = "Image2D"; + ElementType2[ElementType2["Animation"] = 2] = "Animation"; + ElementType2[ElementType2["HorizontalScroll"] = 3] = "HorizontalScroll"; + ElementType2[ElementType2["VerticalScroll"] = 4] = "VerticalScroll"; + ElementType2[ElementType2["Line"] = 5] = "Line"; + ElementType2[ElementType2["ClippedText"] = 16] = "ClippedText"; + ElementType2[ElementType2["HScrollText"] = 17] = "HScrollText"; + ElementType2[ElementType2["VScrollText"] = 18] = "VScrollText"; + ElementType2[ElementType2["CurrentTime"] = 32] = "CurrentTime"; +})(ElementType ||= {}); +var ElementTypeToString = Object.fromEntries(Object.keys(ElementType).filter((k) => isNaN(Number(k))).map((k) => [ElementType[k], k])); +var StringToElementType = Object.fromEntries(Object.keys(ElementType).filter((k) => isNaN(Number(k))).map((k) => [k, ElementType[k]])); +var CUSTOM_FONT_BASE = 32768; +function isCustomFont(fontIndex) { + return fontIndex >= CUSTOM_FONT_BASE; +} +function customFontOrdinal(fontIndex) { + return fontIndex - CUSTOM_FONT_BASE; +} +// src/helper.ts +function packPixels(pixels, width, height) { + const total = width * height; + const packed = new Uint8Array(total + 7 >> 3); + for (let i = 0;i < total; i++) { + if (pixels[i]) + packed[i >> 3] = (packed[i >> 3] ?? 0) | 1 << (i & 7); + } + return packed; +} +function unpackPixels(packed, width, height) { + const total = width * height; + const pixels = new Uint8Array(total); + for (let i = 0;i < total; i++) { + pixels[i] = (packed[i >> 3] ?? 0) >> (i & 7) & 1; + } + return pixels; +} +function packedSize(width, height) { + return width * height + 7 >> 3; +} +function pad32(byteCount) { + return 4 - (byteCount & 3) & 3; +} + +class BinaryReader { + view; + pos = 0; + constructor(buffer) { + this.view = buffer instanceof ArrayBuffer ? new DataView(buffer) : new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength); + } + get offset() { + return this.pos; + } + get byteLength() { + return this.view.byteLength; + } + get remaining() { + return this.view.byteLength - this.pos; + } + readByte() { + this.#need(1); + return this.view.getUint8(this.pos++); + } + readUint16() { + this.#need(2); + const v = this.view.getUint16(this.pos, true); + this.pos += 2; + return v; + } + readShort() { + return this.readUint16(); + } + readInt16() { + this.#need(2); + const v = this.view.getInt16(this.pos, true); + this.pos += 2; + return v; + } + readUint24() { + this.#need(3); + const v = this.view.getUint8(this.pos) | this.view.getUint8(this.pos + 1) << 8 | this.view.getUint8(this.pos + 2) << 16; + this.pos += 3; + return v; + } + readUint32() { + this.#need(4); + const v = this.view.getUint32(this.pos, true); + this.pos += 4; + return v; + } + readUint64() { + this.#need(8); + const lo = BigInt(this.view.getUint32(this.pos, true)); + const hi = BigInt(this.view.getUint32(this.pos + 4, true)); + this.pos += 8; + return hi << 32n | lo; + } + readBytes(n) { + this.#need(n); + const s = new Uint8Array(this.view.buffer, this.pos, n); + this.pos += n; + return s; + } + readUtf8(n) { + return new TextDecoder().decode(this.readBytes(n)); + } + seek(pos) { + if (pos < 0 || pos > this.view.byteLength) { + throw new RangeError(`seek(${pos}) out of [0, ${this.view.byteLength}]`); + } + this.pos = pos; + } + #need(n) { + if (this.remaining < n) + throw new RangeError(`BinaryReader: need ${n} byte(s) at offset ${this.pos}, ${this.remaining} remain`); + } +} +// src/parser.ts +var MONOFORMAT_MAGIC_HEADER = 1663794863; + +class MonoDisplayParser { + parse(buffer) { + const r = new BinaryReader(buffer); + const magic = r.readUint32(); + if (magic !== MONOFORMAT_MAGIC_HEADER) + throw new Error(`Bad magic 0x${magic.toString(16)} - expected 0x632B7EAF`); + const version = r.readUint32(); + if (version === 0 || version > 1) + throw new Error(`Unsupported version ${version}`); + const numSections = r.readUint16(); + r.readUint16(); + const sections = []; + for (let i = 0;i < numSections; i++) { + const sectionType = r.readByte(); + const sectionSize = r.readUint24(); + const sectionStart = r.offset; + const dataSize = sectionSize - 4; + const sectionEnd = sectionStart + dataSize; + switch (sectionType) { + case 1 /* ElementsAlways */: + sections.push(this.#parseElementsSection(r, 1 /* ElementsAlways */, false, sectionEnd)); + break; + case 2 /* ElementsTimespan */: + sections.push(this.#parseElementsSection(r, 2 /* ElementsTimespan */, true, sectionEnd)); + break; + case 32 /* CustomFont */: + sections.push(this.#parseCustomFont(r, dataSize)); + break; + default: + break; + } + r.seek(sectionStart + dataSize); + } + return { sections }; + } + #parseFlags(raw) { + return { + drawFront: !!(raw & 1), + drawBack: !!(raw & 2), + clearBuffer: !!(raw & 4) + }; + } + #parseElementsSection(r, type, hasTimestamp, sectionEnd) { + const flags = this.#parseFlags(r.readUint16()); + const numElements = r.readUint16(); + let startTimestamp = 0n, endTimestamp = 0n; + if (hasTimestamp) { + startTimestamp = r.readUint64(); + endTimestamp = r.readUint64(); + } + const elements = []; + for (let i = 0;i < numElements; i++) { + if (r.offset + 2 > sectionEnd) + break; + const el = this.#parseElement(r); + if (el === null) + break; + elements.push(el); + } + if (hasTimestamp) { + return { sectionType: 2 /* ElementsTimespan */, flags, startTimestamp, endTimestamp, elements }; + } + return { sectionType: 1 /* ElementsAlways */, flags, elements }; + } + #parseCustomFont(r, dataSize) { + const actualSize = r.readUint24(); + r.readByte(); + const fontData = new Uint8Array(r.readBytes(Math.min(actualSize, dataSize - 4))); + return { sectionType: 32 /* CustomFont */, fontData }; + } + #parseElement(r) { + const elementStart = r.offset; + const type = r.readUint16(); + switch (type) { + case 1 /* Image2D */: + return this.#parseImage2D(r, elementStart); + case 2 /* Animation */: + return this.#parseAnimation(r, elementStart); + case 3 /* HorizontalScroll */: + return this.#parseHScroll(r, elementStart); + case 4 /* VerticalScroll */: + return this.#parseVScroll(r, elementStart); + case 5 /* Line */: + return this.#parseLine(r); + case 16 /* ClippedText */: + return this.#parseClippedText(r, elementStart); + case 17 /* HScrollText */: + return this.#parseHScrollText(r, elementStart); + case 32 /* CurrentTime */: + return this.#parseCurrentTime(r); + default: + return null; + } + } + #alignTo4(start, current) { + const end = start + (current - start + 3 & ~3); + } + #parseImage2D(r, start) { + const xOffset = r.readUint16(); + const yOffset = r.readUint16(); + const width = r.readUint16(); + const height = r.readUint16(); + r.readUint16(); + const packed = r.readBytes(packedSize(width, height)); + const pixels = unpackPixels(new Uint8Array(packed), width, height); + const dataRead = 12 + packed.byteLength; + r.seek(start + dataRead + pad32(dataRead)); + return { type: 1 /* Image2D */, xOffset, yOffset, image: { pixels, width, height } }; + } + #parseAnimation(r, start) { + const xOffset = r.readUint16(); + const yOffset = r.readUint16(); + const width = r.readUint16(); + const height = r.readUint16(); + const numFrames = r.readUint16(); + const updateInterval = r.readUint16(); + r.readUint16(); + const frameBytes = packedSize(width, height); + const frames = []; + for (let f = 0;f < numFrames; f++) { + const packed = r.readBytes(frameBytes); + frames.push({ pixels: unpackPixels(new Uint8Array(packed), width, height), width, height }); + } + const dataRead = 16 + numFrames * frameBytes; + r.seek(start + dataRead + pad32(dataRead)); + return { type: 2 /* Animation */, xOffset, yOffset, width, height, updateInterval, frames }; + } + #parseScrollFlags(raw) { + return { + endless: !!(raw & 1), + invertDirection: !!(raw & 2), + padStart: !!(raw & 4), + padEnd: !!(raw & 8) + }; + } + #parseHScroll(r, start) { + const xOffset = r.readUint16(); + const yOffset = r.readUint16(); + const width = r.readUint16(); + const height = r.readUint16(); + const contentWidth = r.readUint16(); + const flagsByte = r.readByte(); + const scrollSpeed = r.readByte(); + r.readUint16(); + const packed = r.readBytes(packedSize(contentWidth, height)); + const pixels = unpackPixels(new Uint8Array(packed), contentWidth, height); + const dataRead = 16 + packed.byteLength; + r.seek(start + dataRead + pad32(dataRead)); + return { + type: 3 /* HorizontalScroll */, + xOffset, + yOffset, + width, + height, + contentWidth, + scrollSpeed, + flags: this.#parseScrollFlags(flagsByte), + content: { pixels, width: contentWidth, height } + }; + } + #parseVScroll(r, start) { + const xOffset = r.readUint16(); + const yOffset = r.readUint16(); + const width = r.readUint16(); + const height = r.readUint16(); + const contentHeight = r.readUint16(); + const flagsByte = r.readByte(); + const scrollSpeed = r.readByte(); + r.readUint16(); + const packed = r.readBytes(packedSize(width, contentHeight)); + const pixels = unpackPixels(new Uint8Array(packed), width, contentHeight); + const dataRead = 16 + packed.byteLength; + r.seek(start + dataRead + pad32(dataRead)); + return { + type: 4 /* VerticalScroll */, + xOffset, + yOffset, + width, + height, + contentHeight, + scrollSpeed, + flags: this.#parseScrollFlags(flagsByte), + content: { pixels, width, height: contentHeight } + }; + } + #parseLine(r) { + const xOrigin = r.readUint16(); + const yOrigin = r.readUint16(); + const xTarget = r.readUint16(); + const yTarget = r.readUint16(); + const lineStyle = r.readByte(); + const flagsByte = r.readByte(); + return { type: 5 /* Line */, xOrigin, yOrigin, xTarget, yTarget, lineStyle, invertPixels: !!(flagsByte & 1) }; + } + #parseClippedText(r, start) { + const xOffset = r.readUint16(); + const yOffset = r.readUint16(); + const width = r.readUint16(); + const height = r.readUint16(); + const fontIndex = r.readUint16(); + const textLen = r.readUint16(); + const text = r.readUtf8(textLen); + const dataRead = 14 + textLen; + r.seek(start + dataRead + pad32(dataRead)); + return { type: 16 /* ClippedText */, xOffset, yOffset, width, height, fontIndex, text }; + } + #parseCurrentTime(r) { + const xOffset = r.readUint16(); + const yOffset = r.readUint16(); + const width = r.readUint16(); + const height = r.readUint16(); + const fontIndex = r.readUint16(); + const utcOffsetMinutes = r.readInt16(); + const flagsByte = r.readByte(); + r.readByte(); + return { + type: 32 /* CurrentTime */, + xOffset, + yOffset, + width, + height, + fontIndex, + utcOffsetMinutes, + flags: { + clock12h: !!(flagsByte & 1), + showHours: !!(flagsByte & 2), + showMinutes: !!(flagsByte & 4), + showSeconds: !!(flagsByte & 8) + } + }; + } + #parseHScrollText(r, start) { + const xOffset = r.readUint16(); + const yOffset = r.readUint16(); + const width = r.readUint16(); + const height = r.readUint16(); + const flagsByte = r.readByte(); + const scrollSpeed = r.readByte(); + const fontIndex = r.readUint16(); + const textLen = r.readUint16(); + const text = r.readUtf8(textLen); + const dataRead = 16 + textLen; + r.seek(start + dataRead + pad32(dataRead)); + return { + type: 17 /* HScrollText */, + xOffset, + yOffset, + width, + height, + scrollSpeed, + fontIndex, + flags: this.#parseScrollFlags(flagsByte), + text + }; + } +} +// src/font/fonts/u8g2_font_5x7_mf_u8g2font.ts +var u8g2_font_5x7_mf_u8g2font = [191, 2, 3, 2, 3, 3, 1, 1, 4, 5, 7, 0, 255, 6, 255, 6, 0, 1, 42, 2, 114, 7, 90, 32, 5, 125, 125, 30, 33, 8, 125, 173, 176, 29, 202, 1, 34, 9, 125, 157, 82, 18, 37, 57, 3, 35, 11, 125, 237, 36, 25, 148, 202, 160, 148, 1, 36, 9, 125, 237, 165, 182, 37, 201, 12, 37, 9, 125, 141, 48, 202, 154, 194, 24, 38, 10, 125, 237, 44, 201, 42, 89, 18, 3, 39, 7, 125, 173, 176, 206, 2, 40, 8, 125, 173, 44, 108, 205, 1, 41, 9, 125, 157, 52, 108, 203, 17, 0, 42, 9, 125, 237, 36, 203, 182, 74, 12, 43, 9, 125, 253, 48, 26, 164, 48, 7, 44, 7, 125, 125, 22, 173, 6, 45, 8, 125, 125, 202, 144, 147, 0, 46, 7, 125, 125, 186, 166, 3, 47, 7, 125, 125, 36, 107, 39, 48, 12, 125, 173, 44, 137, 146, 40, 137, 146, 44, 7, 49, 8, 125, 173, 76, 108, 155, 1, 50, 9, 125, 29, 169, 152, 213, 134, 24, 51, 11, 125, 141, 33, 140, 212, 36, 138, 116, 0, 52, 11, 125, 173, 76, 74, 162, 33, 11, 115, 0, 53, 11, 125, 141, 33, 9, 215, 36, 138, 116, 0, 54, 10, 125, 29, 41, 156, 42, 81, 164, 3, 55, 11, 125, 141, 33, 204, 194, 44, 204, 17, 0, 56, 10, 125, 29, 169, 36, 85, 162, 72, 7, 57, 10, 125, 29, 169, 18, 69, 99, 164, 3, 58, 8, 125, 109, 77, 71, 52, 29, 59, 9, 125, 109, 77, 71, 180, 34, 0, 60, 8, 125, 125, 36, 171, 150, 1, 61, 9, 125, 125, 108, 136, 135, 28, 4, 62, 8, 125, 237, 180, 86, 71, 0, 63, 10, 125, 173, 44, 9, 179, 28, 202, 1, 64, 11, 125, 29, 169, 146, 40, 137, 146, 234, 0, 65, 12, 125, 29, 169, 18, 37, 67, 18, 37, 81, 12, 66, 10, 125, 141, 169, 50, 85, 162, 100, 7, 67, 9, 125, 29, 169, 18, 150, 34, 29, 68, 12, 125, 141, 169, 18, 37, 81, 18, 37, 59, 0, 69, 11, 125, 141, 33, 9, 167, 48, 28, 98, 0, 70, 10, 125, 141, 33, 9, 167, 176, 14, 1, 71, 11, 125, 29, 169, 18, 38, 74, 20, 205, 0, 72, 14, 125, 141, 40, 137, 146, 33, 137, 146, 40, 137, 98, 0, 73, 8, 125, 157, 45, 108, 155, 1, 74, 9, 125, 189, 176, 37, 138, 116, 0, 75, 11, 125, 141, 40, 41, 105, 90, 18, 149, 1, 76, 8, 125, 141, 176, 227, 16, 3, 77, 14, 125, 141, 40, 25, 146, 33, 137, 146, 40, 137, 98, 0, 78, 13, 125, 141, 40, 81, 18, 165, 162, 36, 74, 20, 3, 79, 12, 125, 29, 169, 18, 37, 81, 18, 69, 58, 0, 80, 11, 125, 141, 169, 18, 37, 83, 152, 67, 0, 81, 12, 125, 29, 169, 18, 37, 81, 162, 68, 106, 2, 82, 11, 125, 141, 169, 18, 37, 83, 18, 149, 1, 83, 9, 125, 29, 169, 53, 42, 233, 0, 84, 7, 125, 157, 45, 236, 14, 85, 13, 125, 141, 40, 137, 146, 40, 137, 146, 40, 210, 1, 86, 12, 125, 141, 40, 137, 146, 40, 137, 34, 77, 7, 87, 14, 125, 141, 40, 137, 146, 40, 25, 146, 33, 137, 98, 0, 88, 12, 125, 141, 40, 137, 34, 77, 170, 68, 49, 0, 89, 10, 125, 157, 82, 18, 37, 89, 88, 7, 90, 9, 125, 141, 33, 204, 26, 135, 24, 91, 8, 125, 157, 41, 108, 156, 1, 92, 7, 125, 221, 238, 32, 0, 93, 7, 125, 157, 177, 211, 12, 94, 8, 125, 173, 44, 201, 89, 1, 95, 7, 125, 125, 198, 33, 6, 96, 7, 125, 157, 52, 103, 6, 97, 11, 125, 125, 112, 137, 146, 68, 74, 98, 0, 98, 10, 125, 141, 48, 156, 42, 81, 178, 3, 99, 8, 125, 125, 80, 10, 83, 29, 100, 11, 125, 189, 48, 90, 162, 36, 138, 102, 0, 101, 9, 125, 125, 80, 74, 140, 58, 0, 102, 11, 125, 173, 44, 137, 178, 45, 204, 17, 0, 103, 10, 125, 125, 112, 137, 34, 41, 93, 0, 104, 11, 125, 141, 48, 156, 42, 81, 18, 197, 0, 105, 9, 125, 173, 28, 17, 107, 51, 0, 106, 10, 125, 189, 28, 10, 75, 73, 22, 1, 107, 10, 125, 141, 176, 18, 105, 73, 84, 6, 108, 7, 125, 29, 177, 219, 12, 109, 11, 125, 125, 44, 137, 134, 36, 74, 162, 24, 110, 10, 125, 125, 108, 170, 68, 73, 20, 3, 111, 9, 125, 125, 80, 170, 68, 145, 14, 112, 10, 125, 125, 108, 170, 68, 201, 20, 2, 113, 11, 125, 125, 112, 137, 146, 40, 26, 19, 0, 114, 9, 125, 125, 108, 170, 132, 57, 4, 115, 8, 125, 125, 112, 81, 149, 29, 116, 9, 125, 157, 48, 219, 194, 84, 6, 117, 11, 125, 125, 44, 74, 162, 36, 138, 102, 0, 118, 11, 125, 125, 48, 137, 146, 40, 201, 114, 0, 119, 11, 125, 125, 44, 74, 162, 100, 72, 134, 24, 120, 9, 125, 125, 44, 138, 52, 169, 12, 121, 10, 125, 125, 44, 74, 162, 74, 86, 3, 122, 9, 125, 125, 108, 200, 106, 67, 12, 123, 8, 125, 189, 154, 88, 141, 1, 124, 7, 125, 173, 176, 119, 0, 125, 10, 125, 157, 52, 212, 194, 44, 71, 0, 126, 7, 125, 157, 238, 204, 0, 160, 5, 125, 125, 30, 161, 8, 125, 173, 28, 10, 219, 1, 162, 10, 125, 253, 108, 41, 37, 217, 22, 1, 163, 9, 125, 125, 41, 219, 42, 50, 0, 164, 11, 125, 221, 44, 153, 146, 104, 201, 82, 0, 165, 10, 125, 157, 82, 146, 101, 91, 152, 3, 166, 8, 125, 253, 48, 135, 194, 28, 167, 11, 125, 45, 41, 212, 146, 76, 140, 36, 0, 168, 7, 125, 157, 58, 55, 0, 169, 12, 125, 157, 37, 83, 146, 73, 73, 180, 100, 1, 170, 9, 125, 29, 41, 201, 116, 22, 0, 171, 9, 125, 125, 48, 146, 218, 49, 0, 172, 9, 125, 125, 202, 16, 230, 32, 0, 173, 7, 125, 125, 210, 78, 2, 174, 11, 125, 157, 37, 27, 146, 105, 210, 146, 5, 175, 7, 125, 141, 33, 231, 6, 176, 9, 125, 173, 44, 201, 114, 22, 0, 177, 11, 125, 173, 48, 26, 164, 48, 26, 84, 0, 178, 9, 125, 29, 49, 11, 117, 26, 0, 179, 9, 125, 29, 77, 204, 116, 26, 0, 180, 7, 125, 173, 44, 231, 0, 181, 11, 125, 125, 44, 74, 162, 36, 74, 166, 16, 182, 13, 125, 157, 69, 73, 148, 40, 137, 146, 40, 137, 1, 183, 8, 125, 125, 80, 211, 105, 0, 184, 7, 125, 125, 230, 44, 3, 185, 9, 125, 173, 76, 204, 118, 18, 0, 186, 9, 125, 157, 44, 201, 114, 38, 0, 187, 9, 125, 125, 44, 42, 73, 57, 8, 188, 9, 125, 141, 176, 41, 147, 198, 4, 189, 9, 125, 141, 176, 69, 204, 66, 5, 190, 11, 125, 13, 77, 204, 148, 76, 26, 19, 0, 191, 10, 125, 173, 28, 202, 194, 36, 203, 1, 192, 12, 125, 29, 169, 18, 37, 67, 18, 37, 81, 12, 193, 12, 125, 29, 169, 18, 37, 67, 18, 37, 81, 12, 194, 12, 125, 29, 169, 18, 37, 67, 18, 37, 81, 12, 195, 12, 125, 29, 169, 18, 37, 67, 18, 37, 81, 12, 196, 12, 125, 141, 40, 146, 42, 67, 18, 37, 81, 12, 197, 12, 125, 29, 77, 170, 12, 73, 148, 68, 49, 0, 198, 12, 125, 157, 165, 148, 40, 83, 18, 37, 50, 0, 199, 10, 125, 29, 169, 18, 150, 34, 45, 3, 200, 11, 125, 141, 33, 9, 167, 48, 28, 98, 0, 201, 11, 125, 141, 33, 9, 167, 48, 28, 98, 0, 202, 11, 125, 141, 33, 9, 167, 48, 28, 98, 0, 203, 11, 125, 141, 33, 9, 167, 48, 28, 98, 0, 204, 8, 125, 157, 45, 108, 155, 1, 205, 8, 125, 157, 45, 108, 155, 1, 206, 8, 125, 157, 45, 108, 155, 1, 207, 8, 125, 157, 45, 108, 155, 1, 208, 12, 125, 141, 45, 73, 148, 40, 137, 146, 100, 7, 209, 13, 125, 141, 68, 137, 18, 165, 162, 36, 74, 20, 3, 210, 12, 125, 29, 169, 18, 37, 81, 18, 69, 58, 0, 211, 12, 125, 29, 169, 18, 37, 81, 18, 69, 58, 0, 212, 12, 125, 29, 169, 18, 37, 81, 18, 69, 58, 0, 213, 12, 125, 29, 169, 18, 37, 81, 18, 69, 58, 0, 214, 12, 125, 141, 40, 146, 42, 81, 18, 69, 58, 0, 215, 9, 125, 125, 44, 138, 52, 169, 12, 216, 12, 125, 157, 37, 81, 18, 75, 162, 36, 59, 0, 217, 13, 125, 141, 40, 137, 146, 40, 137, 146, 40, 210, 1, 218, 13, 125, 141, 40, 137, 146, 40, 137, 146, 40, 210, 1, 219, 13, 125, 141, 40, 137, 146, 40, 137, 146, 40, 210, 1, 220, 11, 125, 141, 40, 142, 146, 40, 137, 34, 29, 221, 10, 125, 157, 82, 18, 37, 89, 88, 7, 222, 10, 125, 141, 112, 170, 76, 97, 14, 1, 223, 10, 125, 29, 169, 82, 170, 68, 73, 29, 224, 11, 125, 157, 52, 91, 162, 36, 145, 146, 24, 225, 11, 125, 173, 44, 92, 162, 36, 145, 146, 24, 226, 12, 125, 173, 44, 137, 150, 40, 73, 164, 36, 6, 227, 11, 125, 157, 110, 75, 148, 36, 82, 18, 3, 228, 11, 125, 157, 250, 18, 37, 137, 148, 196, 0, 229, 11, 125, 29, 77, 91, 162, 36, 145, 146, 24, 230, 9, 125, 125, 112, 73, 148, 218, 12, 231, 9, 125, 125, 84, 10, 83, 45, 2, 232, 9, 125, 157, 52, 147, 18, 163, 14, 233, 9, 125, 173, 44, 148, 18, 163, 14, 234, 10, 125, 157, 44, 201, 164, 196, 168, 3, 235, 10, 125, 141, 36, 71, 164, 196, 168, 3, 236, 9, 125, 157, 52, 19, 107, 51, 0, 237, 9, 125, 173, 44, 20, 107, 51, 0, 238, 9, 125, 173, 44, 137, 196, 218, 12, 239, 8, 125, 157, 186, 88, 155, 1, 240, 10, 125, 157, 84, 146, 42, 81, 164, 3, 241, 10, 125, 157, 78, 83, 37, 74, 162, 24, 242, 10, 125, 157, 52, 147, 42, 81, 164, 3, 243, 10, 125, 173, 44, 148, 42, 81, 164, 3, 244, 10, 125, 29, 29, 145, 42, 81, 164, 3, 245, 10, 125, 157, 110, 82, 37, 138, 116, 0, 246, 9, 125, 157, 186, 84, 137, 34, 29, 247, 8, 125, 109, 125, 200, 117, 0, 248, 9, 125, 125, 112, 73, 44, 201, 14, 249, 11, 125, 157, 52, 170, 68, 73, 20, 205, 0, 250, 11, 125, 173, 172, 148, 68, 73, 20, 205, 0, 251, 11, 125, 29, 61, 74, 162, 36, 138, 102, 0, 252, 11, 125, 157, 114, 148, 68, 73, 20, 205, 0, 253, 10, 125, 173, 172, 148, 68, 149, 172, 6, 254, 10, 125, 221, 112, 170, 68, 201, 20, 2, 255, 10, 125, 157, 114, 148, 68, 149, 172, 6, 0, 0, 0, 4, 255, 255, 0, 0]; + +// src/font/fonts/u8g2_font_5x7_tf.u8g2font.ts +var u8g2_font_5x7_tf_u8g2font = [ + 191, + 0, + 2, + 2, + 3, + 3, + 3, + 4, + 4, + 5, + 7, + 0, + 255, + 6, + 255, + 6, + 0, + 1, + 10, + 2, + 22, + 6, + 47, + 32, + 5, + 0, + 189, + 1, + 33, + 6, + 177, + 177, + 25, + 41, + 34, + 7, + 91, + 183, + 73, + 86, + 0, + 35, + 10, + 45, + 177, + 171, + 134, + 170, + 134, + 170, + 0, + 36, + 9, + 45, + 177, + 91, + 245, + 78, + 105, + 1, + 37, + 8, + 52, + 177, + 201, + 177, + 119, + 0, + 38, + 9, + 44, + 177, + 139, + 41, + 86, + 49, + 5, + 39, + 5, + 153, + 183, + 25, + 40, + 7, + 114, + 177, + 83, + 205, + 0, + 41, + 8, + 114, + 177, + 137, + 169, + 82, + 0, + 42, + 7, + 107, + 177, + 73, + 213, + 106, + 43, + 10, + 45, + 177, + 205, + 40, + 14, + 153, + 81, + 4, + 44, + 7, + 91, + 175, + 83, + 37, + 0, + 45, + 6, + 12, + 181, + 25, + 1, + 46, + 6, + 82, + 177, + 25, + 1, + 47, + 7, + 36, + 179, + 143, + 109, + 0, + 48, + 8, + 115, + 177, + 171, + 92, + 21, + 0, + 49, + 7, + 115, + 177, + 75, + 178, + 53, + 50, + 9, + 52, + 177, + 83, + 49, + 199, + 114, + 4, + 51, + 10, + 52, + 177, + 25, + 57, + 105, + 36, + 147, + 2, + 52, + 10, + 52, + 177, + 141, + 170, + 26, + 49, + 39, + 0, + 53, + 10, + 52, + 177, + 25, + 122, + 35, + 153, + 20, + 0, + 54, + 10, + 52, + 177, + 83, + 121, + 69, + 153, + 20, + 0, + 55, + 10, + 52, + 177, + 25, + 57, + 230, + 152, + 35, + 0, + 56, + 10, + 52, + 177, + 83, + 49, + 169, + 40, + 147, + 2, + 57, + 10, + 52, + 177, + 83, + 81, + 166, + 157, + 20, + 0, + 58, + 7, + 106, + 177, + 25, + 113, + 4, + 59, + 8, + 51, + 175, + 179, + 145, + 42, + 1, + 60, + 7, + 107, + 177, + 77, + 117, + 1, + 61, + 8, + 28, + 179, + 25, + 25, + 141, + 0, + 62, + 7, + 107, + 177, + 201, + 85, + 9, + 63, + 9, + 115, + 177, + 107, + 166, + 12, + 19, + 0, + 64, + 9, + 52, + 177, + 83, + 81, + 221, + 72, + 1, + 65, + 9, + 52, + 177, + 83, + 81, + 142, + 41, + 3, + 66, + 10, + 52, + 177, + 89, + 113, + 164, + 40, + 71, + 2, + 67, + 9, + 52, + 177, + 83, + 81, + 151, + 73, + 1, + 68, + 9, + 52, + 177, + 89, + 209, + 57, + 18, + 0, + 69, + 9, + 52, + 177, + 25, + 122, + 229, + 60, + 2, + 70, + 8, + 52, + 177, + 25, + 122, + 229, + 26, + 71, + 9, + 52, + 177, + 83, + 81, + 167, + 153, + 6, + 72, + 8, + 52, + 177, + 137, + 114, + 76, + 51, + 73, + 7, + 115, + 177, + 89, + 177, + 53, + 74, + 8, + 52, + 177, + 111, + 203, + 164, + 0, + 75, + 10, + 52, + 177, + 137, + 42, + 73, + 153, + 202, + 0, + 76, + 7, + 52, + 177, + 201, + 221, + 35, + 77, + 9, + 52, + 177, + 137, + 227, + 136, + 102, + 0, + 78, + 8, + 52, + 177, + 137, + 107, + 169, + 51, + 79, + 9, + 52, + 177, + 83, + 209, + 153, + 20, + 0, + 80, + 10, + 52, + 177, + 89, + 81, + 142, + 148, + 51, + 0, + 81, + 10, + 60, + 175, + 83, + 209, + 92, + 73, + 163, + 0, + 82, + 9, + 52, + 177, + 89, + 81, + 142, + 212, + 12, + 83, + 10, + 52, + 177, + 83, + 49, + 101, + 84, + 38, + 5, + 84, + 7, + 115, + 177, + 89, + 177, + 11, + 85, + 8, + 52, + 177, + 137, + 158, + 73, + 1, + 86, + 9, + 52, + 177, + 137, + 206, + 36, + 21, + 0, + 87, + 9, + 52, + 177, + 137, + 230, + 56, + 98, + 0, + 88, + 10, + 52, + 177, + 137, + 50, + 73, + 21, + 101, + 0, + 89, + 8, + 115, + 177, + 73, + 86, + 89, + 1, + 90, + 9, + 52, + 177, + 25, + 57, + 182, + 71, + 0, + 91, + 7, + 115, + 177, + 25, + 177, + 57, + 92, + 9, + 36, + 179, + 201, + 40, + 163, + 140, + 2, + 93, + 7, + 115, + 177, + 153, + 205, + 17, + 94, + 5, + 83, + 185, + 107, + 95, + 6, + 12, + 177, + 25, + 1, + 96, + 6, + 82, + 185, + 137, + 1, + 97, + 8, + 36, + 177, + 27, + 81, + 169, + 2, + 98, + 10, + 52, + 177, + 201, + 121, + 69, + 57, + 18, + 0, + 99, + 6, + 35, + 177, + 155, + 89, + 100, + 8, + 52, + 177, + 175, + 70, + 148, + 105, + 101, + 8, + 36, + 177, + 83, + 105, + 100, + 5, + 102, + 9, + 52, + 177, + 173, + 202, + 153, + 35, + 0, + 103, + 9, + 44, + 175, + 27, + 49, + 169, + 140, + 6, + 104, + 8, + 52, + 177, + 201, + 121, + 69, + 51, + 105, + 8, + 115, + 177, + 203, + 72, + 86, + 3, + 106, + 9, + 123, + 175, + 205, + 176, + 84, + 21, + 0, + 107, + 9, + 52, + 177, + 201, + 85, + 146, + 169, + 12, + 108, + 7, + 115, + 177, + 145, + 93, + 3, + 109, + 8, + 36, + 177, + 73, + 105, + 72, + 25, + 110, + 7, + 36, + 177, + 89, + 209, + 12, + 111, + 8, + 36, + 177, + 83, + 81, + 38, + 5, + 112, + 9, + 44, + 175, + 89, + 81, + 142, + 148, + 1, + 113, + 8, + 44, + 175, + 27, + 81, + 166, + 29, + 114, + 8, + 36, + 177, + 89, + 81, + 103, + 0, + 115, + 8, + 36, + 177, + 27, + 26, + 13, + 5, + 116, + 9, + 52, + 177, + 203, + 113, + 230, + 140, + 4, + 117, + 7, + 36, + 177, + 137, + 102, + 26, + 118, + 7, + 99, + 177, + 73, + 86, + 5, + 119, + 7, + 36, + 177, + 137, + 114, + 28, + 120, + 8, + 36, + 177, + 137, + 73, + 170, + 24, + 121, + 9, + 44, + 175, + 137, + 50, + 149, + 37, + 0, + 122, + 8, + 36, + 177, + 25, + 177, + 28, + 1, + 123, + 8, + 115, + 177, + 77, + 73, + 214, + 1, + 124, + 5, + 177, + 177, + 57, + 125, + 9, + 115, + 177, + 201, + 81, + 197, + 20, + 1, + 126, + 7, + 20, + 185, + 75, + 42, + 1, + 160, + 5, + 0, + 189, + 1, + 161, + 6, + 177, + 177, + 73, + 35, + 162, + 9, + 52, + 175, + 141, + 35, + 53, + 103, + 2, + 163, + 8, + 44, + 177, + 85, + 113, + 86, + 2, + 164, + 10, + 45, + 177, + 201, + 105, + 166, + 184, + 114, + 0, + 165, + 9, + 115, + 177, + 73, + 170, + 90, + 49, + 1, + 166, + 6, + 169, + 177, + 81, + 2, + 167, + 8, + 123, + 175, + 155, + 170, + 146, + 11, + 168, + 6, + 75, + 187, + 73, + 1, + 169, + 11, + 61, + 175, + 91, + 89, + 165, + 169, + 146, + 78, + 11, + 170, + 6, + 27, + 183, + 91, + 73, + 171, + 7, + 29, + 179, + 139, + 178, + 1, + 172, + 6, + 20, + 179, + 25, + 57, + 173, + 5, + 75, + 181, + 25, + 174, + 11, + 61, + 175, + 91, + 121, + 164, + 57, + 117, + 90, + 0, + 175, + 6, + 12, + 187, + 25, + 1, + 176, + 6, + 91, + 183, + 235, + 2, + 177, + 11, + 53, + 177, + 205, + 40, + 14, + 153, + 81, + 28, + 2, + 178, + 6, + 98, + 181, + 81, + 6, + 179, + 6, + 98, + 181, + 25, + 105, + 180, + 6, + 82, + 185, + 83, + 0, + 181, + 8, + 44, + 175, + 137, + 230, + 72, + 25, + 182, + 8, + 52, + 177, + 27, + 106, + 245, + 3, + 183, + 6, + 82, + 181, + 25, + 1, + 184, + 6, + 82, + 175, + 83, + 0, + 185, + 7, + 99, + 181, + 75, + 50, + 13, + 186, + 6, + 27, + 183, + 235, + 2, + 187, + 8, + 29, + 179, + 137, + 165, + 76, + 0, + 188, + 9, + 60, + 175, + 201, + 205, + 168, + 118, + 0, + 189, + 9, + 60, + 175, + 201, + 45, + 29, + 179, + 0, + 190, + 10, + 60, + 175, + 145, + 58, + 170, + 168, + 118, + 0, + 191, + 9, + 115, + 177, + 203, + 48, + 197, + 84, + 1, + 192, + 9, + 52, + 177, + 83, + 81, + 142, + 41, + 3, + 193, + 9, + 52, + 177, + 83, + 81, + 142, + 41, + 3, + 194, + 9, + 52, + 177, + 83, + 81, + 142, + 41, + 3, + 195, + 9, + 52, + 177, + 83, + 81, + 142, + 41, + 3, + 196, + 10, + 52, + 177, + 137, + 73, + 197, + 49, + 101, + 0, + 197, + 9, + 52, + 177, + 147, + 42, + 142, + 41, + 3, + 198, + 9, + 52, + 177, + 27, + 169, + 26, + 170, + 37, + 199, + 10, + 60, + 175, + 83, + 81, + 151, + 73, + 70, + 0, + 200, + 9, + 52, + 177, + 25, + 122, + 229, + 60, + 2, + 201, + 9, + 52, + 177, + 25, + 122, + 229, + 60, + 2, + 202, + 9, + 52, + 177, + 25, + 122, + 229, + 60, + 2, + 203, + 9, + 52, + 177, + 25, + 122, + 229, + 60, + 2, + 204, + 7, + 115, + 177, + 89, + 177, + 53, + 205, + 7, + 115, + 177, + 89, + 177, + 53, + 206, + 7, + 115, + 177, + 89, + 177, + 53, + 207, + 7, + 115, + 177, + 89, + 177, + 53, + 208, + 9, + 52, + 177, + 153, + 105, + 117, + 141, + 4, + 209, + 8, + 52, + 177, + 73, + 115, + 169, + 51, + 210, + 9, + 52, + 177, + 83, + 209, + 153, + 20, + 0, + 211, + 9, + 52, + 177, + 83, + 209, + 153, + 20, + 0, + 212, + 9, + 52, + 177, + 83, + 209, + 153, + 20, + 0, + 213, + 9, + 52, + 177, + 83, + 209, + 153, + 20, + 0, + 214, + 10, + 52, + 177, + 137, + 73, + 69, + 51, + 41, + 0, + 215, + 8, + 36, + 177, + 137, + 73, + 170, + 24, + 216, + 9, + 52, + 177, + 27, + 233, + 72, + 71, + 2, + 217, + 8, + 52, + 177, + 137, + 158, + 73, + 1, + 218, + 8, + 52, + 177, + 137, + 158, + 73, + 1, + 219, + 8, + 52, + 177, + 137, + 158, + 73, + 1, + 220, + 10, + 52, + 177, + 137, + 25, + 69, + 51, + 41, + 0, + 221, + 8, + 115, + 177, + 73, + 86, + 89, + 1, + 222, + 10, + 52, + 177, + 201, + 43, + 142, + 148, + 51, + 0, + 223, + 9, + 52, + 177, + 83, + 81, + 149, + 86, + 2, + 224, + 10, + 52, + 177, + 203, + 40, + 142, + 168, + 84, + 1, + 225, + 9, + 52, + 177, + 173, + 71, + 84, + 170, + 0, + 226, + 9, + 52, + 177, + 173, + 210, + 136, + 74, + 21, + 227, + 10, + 52, + 177, + 75, + 42, + 142, + 168, + 84, + 1, + 228, + 9, + 52, + 177, + 171, + 225, + 136, + 74, + 21, + 229, + 9, + 52, + 177, + 147, + 114, + 68, + 165, + 10, + 230, + 8, + 36, + 177, + 27, + 105, + 197, + 1, + 231, + 8, + 107, + 175, + 155, + 89, + 37, + 0, + 232, + 10, + 52, + 177, + 203, + 40, + 170, + 52, + 178, + 2, + 233, + 9, + 52, + 177, + 173, + 85, + 26, + 89, + 1, + 234, + 10, + 52, + 177, + 139, + 41, + 170, + 52, + 178, + 2, + 235, + 10, + 52, + 177, + 73, + 25, + 171, + 52, + 178, + 2, + 236, + 8, + 115, + 177, + 201, + 73, + 86, + 3, + 237, + 7, + 115, + 177, + 43, + 101, + 53, + 238, + 7, + 115, + 177, + 171, + 102, + 53, + 239, + 8, + 115, + 177, + 73, + 89, + 86, + 3, + 240, + 10, + 52, + 177, + 203, + 200, + 138, + 50, + 41, + 0, + 241, + 9, + 52, + 177, + 75, + 42, + 173, + 104, + 6, + 242, + 10, + 52, + 177, + 203, + 40, + 170, + 40, + 147, + 2, + 243, + 9, + 52, + 177, + 173, + 85, + 148, + 73, + 1, + 244, + 10, + 52, + 177, + 211, + 88, + 69, + 153, + 20, + 0, + 245, + 10, + 52, + 177, + 75, + 42, + 170, + 40, + 147, + 2, + 246, + 10, + 52, + 177, + 171, + 161, + 138, + 50, + 41, + 0, + 247, + 9, + 44, + 177, + 211, + 112, + 100, + 168, + 0, + 248, + 9, + 36, + 177, + 27, + 105, + 164, + 145, + 0, + 249, + 9, + 52, + 177, + 203, + 168, + 52, + 211, + 0, + 250, + 7, + 52, + 177, + 109, + 154, + 105, + 251, + 8, + 52, + 177, + 211, + 48, + 154, + 105, + 252, + 9, + 52, + 177, + 171, + 81, + 52, + 211, + 0, + 253, + 9, + 60, + 175, + 109, + 202, + 84, + 150, + 0, + 254, + 10, + 52, + 175, + 201, + 43, + 202, + 145, + 50, + 0, + 255, + 10, + 60, + 175, + 171, + 81, + 148, + 169, + 44, + 1, + 0, + 0, + 0, + 4, + 255, + 255, + 0, + 0 +]; + +// src/font/fonts/u8g2_font_HelvetiPixel_tr_u8g2font.ts +var u8g2_font_HelvetiPixel_tr_u8g2font = [95, 0, 3, 2, 4, 4, 2, 4, 5, 9, 12, 0, 253, 8, 254, 8, 254, 1, 83, 2, 187, 3, 231, 32, 5, 0, 98, 5, 33, 6, 129, 227, 196, 37, 34, 7, 51, 54, 69, 98, 9, 35, 16, 134, 226, 85, 146, 37, 201, 176, 212, 146, 104, 88, 106, 73, 4, 36, 14, 149, 162, 85, 182, 84, 148, 108, 75, 148, 202, 22, 1, 37, 18, 136, 163, 78, 22, 37, 81, 165, 24, 197, 81, 152, 148, 42, 81, 150, 0, 38, 15, 135, 34, 150, 24, 101, 81, 40, 38, 145, 166, 100, 210, 20, 39, 6, 49, 182, 196, 0, 40, 11, 163, 26, 85, 18, 37, 81, 91, 148, 5, 41, 12, 163, 26, 69, 22, 101, 81, 75, 148, 68, 0, 42, 8, 53, 178, 69, 82, 89, 26, 43, 10, 85, 166, 85, 24, 13, 82, 24, 1, 44, 7, 50, 218, 76, 162, 0, 45, 6, 19, 42, 197, 0, 46, 6, 17, 227, 68, 0, 47, 10, 147, 30, 85, 75, 84, 137, 74, 0, 48, 10, 133, 162, 205, 146, 249, 150, 44, 0, 49, 8, 131, 99, 85, 178, 68, 61, 50, 10, 133, 162, 205, 146, 133, 89, 199, 65, 51, 12, 133, 162, 205, 146, 133, 145, 26, 106, 201, 2, 52, 14, 134, 226, 101, 168, 37, 81, 37, 75, 134, 49, 77, 0, 53, 12, 133, 162, 197, 49, 28, 210, 80, 75, 22, 0, 54, 12, 133, 162, 205, 146, 137, 67, 146, 217, 146, 5, 55, 12, 133, 162, 197, 32, 102, 97, 22, 102, 97, 6, 56, 12, 133, 162, 205, 146, 105, 201, 146, 217, 146, 5, 57, 12, 133, 162, 205, 146, 217, 146, 33, 172, 44, 0, 58, 6, 81, 227, 68, 22, 59, 8, 114, 218, 76, 158, 40, 0, 60, 7, 86, 230, 165, 100, 54, 61, 8, 53, 170, 197, 160, 14, 2, 62, 8, 86, 230, 133, 108, 50, 2, 63, 11, 133, 162, 205, 146, 105, 141, 57, 20, 1, 64, 20, 136, 159, 214, 144, 133, 73, 180, 40, 81, 162, 68, 137, 52, 68, 105, 52, 40, 0, 65, 14, 135, 34, 94, 156, 38, 97, 146, 149, 6, 37, 85, 3, 66, 13, 133, 227, 197, 144, 100, 218, 160, 100, 182, 65, 1, 67, 13, 135, 34, 214, 16, 133, 114, 123, 24, 13, 9, 0, 68, 13, 134, 35, 198, 16, 101, 73, 232, 45, 25, 34, 0, 69, 11, 133, 227, 197, 49, 28, 146, 176, 56, 8, 70, 10, 133, 227, 197, 49, 28, 146, 176, 17, 71, 15, 135, 34, 214, 16, 133, 114, 109, 72, 147, 48, 26, 18, 0, 72, 10, 134, 35, 70, 104, 28, 6, 209, 49, 73, 6, 129, 227, 196, 65, 74, 9, 132, 98, 93, 111, 82, 162, 0, 75, 15, 134, 35, 70, 150, 68, 149, 76, 76, 178, 168, 150, 132, 1, 76, 8, 133, 227, 69, 216, 199, 65, 77, 16, 135, 99, 70, 186, 13, 217, 82, 81, 42, 82, 36, 69, 106, 0, 78, 12, 133, 227, 69, 54, 77, 74, 162, 36, 210, 45, 79, 12, 135, 34, 214, 86, 73, 93, 147, 44, 155, 0, 80, 12, 133, 227, 197, 144, 100, 182, 65, 9, 139, 0, 81, 12, 135, 34, 214, 86, 73, 93, 147, 72, 27, 4, 82, 13, 134, 35, 198, 160, 132, 198, 97, 137, 106, 73, 24, 83, 12, 133, 227, 205, 146, 169, 106, 170, 37, 11, 0, 84, 9, 133, 162, 197, 32, 133, 125, 2, 85, 10, 134, 35, 70, 232, 199, 100, 72, 0, 86, 15, 135, 34, 70, 170, 38, 89, 148, 85, 194, 36, 141, 51, 0, 87, 17, 137, 162, 70, 150, 105, 153, 86, 233, 148, 52, 37, 109, 197, 44, 2, 88, 14, 133, 227, 69, 150, 148, 146, 44, 204, 146, 40, 169, 5, 89, 12, 133, 162, 69, 166, 37, 165, 36, 11, 155, 0, 90, 9, 134, 226, 197, 53, 236, 117, 24, 91, 8, 162, 27, 197, 210, 47, 2, 92, 10, 147, 30, 69, 84, 139, 106, 81, 1, 93, 8, 162, 218, 132, 210, 47, 3, 94, 10, 85, 174, 85, 150, 68, 73, 77, 11, 95, 6, 22, 218, 197, 1, 96, 6, 34, 250, 68, 20, 97, 11, 85, 162, 205, 146, 37, 131, 150, 12, 1, 98, 12, 133, 227, 69, 88, 28, 146, 204, 54, 40, 0, 99, 10, 85, 162, 205, 146, 137, 89, 178, 0, 100, 10, 133, 162, 101, 203, 160, 217, 146, 33, 101, 10, 85, 162, 205, 146, 13, 67, 58, 4, 102, 10, 131, 34, 85, 18, 37, 75, 212, 2, 103, 12, 117, 154, 205, 160, 217, 146, 33, 76, 22, 0, 104, 10, 133, 227, 69, 88, 28, 146, 204, 45, 105, 7, 113, 227, 68, 50, 8, 106, 9, 146, 218, 76, 150, 116, 81, 0, 107, 11, 132, 163, 69, 214, 164, 36, 82, 82, 10, 108, 6, 129, 227, 196, 65, 109, 12, 87, 99, 198, 162, 68, 145, 20, 73, 145, 84, 110, 9, 85, 227, 197, 144, 100, 110, 1, 111, 9, 85, 162, 205, 146, 217, 146, 5, 112, 12, 117, 219, 197, 144, 100, 182, 65, 9, 67, 0, 113, 10, 117, 154, 205, 160, 217, 146, 33, 44, 114, 9, 84, 163, 69, 50, 68, 89, 13, 115, 9, 84, 98, 205, 16, 138, 67, 2, 116, 9, 115, 34, 77, 148, 44, 81, 45, 117, 9, 85, 227, 69, 230, 150, 12, 1, 118, 11, 85, 162, 69, 166, 37, 165, 36, 139, 0, 119, 13, 87, 34, 70, 20, 73, 145, 210, 45, 202, 18, 0, 120, 10, 85, 162, 69, 150, 212, 42, 181, 0, 121, 12, 117, 154, 69, 166, 37, 165, 36, 11, 35, 13, 122, 9, 85, 162, 197, 160, 181, 13, 2, 123, 11, 179, 22, 85, 18, 181, 100, 81, 91, 0, 124, 7, 161, 219, 196, 67, 0, 125, 11, 179, 22, 69, 22, 181, 37, 81, 75, 4, 126, 8, 36, 175, 77, 162, 36, 0, 0, 0, 0, 4, 255, 255, 0, 0]; + +// src/font/fonts/u8g2_font_micropixel_tf_u8g2font.ts +var u8g2_font_micropixel_tf_u8g2font = [187, 0, 2, 2, 3, 4, 2, 4, 5, 8, 12, 255, 253, 5, 254, 6, 255, 0, 245, 1, 245, 5, 253, 32, 5, 0, 177, 2, 33, 6, 41, 81, 178, 2, 34, 7, 19, 153, 146, 84, 0, 35, 10, 45, 209, 86, 13, 85, 13, 85, 1, 36, 10, 60, 175, 26, 71, 138, 122, 232, 4, 37, 6, 36, 177, 18, 123, 38, 10, 45, 209, 150, 83, 92, 50, 170, 0, 39, 5, 17, 91, 34, 40, 7, 58, 111, 166, 58, 3, 41, 8, 58, 111, 18, 83, 75, 1, 42, 6, 27, 153, 146, 58, 43, 7, 27, 147, 150, 86, 2, 44, 5, 17, 79, 34, 45, 5, 10, 117, 34, 46, 5, 9, 81, 18, 47, 7, 43, 145, 90, 149, 17, 48, 9, 44, 177, 166, 226, 152, 73, 1, 49, 6, 170, 145, 182, 26, 50, 8, 44, 177, 166, 98, 115, 4, 51, 9, 44, 177, 166, 98, 21, 147, 2, 52, 8, 44, 177, 18, 229, 208, 5, 53, 9, 44, 177, 50, 244, 70, 35, 1, 54, 9, 44, 177, 166, 242, 138, 73, 1, 55, 9, 44, 177, 50, 114, 172, 35, 0, 56, 10, 44, 177, 166, 98, 82, 49, 41, 0, 57, 9, 44, 177, 166, 98, 218, 73, 1, 58, 6, 41, 81, 146, 3, 59, 6, 49, 79, 146, 5, 60, 6, 26, 115, 38, 3, 61, 6, 27, 147, 178, 7, 62, 7, 26, 115, 18, 21, 0, 63, 9, 44, 177, 166, 98, 77, 37, 0, 64, 10, 45, 209, 182, 178, 26, 169, 90, 0, 65, 8, 44, 177, 166, 226, 152, 50, 66, 10, 44, 177, 178, 226, 72, 113, 36, 0, 67, 8, 44, 177, 54, 114, 141, 6, 68, 9, 44, 177, 178, 162, 57, 18, 0, 69, 8, 44, 177, 50, 244, 202, 35, 70, 8, 44, 177, 50, 244, 202, 25, 71, 8, 44, 177, 54, 114, 154, 105, 72, 8, 44, 177, 18, 229, 152, 50, 73, 7, 43, 145, 178, 98, 53, 74, 7, 43, 145, 38, 203, 5, 75, 9, 44, 177, 18, 85, 146, 169, 12, 76, 7, 43, 145, 18, 155, 3, 77, 9, 45, 209, 146, 215, 74, 90, 7, 78, 8, 44, 177, 18, 151, 154, 50, 79, 9, 44, 177, 166, 162, 153, 20, 0, 80, 9, 44, 177, 178, 226, 72, 57, 3, 81, 8, 44, 177, 166, 162, 165, 6, 82, 9, 44, 177, 178, 226, 72, 81, 6, 83, 10, 44, 177, 54, 50, 210, 104, 36, 0, 84, 7, 43, 145, 178, 98, 11, 85, 8, 44, 177, 18, 157, 73, 1, 86, 8, 44, 177, 18, 93, 49, 2, 87, 9, 45, 209, 146, 173, 164, 186, 0, 88, 9, 44, 177, 18, 101, 82, 81, 6, 89, 8, 43, 145, 146, 84, 89, 1, 90, 9, 44, 177, 50, 114, 82, 121, 4, 91, 7, 58, 111, 178, 186, 4, 92, 7, 43, 145, 18, 115, 25, 93, 7, 58, 111, 162, 186, 6, 94, 6, 19, 153, 214, 0, 95, 6, 12, 177, 50, 2, 96, 6, 18, 121, 18, 3, 97, 7, 27, 145, 182, 146, 0, 98, 8, 43, 145, 18, 163, 74, 11, 99, 6, 27, 145, 182, 5, 100, 7, 43, 145, 90, 173, 36, 101, 6, 26, 113, 178, 4, 102, 8, 43, 145, 154, 210, 138, 9, 103, 8, 43, 141, 182, 146, 92, 0, 104, 8, 43, 145, 18, 163, 74, 42, 105, 6, 41, 81, 146, 6, 106, 8, 186, 76, 150, 83, 165, 0, 107, 8, 35, 145, 146, 86, 82, 1, 108, 6, 41, 81, 50, 4, 109, 8, 29, 209, 50, 82, 165, 10, 110, 7, 27, 145, 162, 146, 10, 111, 7, 27, 145, 50, 210, 8, 112, 8, 43, 141, 162, 210, 138, 17, 113, 7, 43, 141, 182, 146, 44, 114, 7, 27, 145, 50, 146, 4, 115, 7, 27, 145, 166, 146, 2, 116, 7, 42, 113, 146, 86, 12, 117, 7, 27, 145, 146, 84, 18, 118, 7, 27, 145, 146, 84, 5, 119, 8, 29, 209, 146, 146, 234, 2, 120, 6, 27, 145, 146, 58, 121, 8, 43, 141, 146, 84, 146, 11, 122, 7, 27, 145, 178, 210, 0, 123, 9, 59, 143, 154, 98, 202, 49, 7, 124, 5, 49, 81, 114, 125, 10, 59, 143, 146, 99, 78, 49, 69, 0, 126, 7, 20, 181, 150, 84, 2, 160, 5, 0, 177, 2, 161, 6, 41, 81, 146, 6, 162, 9, 44, 175, 26, 71, 138, 51, 1, 163, 9, 44, 177, 170, 226, 72, 113, 4, 164, 10, 45, 209, 146, 211, 76, 113, 229, 0, 165, 9, 43, 145, 146, 84, 181, 18, 0, 166, 6, 49, 79, 34, 5, 167, 12, 60, 177, 54, 50, 82, 49, 105, 52, 18, 0, 168, 7, 21, 217, 162, 70, 18, 169, 10, 45, 209, 182, 178, 26, 57, 45, 0, 170, 7, 27, 153, 182, 146, 0, 171, 7, 29, 211, 22, 101, 3, 172, 6, 19, 147, 50, 3, 174, 9, 45, 209, 182, 226, 85, 45, 0, 175, 6, 12, 187, 50, 2, 176, 7, 27, 153, 50, 210, 8, 177, 8, 43, 145, 150, 86, 70, 3, 178, 7, 35, 155, 162, 215, 0, 179, 7, 35, 155, 34, 229, 2, 180, 6, 18, 121, 166, 0, 181, 8, 43, 141, 146, 212, 144, 17, 182, 11, 45, 209, 54, 86, 82, 49, 197, 148, 0, 183, 5, 9, 85, 18, 184, 7, 155, 106, 150, 23, 0, 185, 6, 162, 155, 162, 26, 186, 7, 27, 157, 50, 210, 8, 187, 8, 29, 211, 18, 75, 153, 0, 191, 9, 44, 173, 150, 169, 88, 41, 0, 192, 11, 68, 177, 150, 81, 198, 42, 142, 41, 3, 193, 10, 68, 177, 90, 83, 42, 142, 41, 3, 194, 11, 68, 177, 166, 98, 134, 42, 142, 41, 3, 195, 11, 68, 177, 150, 84, 198, 42, 142, 41, 3, 196, 10, 60, 177, 18, 51, 84, 113, 76, 25, 197, 10, 60, 177, 166, 98, 82, 113, 76, 25, 198, 12, 47, 17, 55, 70, 204, 99, 197, 28, 71, 0, 199, 11, 68, 171, 54, 114, 141, 102, 70, 49, 1, 200, 11, 68, 177, 150, 81, 198, 42, 142, 41, 3, 201, 10, 68, 177, 90, 227, 161, 87, 30, 1, 202, 11, 68, 177, 22, 83, 134, 67, 175, 60, 2, 203, 10, 60, 177, 146, 50, 28, 122, 229, 17, 204, 9, 67, 145, 146, 51, 90, 177, 26, 205, 9, 67, 145, 154, 50, 90, 177, 26, 206, 8, 67, 145, 214, 94, 177, 26, 207, 9, 59, 145, 146, 242, 138, 213, 0, 208, 10, 173, 176, 54, 227, 72, 101, 90, 0, 209, 11, 68, 177, 150, 84, 134, 113, 169, 41, 3, 210, 11, 68, 177, 150, 81, 198, 42, 154, 73, 1, 211, 10, 68, 177, 90, 83, 42, 154, 73, 1, 212, 11, 68, 177, 166, 98, 134, 42, 154, 73, 1, 213, 11, 68, 177, 150, 84, 198, 42, 154, 73, 1, 214, 10, 60, 177, 18, 51, 84, 209, 76, 10, 215, 6, 27, 147, 146, 58, 216, 10, 174, 208, 170, 99, 26, 43, 102, 9, 217, 11, 68, 177, 150, 81, 134, 209, 153, 20, 0, 218, 9, 68, 177, 90, 227, 232, 76, 10, 219, 11, 68, 177, 166, 98, 70, 209, 153, 20, 0, 220, 10, 60, 177, 18, 51, 138, 206, 164, 0, 221, 10, 67, 145, 154, 50, 74, 170, 172, 0, 222, 9, 44, 177, 146, 87, 28, 41, 3, 223, 9, 44, 177, 178, 162, 42, 85, 2, 224, 8, 51, 145, 146, 51, 92, 73, 225, 8, 51, 145, 154, 50, 92, 73, 226, 8, 51, 145, 214, 70, 43, 9, 227, 10, 52, 177, 150, 84, 198, 170, 84, 0, 228, 8, 43, 145, 146, 50, 90, 73, 229, 10, 59, 145, 50, 210, 200, 104, 37, 1, 230, 7, 28, 177, 54, 82, 28, 231, 8, 51, 139, 182, 85, 94, 0, 232, 7, 50, 113, 18, 227, 18, 233, 7, 50, 113, 166, 151, 0, 234, 8, 51, 145, 214, 86, 81, 1, 235, 8, 43, 145, 146, 178, 138, 10, 236, 7, 50, 113, 18, 115, 42, 237, 7, 50, 113, 166, 83, 5, 238, 8, 51, 145, 214, 70, 177, 2, 239, 9, 43, 145, 146, 50, 138, 21, 0, 240, 12, 60, 177, 146, 98, 149, 81, 26, 49, 41, 0, 241, 10, 52, 177, 150, 84, 134, 50, 181, 0, 242, 9, 51, 145, 146, 51, 26, 105, 4, 243, 9, 51, 145, 154, 50, 26, 105, 4, 244, 8, 51, 145, 214, 30, 105, 4, 245, 10, 52, 177, 150, 84, 134, 171, 90, 0, 246, 8, 43, 145, 146, 242, 72, 35, 247, 8, 43, 145, 150, 209, 70, 9, 248, 10, 173, 174, 142, 210, 76, 113, 101, 4, 249, 9, 51, 145, 146, 51, 74, 42, 9, 250, 9, 51, 145, 154, 50, 74, 42, 9, 251, 8, 51, 145, 214, 78, 42, 9, 252, 8, 43, 145, 146, 114, 82, 73, 253, 10, 67, 141, 154, 50, 74, 42, 201, 5, 254, 10, 67, 143, 18, 163, 74, 106, 197, 8, 255, 10, 59, 141, 146, 114, 82, 73, 46, 0, 0, 0, 0, 4, 255, 255, 0, 0]; + +// src/font/fonts/u8g2_font_micropixel_tr_u8g2font.ts +var u8g2_font_micropixel_tr_u8g2font = [95, 0, 2, 2, 3, 3, 2, 4, 4, 6, 9, 255, 254, 5, 254, 6, 255, 0, 239, 1, 234, 2, 195, 32, 5, 128, 216, 0, 33, 6, 169, 168, 172, 0, 34, 6, 147, 204, 36, 21, 35, 10, 173, 232, 85, 67, 85, 67, 85, 0, 36, 10, 188, 215, 198, 145, 162, 30, 58, 1, 37, 6, 164, 216, 196, 30, 38, 9, 173, 232, 229, 20, 151, 140, 42, 39, 5, 145, 173, 8, 40, 7, 186, 183, 169, 206, 0, 41, 8, 186, 183, 196, 212, 82, 0, 42, 6, 155, 204, 164, 14, 43, 7, 155, 201, 165, 149, 0, 44, 5, 145, 167, 8, 45, 5, 138, 186, 8, 46, 5, 137, 168, 4, 47, 7, 171, 200, 86, 101, 4, 48, 9, 172, 216, 169, 56, 102, 82, 0, 49, 6, 234, 200, 173, 6, 50, 8, 172, 216, 169, 216, 28, 1, 51, 9, 172, 216, 169, 88, 197, 164, 0, 52, 8, 172, 216, 68, 57, 116, 1, 53, 9, 172, 216, 12, 189, 209, 72, 0, 54, 9, 172, 216, 169, 188, 98, 82, 0, 55, 8, 172, 216, 140, 28, 235, 8, 56, 9, 172, 216, 169, 152, 84, 76, 10, 57, 9, 172, 216, 169, 152, 118, 82, 0, 58, 6, 169, 168, 228, 0, 59, 6, 177, 167, 100, 1, 60, 6, 154, 185, 201, 0, 61, 6, 155, 201, 236, 1, 62, 6, 154, 185, 68, 5, 63, 8, 172, 216, 169, 88, 83, 9, 64, 10, 173, 232, 173, 172, 70, 170, 22, 0, 65, 8, 172, 216, 169, 56, 166, 12, 66, 9, 172, 216, 172, 56, 82, 28, 9, 67, 8, 172, 216, 141, 92, 163, 1, 68, 8, 172, 216, 172, 104, 142, 4, 69, 8, 172, 216, 12, 189, 242, 8, 70, 8, 172, 216, 12, 189, 114, 6, 71, 8, 172, 216, 141, 156, 102, 26, 72, 8, 172, 216, 68, 57, 166, 12, 73, 7, 171, 200, 172, 88, 13, 74, 7, 171, 200, 201, 114, 1, 75, 9, 172, 216, 68, 149, 100, 42, 3, 76, 7, 171, 200, 196, 230, 0, 77, 9, 173, 232, 228, 181, 146, 214, 1, 78, 8, 172, 216, 196, 165, 166, 12, 79, 8, 172, 216, 169, 104, 38, 5, 80, 9, 172, 216, 172, 56, 82, 206, 0, 81, 8, 172, 216, 169, 104, 169, 1, 82, 9, 172, 216, 172, 56, 82, 148, 1, 83, 9, 172, 216, 141, 140, 52, 26, 9, 84, 7, 171, 200, 172, 216, 2, 85, 8, 172, 216, 68, 103, 82, 0, 86, 8, 172, 216, 68, 87, 140, 0, 87, 9, 173, 232, 100, 43, 169, 46, 0, 88, 9, 172, 216, 68, 153, 84, 148, 1, 89, 8, 171, 200, 36, 85, 86, 0, 90, 9, 172, 216, 140, 156, 84, 30, 1, 91, 7, 186, 183, 172, 46, 1, 92, 7, 171, 200, 196, 92, 6, 93, 7, 186, 183, 168, 174, 1, 94, 5, 147, 204, 53, 95, 6, 140, 216, 140, 0, 96, 6, 146, 188, 196, 0, 97, 6, 155, 200, 173, 36, 98, 8, 171, 200, 196, 168, 210, 2, 99, 6, 155, 200, 109, 1, 100, 7, 171, 200, 86, 43, 9, 101, 6, 154, 184, 44, 1, 102, 8, 171, 200, 166, 180, 98, 2, 103, 8, 171, 198, 173, 36, 23, 0, 104, 8, 171, 200, 196, 168, 146, 10, 105, 6, 169, 168, 164, 1, 106, 8, 122, 166, 229, 84, 41, 0, 107, 8, 163, 200, 164, 149, 84, 0, 108, 6, 169, 168, 12, 1, 109, 8, 157, 232, 140, 84, 169, 2, 110, 7, 155, 200, 168, 164, 2, 111, 7, 155, 200, 140, 52, 2, 112, 8, 171, 198, 168, 180, 98, 4, 113, 7, 171, 198, 173, 36, 11, 114, 7, 155, 200, 140, 36, 1, 115, 7, 155, 200, 169, 164, 0, 116, 7, 170, 184, 164, 21, 3, 117, 7, 155, 200, 36, 149, 4, 118, 7, 155, 200, 36, 85, 1, 119, 8, 157, 232, 164, 164, 186, 0, 120, 6, 155, 200, 164, 14, 121, 8, 171, 198, 36, 149, 228, 2, 122, 6, 155, 200, 172, 52, 123, 9, 187, 199, 166, 152, 114, 204, 1, 124, 5, 177, 168, 28, 125, 9, 187, 199, 228, 152, 83, 76, 17, 126, 7, 148, 218, 37, 149, 0, 0, 0, 0, 4, 255, 255, 0, 0]; + +// src/font/fonts/u8g2_font_NokiaSmallPlain_tf_u8g2font.ts +var u8g2_font_NokiaSmallPlain_tf_u8g2font = [148, 0, 2, 2, 3, 4, 2, 4, 5, 7, 8, 0, 255, 7, 255, 7, 255, 1, 34, 2, 43, 5, 72, 32, 5, 0, 113, 2, 33, 7, 57, 81, 50, 84, 0, 34, 7, 19, 155, 146, 84, 0, 35, 13, 61, 209, 86, 166, 52, 84, 53, 84, 153, 18, 0, 36, 10, 68, 175, 26, 71, 74, 243, 208, 9, 37, 10, 60, 177, 162, 86, 140, 178, 90, 2, 38, 12, 61, 209, 150, 83, 76, 185, 74, 50, 170, 0, 39, 5, 17, 91, 34, 40, 7, 66, 111, 166, 122, 6, 41, 8, 66, 111, 18, 83, 151, 2, 42, 10, 45, 211, 86, 199, 33, 115, 74, 0, 43, 10, 45, 211, 154, 81, 28, 50, 163, 8, 44, 6, 18, 111, 166, 0, 45, 6, 12, 183, 50, 2, 46, 5, 9, 81, 18, 47, 9, 59, 145, 90, 197, 42, 70, 0, 48, 9, 60, 177, 166, 162, 103, 82, 0, 49, 6, 186, 145, 182, 122, 50, 10, 60, 177, 178, 81, 78, 42, 231, 17, 51, 11, 60, 177, 178, 81, 78, 26, 229, 145, 0, 52, 9, 60, 177, 30, 85, 146, 67, 23, 53, 10, 60, 177, 178, 242, 70, 57, 143, 4, 54, 10, 60, 177, 166, 242, 138, 102, 82, 0, 55, 10, 60, 177, 50, 114, 204, 49, 151, 0, 56, 11, 60, 177, 166, 162, 76, 42, 202, 164, 0, 57, 10, 60, 177, 166, 162, 153, 118, 82, 0, 58, 6, 161, 113, 18, 3, 59, 7, 42, 111, 150, 161, 2, 60, 10, 60, 177, 30, 219, 40, 163, 140, 2, 61, 8, 28, 179, 50, 50, 26, 1, 62, 11, 60, 177, 146, 81, 70, 25, 197, 54, 0, 63, 10, 60, 177, 178, 81, 172, 51, 21, 1, 64, 12, 61, 209, 182, 178, 26, 106, 169, 156, 22, 0, 65, 9, 60, 177, 166, 162, 57, 166, 12, 66, 10, 60, 177, 178, 226, 72, 209, 28, 9, 67, 8, 60, 177, 54, 114, 55, 26, 68, 9, 60, 177, 178, 162, 231, 72, 0, 69, 9, 60, 177, 50, 244, 202, 245, 8, 70, 9, 60, 177, 50, 244, 202, 109, 0, 71, 9, 60, 177, 54, 114, 154, 102, 26, 72, 9, 60, 177, 18, 229, 152, 206, 0, 73, 6, 57, 81, 114, 4, 74, 7, 59, 145, 218, 115, 1, 75, 10, 60, 177, 18, 85, 146, 89, 166, 50, 76, 8, 60, 177, 146, 123, 143, 0, 77, 10, 61, 209, 146, 215, 74, 42, 105, 59, 78, 10, 61, 209, 146, 167, 74, 114, 219, 1, 79, 9, 61, 209, 182, 178, 119, 90, 0, 80, 10, 60, 177, 178, 162, 57, 82, 206, 0, 81, 10, 69, 207, 182, 178, 87, 181, 97, 0, 82, 9, 60, 177, 178, 162, 57, 82, 51, 83, 11, 60, 177, 54, 114, 70, 26, 229, 145, 0, 84, 13, 61, 209, 50, 100, 70, 25, 101, 148, 81, 70, 17, 85, 8, 60, 177, 18, 125, 38, 5, 86, 11, 61, 209, 146, 237, 84, 166, 156, 81, 4, 87, 15, 63, 17, 147, 161, 134, 41, 199, 84, 166, 58, 101, 84, 2, 88, 10, 61, 209, 146, 117, 170, 171, 90, 7, 89, 12, 61, 209, 146, 117, 170, 51, 202, 40, 163, 8, 90, 9, 60, 177, 50, 114, 108, 231, 17, 92, 9, 59, 145, 18, 115, 172, 99, 0, 95, 6, 13, 207, 50, 4, 97, 9, 44, 177, 166, 81, 26, 49, 13, 98, 10, 60, 177, 146, 243, 138, 230, 72, 0, 99, 7, 43, 145, 54, 107, 1, 100, 9, 60, 177, 94, 141, 104, 166, 1, 101, 9, 44, 177, 166, 226, 216, 72, 1, 102, 7, 58, 113, 166, 86, 11, 103, 10, 52, 175, 54, 162, 76, 59, 41, 0, 104, 9, 60, 177, 146, 243, 138, 206, 0, 105, 7, 57, 81, 146, 134, 0, 106, 8, 66, 111, 150, 83, 75, 1, 107, 10, 60, 177, 146, 75, 149, 100, 42, 3, 108, 6, 57, 81, 114, 4, 109, 11, 45, 209, 50, 82, 165, 146, 74, 170, 0, 110, 7, 44, 177, 178, 162, 51, 111, 9, 44, 177, 166, 162, 153, 20, 0, 112, 10, 52, 175, 178, 162, 28, 41, 103, 0, 113, 9, 52, 175, 54, 162, 76, 187, 0, 114, 8, 43, 145, 146, 134, 44, 1, 115, 7, 43, 145, 182, 243, 2, 116, 8, 58, 113, 146, 210, 42, 3, 117, 7, 44, 177, 18, 157, 105, 118, 10, 45, 209, 146, 117, 42, 83, 142, 0, 119, 10, 45, 209, 146, 85, 82, 157, 41, 1, 120, 9, 44, 177, 18, 101, 82, 81, 6, 121, 9, 52, 175, 18, 205, 180, 147, 2, 122, 8, 44, 177, 50, 98, 123, 4, 126, 8, 61, 209, 114, 123, 143, 1, 160, 5, 0, 113, 2, 161, 7, 57, 81, 146, 134, 0, 163, 10, 60, 177, 54, 114, 158, 57, 142, 0, 164, 10, 52, 177, 18, 147, 138, 50, 169, 24, 165, 12, 61, 209, 146, 83, 53, 100, 222, 25, 69, 0, 167, 10, 67, 143, 86, 173, 164, 210, 170, 0, 191, 9, 60, 177, 154, 169, 28, 107, 52, 192, 10, 60, 177, 150, 81, 84, 113, 76, 25, 193, 9, 60, 177, 90, 171, 56, 166, 12, 194, 10, 60, 177, 166, 98, 82, 113, 76, 25, 195, 10, 60, 177, 150, 84, 84, 113, 76, 25, 196, 10, 60, 177, 18, 51, 84, 113, 76, 25, 197, 9, 60, 177, 38, 173, 56, 166, 12, 198, 11, 62, 241, 58, 82, 157, 242, 72, 177, 115, 199, 9, 68, 175, 54, 114, 55, 154, 9, 200, 10, 60, 177, 150, 81, 26, 122, 229, 17, 201, 9, 60, 177, 90, 14, 189, 242, 8, 202, 9, 60, 177, 90, 141, 189, 242, 8, 203, 10, 60, 177, 86, 163, 161, 87, 30, 1, 204, 8, 58, 113, 18, 85, 23, 0, 205, 7, 58, 113, 166, 122, 1, 206, 7, 59, 145, 214, 217, 5, 207, 9, 59, 145, 146, 50, 138, 93, 0, 209, 10, 61, 209, 102, 166, 60, 85, 146, 59, 210, 10, 61, 209, 154, 97, 92, 217, 78, 11, 211, 9, 61, 209, 94, 175, 108, 167, 5, 212, 10, 61, 209, 154, 83, 26, 219, 78, 11, 214, 10, 61, 209, 86, 83, 43, 219, 105, 1, 216, 10, 61, 209, 54, 228, 149, 230, 161, 0, 217, 9, 60, 177, 150, 81, 233, 76, 10, 218, 8, 60, 177, 218, 116, 38, 5, 219, 10, 60, 177, 166, 98, 70, 209, 76, 10, 220, 10, 60, 177, 18, 51, 138, 206, 164, 0, 223, 10, 67, 143, 86, 169, 149, 212, 138, 0, 224, 11, 60, 177, 150, 81, 212, 40, 141, 152, 6, 225, 10, 60, 177, 30, 163, 70, 105, 196, 52, 226, 11, 60, 177, 166, 98, 210, 40, 141, 152, 6, 227, 11, 60, 177, 150, 84, 212, 40, 141, 152, 6, 228, 10, 60, 177, 86, 67, 141, 210, 136, 105, 229, 10, 60, 177, 90, 197, 140, 210, 136, 105, 230, 10, 46, 241, 166, 50, 170, 198, 204, 74, 231, 8, 51, 143, 54, 107, 149, 0, 232, 11, 60, 177, 150, 81, 84, 113, 108, 164, 0, 233, 10, 60, 177, 90, 171, 56, 54, 82, 0, 234, 11, 60, 177, 166, 98, 82, 113, 108, 164, 0, 235, 10, 60, 177, 86, 67, 21, 199, 70, 10, 236, 8, 58, 113, 18, 85, 23, 0, 237, 7, 58, 113, 166, 83, 11, 238, 7, 59, 145, 214, 217, 5, 239, 9, 59, 145, 146, 50, 138, 93, 0, 241, 9, 60, 177, 150, 84, 90, 209, 25, 242, 10, 60, 177, 150, 81, 84, 209, 76, 10, 243, 9, 60, 177, 90, 171, 104, 38, 5, 244, 10, 60, 177, 166, 98, 82, 209, 76, 10, 246, 10, 60, 177, 18, 51, 84, 209, 76, 10, 248, 10, 44, 177, 54, 162, 26, 105, 36, 0, 249, 9, 60, 177, 150, 81, 233, 76, 3, 250, 8, 60, 177, 218, 116, 166, 1, 251, 10, 60, 177, 166, 98, 70, 209, 76, 3, 252, 9, 60, 177, 18, 51, 138, 206, 52, 0, 0, 0, 4, 255, 255, 0, 0]; + +// src/font/fonts/u8g2_font_smolfont_tf_u8g2font.ts +var u8g2_font_smolfont_tf_u8g2font = [187, 0, 2, 2, 3, 4, 2, 4, 4, 5, 10, 255, 254, 5, 255, 5, 0, 0, 226, 1, 204, 5, 130, 32, 5, 0, 113, 1, 33, 6, 41, 81, 89, 1, 34, 6, 19, 151, 73, 42, 35, 7, 43, 145, 73, 253, 0, 36, 7, 59, 143, 139, 207, 4, 37, 8, 43, 145, 73, 49, 149, 41, 38, 9, 59, 143, 137, 35, 165, 67, 2, 39, 5, 17, 87, 17, 40, 7, 42, 113, 83, 101, 0, 41, 8, 42, 113, 137, 41, 41, 0, 42, 6, 27, 149, 73, 29, 43, 7, 27, 147, 75, 43, 1, 44, 5, 17, 79, 17, 45, 5, 11, 149, 25, 46, 5, 137, 113, 9, 47, 7, 43, 145, 173, 202, 8, 48, 7, 43, 145, 91, 214, 2, 49, 7, 43, 145, 75, 178, 26, 50, 8, 43, 145, 25, 41, 166, 52, 51, 8, 43, 145, 153, 41, 143, 0, 52, 8, 43, 145, 73, 106, 196, 2, 53, 8, 43, 145, 25, 81, 47, 0, 54, 8, 43, 145, 155, 35, 141, 0, 55, 6, 43, 145, 153, 29, 56, 8, 43, 145, 25, 169, 53, 2, 57, 8, 43, 145, 25, 105, 196, 17, 58, 6, 25, 83, 73, 1, 59, 7, 178, 78, 203, 148, 2, 60, 6, 26, 115, 147, 1, 61, 6, 27, 147, 217, 3, 62, 6, 26, 115, 137, 10, 63, 9, 51, 143, 81, 101, 202, 48, 1, 64, 7, 43, 145, 59, 212, 8, 65, 8, 43, 145, 91, 106, 168, 0, 66, 8, 43, 145, 81, 105, 165, 17, 67, 8, 43, 145, 91, 50, 141, 0, 68, 8, 43, 145, 81, 201, 26, 1, 69, 8, 43, 145, 25, 81, 197, 1, 70, 8, 43, 145, 25, 81, 197, 8, 71, 8, 51, 143, 155, 73, 141, 24, 72, 8, 43, 145, 137, 105, 40, 21, 73, 7, 43, 145, 89, 177, 26, 74, 7, 43, 145, 45, 213, 2, 75, 8, 43, 145, 73, 106, 37, 21, 76, 7, 43, 145, 137, 205, 1, 77, 8, 43, 145, 73, 99, 40, 21, 78, 7, 43, 145, 81, 201, 21, 79, 8, 43, 145, 25, 201, 26, 1, 80, 8, 43, 145, 25, 105, 197, 8, 81, 7, 51, 143, 91, 214, 14, 82, 8, 43, 145, 81, 73, 173, 2, 83, 7, 43, 145, 155, 23, 0, 84, 7, 43, 145, 89, 177, 5, 85, 7, 43, 145, 73, 174, 36, 86, 8, 43, 145, 137, 73, 149, 9, 87, 8, 43, 145, 85, 106, 168, 0, 88, 7, 43, 145, 73, 101, 85, 89, 7, 43, 145, 85, 101, 5, 90, 7, 43, 145, 153, 169, 28, 91, 7, 42, 113, 89, 149, 0, 92, 7, 43, 145, 137, 185, 12, 93, 7, 42, 113, 81, 213, 0, 94, 5, 19, 151, 107, 95, 5, 11, 145, 25, 96, 6, 18, 119, 137, 1, 97, 6, 27, 145, 59, 2, 98, 8, 35, 145, 137, 42, 141, 0, 99, 6, 27, 145, 25, 113, 100, 7, 35, 145, 77, 71, 0, 101, 6, 27, 145, 25, 106, 102, 7, 27, 145, 25, 42, 2, 103, 8, 35, 143, 25, 41, 201, 0, 104, 8, 35, 145, 137, 113, 164, 0, 105, 6, 33, 81, 73, 2, 106, 7, 42, 111, 203, 73, 1, 107, 7, 27, 145, 73, 171, 0, 108, 7, 34, 113, 73, 149, 0, 109, 7, 27, 145, 73, 67, 5, 110, 7, 27, 145, 81, 73, 5, 111, 7, 27, 145, 25, 105, 4, 112, 8, 35, 143, 25, 105, 69, 0, 113, 7, 35, 143, 171, 118, 0, 114, 6, 27, 145, 155, 17, 115, 7, 27, 145, 83, 73, 1, 116, 6, 34, 113, 73, 75, 117, 7, 27, 145, 73, 42, 9, 118, 7, 27, 145, 137, 169, 2, 119, 7, 35, 145, 85, 67, 5, 120, 6, 27, 145, 73, 29, 121, 7, 35, 145, 85, 101, 2, 122, 6, 27, 145, 145, 81, 123, 7, 43, 145, 83, 117, 20, 124, 6, 41, 81, 25, 2, 125, 8, 43, 145, 145, 57, 37, 5, 126, 6, 27, 147, 137, 51, 160, 5, 0, 113, 1, 161, 6, 49, 79, 73, 35, 162, 8, 43, 143, 29, 114, 68, 0, 163, 8, 51, 143, 155, 42, 206, 0, 164, 8, 43, 145, 73, 67, 13, 21, 165, 9, 51, 143, 73, 213, 74, 43, 1, 166, 6, 169, 113, 81, 2, 167, 8, 43, 145, 27, 169, 26, 9, 168, 6, 11, 153, 73, 1, 169, 8, 59, 143, 217, 35, 238, 1, 170, 7, 27, 149, 25, 105, 1, 171, 6, 27, 147, 27, 81, 172, 6, 19, 147, 153, 1, 174, 8, 51, 143, 25, 105, 213, 3, 175, 5, 11, 153, 25, 176, 6, 27, 149, 235, 2, 177, 8, 43, 145, 75, 43, 163, 1, 178, 6, 27, 149, 217, 3, 179, 6, 27, 149, 153, 35, 180, 6, 18, 119, 83, 0, 181, 8, 35, 143, 73, 106, 72, 0, 182, 8, 51, 143, 91, 42, 201, 2, 183, 5, 9, 89, 9, 184, 6, 146, 141, 137, 1, 185, 6, 26, 119, 91, 1, 186, 6, 18, 119, 25, 1, 187, 7, 27, 147, 145, 35, 1, 191, 9, 51, 143, 203, 48, 197, 148, 4, 192, 10, 67, 145, 201, 25, 46, 53, 84, 0, 193, 10, 67, 145, 77, 25, 46, 53, 84, 0, 194, 9, 67, 145, 107, 163, 165, 134, 10, 195, 10, 67, 145, 145, 26, 45, 53, 84, 0, 196, 10, 59, 145, 73, 25, 45, 53, 84, 0, 197, 9, 59, 145, 107, 45, 53, 84, 0, 198, 9, 44, 177, 27, 169, 26, 42, 9, 199, 9, 59, 141, 91, 50, 141, 84, 2, 200, 10, 67, 145, 201, 25, 141, 168, 226, 0, 201, 10, 67, 145, 77, 25, 141, 168, 226, 0, 202, 9, 67, 145, 107, 143, 168, 226, 0, 203, 9, 59, 145, 73, 121, 68, 21, 7, 204, 9, 67, 145, 201, 25, 173, 88, 13, 205, 9, 67, 145, 77, 25, 173, 88, 13, 206, 8, 67, 145, 107, 175, 88, 13, 207, 8, 59, 145, 73, 121, 197, 106, 208, 8, 172, 144, 147, 105, 168, 106, 209, 9, 67, 145, 145, 90, 37, 87, 0, 210, 10, 67, 145, 201, 25, 141, 100, 141, 0, 211, 10, 67, 145, 77, 25, 141, 100, 141, 0, 212, 9, 67, 145, 107, 143, 100, 141, 0, 213, 9, 67, 145, 145, 122, 36, 107, 4, 214, 9, 59, 145, 73, 121, 36, 107, 4, 215, 6, 27, 147, 73, 29, 216, 13, 189, 174, 71, 105, 166, 152, 98, 138, 43, 35, 0, 217, 9, 67, 145, 201, 25, 37, 87, 18, 218, 9, 67, 145, 77, 25, 37, 87, 18, 219, 8, 67, 145, 107, 39, 87, 18, 220, 9, 59, 145, 73, 57, 185, 146, 0, 221, 9, 67, 145, 77, 25, 171, 178, 2, 222, 8, 43, 145, 137, 42, 173, 8, 223, 10, 59, 141, 81, 105, 165, 33, 35, 0, 224, 8, 51, 145, 201, 25, 174, 17, 225, 8, 51, 145, 77, 25, 174, 17, 226, 7, 51, 145, 107, 163, 35, 227, 8, 51, 145, 145, 26, 29, 1, 228, 8, 43, 145, 73, 25, 29, 1, 229, 7, 51, 145, 235, 92, 35, 230, 8, 28, 177, 27, 41, 141, 0, 231, 8, 43, 141, 25, 113, 149, 0, 232, 8, 51, 145, 201, 25, 13, 53, 233, 8, 51, 145, 77, 25, 13, 53, 234, 7, 51, 145, 107, 15, 53, 235, 8, 43, 145, 73, 121, 168, 1, 236, 7, 178, 80, 137, 57, 21, 237, 7, 178, 80, 137, 57, 21, 238, 8, 179, 112, 107, 163, 88, 1, 239, 8, 171, 112, 73, 25, 197, 10, 240, 8, 43, 145, 203, 105, 85, 0, 241, 8, 51, 145, 145, 90, 37, 21, 242, 9, 51, 145, 201, 25, 141, 52, 2, 243, 9, 51, 145, 77, 25, 141, 52, 2, 244, 8, 51, 145, 107, 143, 52, 2, 245, 8, 51, 145, 145, 122, 164, 17, 246, 8, 43, 145, 73, 121, 164, 17, 247, 8, 43, 145, 203, 104, 163, 4, 248, 10, 173, 174, 71, 105, 166, 184, 50, 2, 249, 9, 51, 145, 201, 25, 37, 149, 4, 250, 9, 51, 145, 77, 25, 37, 149, 4, 251, 8, 51, 145, 107, 39, 149, 4, 252, 8, 43, 145, 73, 57, 169, 36, 253, 9, 59, 145, 77, 25, 171, 50, 1, 254, 8, 35, 145, 137, 99, 68, 0, 255, 9, 51, 145, 73, 25, 170, 50, 1, 0, 0, 0, 4, 255, 255, 0, 0]; + +// src/font/fonts/u8g2_font_spleen12x24_me_u8g2font.ts +var u8g2_font_spleen12x24_me_u8g2font = [122, 2, 4, 2, 4, 5, 1, 4, 5, 12, 24, 0, 251, 15, 251, 17, 253, 2, 163, 5, 225, 18, 235, 32, 9, 140, 15, 127, 254, 255, 55, 0, 33, 14, 140, 15, 127, 134, 82, 255, 123, 34, 169, 158, 43, 0, 34, 12, 140, 15, 127, 172, 200, 255, 247, 252, 159, 0, 35, 21, 140, 15, 127, 86, 34, 223, 12, 14, 50, 34, 255, 223, 12, 14, 50, 34, 223, 115, 2, 36, 39, 140, 15, 127, 184, 84, 56, 24, 104, 20, 50, 137, 76, 34, 147, 200, 36, 58, 133, 112, 48, 84, 232, 36, 50, 137, 76, 34, 147, 200, 20, 154, 193, 64, 40, 213, 211, 2, 37, 27, 140, 15, 127, 14, 69, 27, 11, 35, 133, 213, 66, 170, 148, 42, 165, 138, 149, 194, 72, 97, 163, 25, 233, 57, 4, 38, 32, 140, 15, 127, 118, 131, 156, 68, 37, 242, 87, 18, 221, 32, 38, 81, 137, 20, 17, 213, 68, 165, 81, 137, 52, 131, 208, 64, 162, 231, 0, 39, 11, 140, 15, 127, 184, 212, 239, 249, 191, 2, 40, 18, 140, 15, 127, 146, 161, 210, 169, 82, 234, 111, 165, 90, 183, 123, 40, 0, 41, 17, 140, 15, 127, 232, 214, 83, 173, 212, 95, 74, 149, 14, 247, 36, 0, 42, 24, 140, 15, 127, 62, 20, 169, 36, 186, 65, 82, 54, 56, 136, 41, 7, 57, 137, 74, 164, 231, 45, 0, 43, 15, 140, 15, 127, 126, 40, 181, 27, 12, 116, 82, 123, 190, 5, 44, 12, 140, 15, 127, 254, 159, 72, 45, 237, 169, 1, 45, 12, 140, 15, 127, 254, 237, 96, 160, 231, 207, 1, 46, 11, 140, 15, 127, 254, 159, 75, 245, 92, 1, 47, 25, 140, 15, 127, 34, 169, 82, 170, 148, 42, 165, 74, 169, 82, 170, 148, 42, 165, 74, 169, 82, 170, 167, 1, 48, 36, 140, 15, 127, 102, 131, 149, 72, 35, 147, 200, 36, 170, 137, 104, 16, 209, 40, 36, 94, 104, 36, 131, 144, 100, 37, 145, 73, 100, 26, 145, 106, 176, 231, 6, 49, 19, 140, 15, 127, 118, 195, 65, 78, 161, 139, 72, 253, 239, 6, 3, 61, 39, 0, 50, 20, 140, 15, 127, 102, 131, 149, 72, 35, 147, 186, 244, 207, 36, 131, 131, 60, 23, 0, 51, 24, 140, 15, 127, 102, 131, 149, 72, 35, 147, 90, 202, 6, 90, 83, 39, 50, 141, 72, 53, 216, 115, 3, 52, 17, 140, 15, 127, 70, 82, 71, 254, 63, 26, 28, 4, 165, 238, 185, 1, 53, 25, 140, 15, 127, 70, 131, 131, 136, 76, 34, 117, 58, 24, 104, 77, 157, 200, 52, 34, 213, 96, 207, 13, 0, 54, 31, 140, 15, 127, 102, 131, 65, 72, 37, 145, 58, 29, 12, 68, 42, 141, 76, 34, 147, 200, 36, 50, 137, 76, 35, 82, 13, 246, 220, 0, 55, 16, 140, 15, 127, 70, 131, 131, 136, 76, 234, 210, 167, 190, 231, 12, 56, 34, 140, 15, 127, 102, 131, 149, 72, 35, 147, 200, 36, 50, 141, 72, 53, 88, 137, 52, 50, 137, 76, 34, 147, 200, 36, 50, 141, 72, 53, 216, 115, 3, 57, 30, 140, 15, 127, 102, 131, 149, 72, 35, 147, 200, 36, 50, 137, 76, 34, 211, 168, 68, 131, 129, 212, 19, 149, 104, 48, 200, 115, 3, 58, 13, 140, 15, 127, 254, 74, 170, 231, 212, 158, 43, 0, 59, 14, 140, 15, 127, 254, 74, 170, 103, 45, 181, 180, 167, 6, 60, 12, 140, 15, 127, 198, 74, 127, 235, 239, 185, 0, 61, 16, 140, 15, 127, 126, 52, 56, 200, 179, 26, 28, 228, 249, 17, 0, 62, 12, 140, 15, 127, 86, 90, 127, 233, 239, 57, 4, 63, 19, 140, 15, 127, 102, 131, 149, 72, 35, 147, 90, 122, 106, 79, 36, 213, 115, 5, 64, 38, 140, 15, 127, 110, 6, 43, 145, 70, 38, 145, 73, 36, 11, 137, 100, 33, 145, 44, 36, 146, 133, 68, 178, 144, 72, 22, 18, 201, 96, 162, 213, 14, 6, 121, 78, 0, 65, 35, 140, 15, 127, 102, 131, 149, 72, 35, 147, 200, 36, 50, 137, 76, 34, 147, 12, 14, 34, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 211, 115, 1, 66, 37, 140, 15, 127, 70, 131, 129, 72, 165, 145, 73, 100, 18, 153, 68, 38, 81, 105, 6, 3, 145, 74, 35, 147, 200, 36, 50, 137, 76, 162, 210, 12, 6, 122, 110, 0, 67, 18, 140, 15, 127, 102, 131, 129, 70, 41, 245, 191, 213, 14, 6, 122, 46, 0, 68, 36, 140, 15, 127, 70, 131, 129, 72, 165, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 165, 25, 12, 244, 220, 0, 69, 22, 140, 15, 127, 102, 131, 129, 70, 41, 245, 116, 48, 16, 73, 189, 213, 14, 6, 122, 46, 0, 70, 19, 140, 15, 127, 102, 131, 129, 70, 41, 245, 116, 48, 16, 73, 253, 158, 75, 0, 71, 29, 140, 15, 127, 102, 131, 129, 70, 41, 245, 205, 64, 34, 147, 200, 36, 50, 137, 76, 34, 211, 168, 68, 131, 129, 158, 11, 0, 72, 35, 140, 15, 127, 70, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 12, 14, 34, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 211, 115, 1, 73, 16, 140, 15, 127, 86, 131, 129, 78, 234, 255, 221, 96, 160, 231, 4, 74, 16, 140, 15, 127, 86, 131, 129, 78, 234, 255, 114, 54, 208, 115, 6, 75, 34, 140, 15, 127, 70, 50, 137, 76, 34, 147, 200, 36, 50, 137, 74, 35, 18, 13, 6, 41, 145, 149, 70, 38, 145, 73, 100, 18, 153, 68, 166, 231, 2, 76, 15, 140, 15, 127, 70, 82, 255, 111, 181, 131, 129, 158, 11, 0, 77, 36, 140, 15, 127, 70, 50, 201, 104, 50, 136, 12, 34, 138, 65, 66, 226, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 166, 231, 2, 78, 39, 140, 15, 127, 70, 50, 201, 74, 178, 146, 12, 66, 146, 65, 72, 162, 208, 72, 20, 26, 137, 191, 81, 72, 52, 10, 137, 104, 16, 17, 13, 34, 170, 137, 106, 207, 5, 0, 79, 34, 140, 15, 127, 102, 131, 149, 72, 35, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 141, 72, 53, 216, 115, 3, 80, 26, 140, 15, 127, 70, 131, 129, 72, 165, 145, 73, 100, 18, 153, 68, 38, 81, 105, 6, 3, 145, 212, 239, 185, 4, 81, 36, 140, 15, 127, 102, 131, 149, 72, 35, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 226, 111, 20, 26, 201, 32, 53, 152, 106, 165, 122, 44, 0, 82, 36, 140, 15, 127, 70, 131, 129, 72, 165, 145, 73, 100, 18, 153, 68, 38, 81, 105, 6, 3, 145, 74, 35, 147, 200, 36, 50, 137, 76, 34, 147, 200, 244, 92, 0, 83, 22, 140, 15, 127, 102, 131, 129, 70, 41, 117, 171, 29, 108, 77, 93, 106, 6, 3, 61, 55, 0, 84, 14, 140, 15, 127, 70, 131, 131, 152, 212, 255, 247, 92, 1, 85, 35, 140, 15, 127, 70, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 141, 74, 52, 24, 232, 185, 0, 86, 32, 140, 15, 127, 70, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 35, 114, 37, 209, 13, 146, 122, 174, 0, 87, 36, 140, 15, 127, 70, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 226, 197, 32, 33, 25, 68, 6, 145, 209, 68, 166, 231, 2, 88, 34, 140, 15, 127, 70, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 35, 82, 13, 86, 34, 141, 76, 34, 147, 200, 36, 50, 137, 76, 34, 211, 115, 1, 89, 27, 140, 15, 127, 70, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 163, 18, 13, 6, 82, 95, 78, 6, 247, 156, 0, 90, 17, 140, 15, 127, 70, 131, 131, 168, 75, 255, 212, 116, 112, 144, 231, 2, 91, 15, 140, 15, 255, 193, 64, 36, 245, 255, 127, 58, 24, 168, 1, 92, 25, 140, 15, 127, 164, 84, 43, 213, 74, 181, 82, 173, 84, 43, 213, 74, 181, 82, 173, 84, 43, 213, 67, 1, 93, 14, 140, 15, 239, 193, 64, 234, 255, 255, 209, 96, 160, 7, 94, 18, 140, 15, 127, 120, 116, 168, 144, 105, 68, 42, 137, 78, 207, 255, 53, 0, 95, 11, 140, 15, 127, 254, 255, 233, 224, 32, 13, 96, 11, 140, 15, 127, 176, 214, 61, 255, 159, 2, 97, 27, 140, 15, 127, 62, 28, 12, 178, 166, 162, 193, 64, 163, 146, 200, 36, 50, 137, 76, 163, 18, 13, 6, 122, 46, 0, 98, 31, 140, 15, 127, 70, 82, 167, 131, 129, 72, 165, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 165, 25, 12, 244, 220, 0, 99, 18, 140, 15, 127, 190, 28, 12, 52, 74, 169, 223, 106, 7, 3, 61, 23, 0, 100, 31, 140, 15, 127, 198, 82, 71, 131, 129, 70, 37, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 70, 37, 26, 12, 244, 92, 0, 101, 26, 140, 15, 127, 190, 28, 12, 52, 42, 137, 76, 34, 147, 200, 36, 131, 131, 136, 212, 86, 59, 24, 232, 185, 0, 102, 18, 140, 15, 127, 134, 3, 217, 82, 234, 112, 48, 200, 73, 253, 61, 103, 0, 103, 34, 140, 15, 127, 190, 28, 12, 52, 42, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 52, 42, 209, 96, 16, 213, 74, 149, 162, 193, 32, 3, 104, 30, 140, 15, 127, 70, 82, 167, 131, 129, 72, 165, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 233, 185, 0, 105, 16, 140, 15, 127, 134, 82, 61, 201, 32, 234, 63, 29, 228, 185, 1, 106, 15, 140, 15, 127, 134, 82, 61, 145, 212, 255, 151, 186, 65, 14, 107, 26, 140, 15, 127, 86, 82, 143, 188, 81, 73, 100, 3, 157, 66, 39, 145, 105, 84, 34, 43, 141, 74, 207, 5, 0, 108, 13, 140, 15, 127, 118, 82, 255, 79, 15, 244, 156, 0, 109, 22, 140, 15, 127, 190, 27, 68, 68, 18, 133, 70, 226, 255, 127, 38, 145, 73, 100, 122, 46, 0, 110, 29, 140, 15, 127, 190, 27, 12, 68, 42, 141, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 207, 5, 0, 111, 28, 140, 15, 127, 190, 28, 172, 68, 26, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 105, 68, 170, 193, 158, 27, 0, 112, 30, 140, 15, 127, 190, 27, 12, 68, 42, 141, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 42, 205, 96, 32, 146, 122, 9, 113, 30, 140, 15, 127, 190, 28, 12, 52, 42, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 52, 42, 209, 96, 32, 245, 5, 0, 114, 15, 140, 15, 127, 190, 28, 12, 52, 42, 137, 212, 191, 231, 18, 115, 19, 140, 15, 127, 62, 28, 156, 72, 221, 14, 6, 90, 169, 147, 193, 61, 39, 0, 116, 17, 140, 15, 127, 118, 82, 135, 131, 65, 78, 234, 79, 15, 244, 156, 0, 117, 29, 140, 15, 127, 190, 147, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 105, 84, 162, 193, 64, 207, 5, 0, 118, 25, 140, 15, 127, 190, 147, 73, 100, 18, 153, 68, 38, 145, 73, 100, 26, 145, 43, 137, 110, 144, 212, 115, 5, 119, 22, 140, 15, 127, 190, 147, 73, 100, 18, 153, 196, 255, 255, 70, 33, 17, 73, 6, 121, 46, 0, 120, 26, 140, 15, 127, 190, 147, 73, 100, 26, 145, 74, 162, 27, 4, 7, 57, 137, 74, 228, 70, 38, 145, 233, 185, 0, 121, 32, 140, 15, 127, 190, 147, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 105, 84, 162, 193, 64, 234, 82, 51, 24, 104, 0, 122, 17, 140, 15, 127, 190, 27, 28, 68, 149, 254, 233, 224, 32, 207, 5, 0, 123, 19, 140, 15, 127, 208, 32, 167, 148, 250, 165, 112, 185, 53, 245, 91, 237, 32, 13, 124, 11, 140, 15, 127, 90, 169, 255, 255, 158, 22, 125, 19, 140, 15, 247, 65, 214, 212, 111, 181, 203, 161, 82, 234, 151, 186, 65, 30, 3, 126, 18, 140, 15, 127, 254, 100, 36, 81, 104, 20, 26, 133, 68, 180, 231, 111, 0, 127, 9, 140, 15, 127, 254, 255, 55, 0, 160, 9, 140, 15, 127, 254, 255, 55, 0, 161, 14, 140, 15, 127, 134, 82, 61, 145, 212, 255, 158, 43, 0, 162, 33, 140, 15, 127, 222, 72, 117, 131, 129, 70, 162, 210, 168, 52, 42, 141, 74, 163, 210, 168, 52, 42, 141, 76, 162, 27, 12, 116, 82, 61, 41, 0, 163, 25, 140, 15, 127, 134, 67, 133, 76, 163, 210, 168, 164, 134, 131, 129, 76, 234, 82, 170, 28, 28, 228, 185, 0, 164, 25, 140, 15, 127, 206, 69, 42, 137, 110, 144, 27, 172, 68, 254, 106, 176, 27, 228, 36, 42, 145, 158, 115, 0, 165, 30, 140, 15, 127, 70, 50, 137, 76, 35, 114, 37, 145, 73, 116, 131, 224, 32, 169, 27, 12, 116, 82, 221, 96, 160, 147, 234, 185, 2, 166, 14, 140, 15, 127, 90, 169, 191, 39, 146, 250, 123, 90, 0, 167, 29, 140, 15, 127, 210, 129, 76, 162, 210, 168, 180, 218, 225, 96, 37, 242, 87, 131, 225, 214, 74, 163, 146, 200, 6, 122, 82, 0, 168, 11, 140, 15, 127, 66, 145, 123, 254, 127, 5, 169, 35, 140, 15, 127, 30, 15, 86, 34, 141, 76, 34, 89, 72, 20, 26, 137, 66, 35, 81, 104, 36, 10, 141, 68, 178, 144, 200, 52, 34, 213, 96, 207, 13, 0, 170, 22, 140, 15, 127, 244, 32, 43, 213, 13, 100, 18, 153, 68, 55, 208, 99, 6, 123, 254, 75, 0, 171, 24, 140, 15, 127, 126, 40, 81, 73, 84, 18, 149, 68, 37, 209, 73, 116, 18, 157, 68, 39, 209, 115, 13, 172, 13, 140, 15, 127, 126, 52, 56, 136, 250, 158, 31, 1, 173, 10, 140, 15, 127, 254, 241, 96, 207, 63, 174, 37, 140, 15, 127, 30, 15, 86, 34, 141, 76, 162, 152, 88, 40, 18, 18, 133, 34, 33, 81, 76, 44, 20, 9, 137, 66, 145, 144, 200, 52, 34, 213, 96, 207, 13, 0, 175, 10, 140, 15, 127, 240, 96, 207, 255, 63, 176, 19, 140, 15, 127, 210, 65, 78, 34, 147, 200, 36, 50, 137, 110, 144, 231, 255, 8, 177, 18, 140, 15, 127, 158, 75, 237, 6, 3, 157, 212, 158, 98, 48, 208, 243, 22, 178, 17, 140, 15, 127, 210, 65, 78, 34, 85, 122, 34, 27, 236, 249, 47, 1, 179, 19, 140, 15, 127, 210, 65, 78, 34, 21, 110, 165, 50, 137, 110, 144, 231, 63, 5, 180, 11, 140, 15, 127, 10, 165, 123, 254, 63, 6, 181, 18, 140, 15, 127, 190, 19, 249, 255, 143, 38, 131, 140, 98, 98, 234, 37, 0, 182, 32, 140, 15, 127, 102, 131, 129, 70, 33, 241, 255, 141, 66, 34, 26, 68, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 209, 115, 1, 183, 11, 140, 15, 127, 254, 137, 84, 207, 63, 1, 184, 11, 140, 15, 127, 254, 127, 41, 181, 52, 3, 185, 15, 140, 15, 127, 90, 229, 112, 16, 245, 114, 144, 231, 63, 5, 186, 22, 140, 15, 127, 210, 65, 78, 34, 147, 200, 36, 50, 137, 110, 144, 7, 13, 246, 252, 151, 0, 187, 24, 140, 15, 127, 126, 36, 209, 73, 116, 18, 157, 68, 39, 81, 73, 84, 18, 149, 68, 37, 209, 243, 4, 188, 26, 140, 15, 127, 176, 114, 56, 136, 58, 178, 17, 13, 18, 74, 63, 145, 90, 232, 20, 186, 193, 82, 170, 199, 2, 189, 26, 140, 15, 127, 176, 114, 56, 136, 58, 178, 17, 13, 18, 74, 159, 12, 50, 18, 83, 165, 39, 178, 193, 30, 10, 190, 28, 140, 15, 127, 236, 32, 39, 145, 10, 183, 50, 137, 205, 32, 162, 244, 39, 82, 11, 157, 66, 55, 88, 74, 245, 88, 0, 191, 19, 140, 15, 127, 134, 82, 61, 145, 212, 210, 83, 51, 141, 72, 53, 216, 115, 3, 192, 37, 140, 15, 31, 173, 61, 106, 176, 18, 105, 100, 18, 153, 68, 38, 145, 73, 100, 146, 193, 65, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 122, 46, 0, 193, 37, 140, 15, 63, 165, 61, 106, 176, 18, 105, 100, 18, 153, 68, 38, 145, 73, 100, 146, 193, 65, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 122, 46, 0, 194, 40, 140, 15, 167, 65, 78, 162, 18, 233, 33, 131, 149, 72, 35, 147, 200, 36, 50, 137, 76, 34, 147, 12, 14, 34, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 211, 115, 1, 195, 41, 140, 15, 159, 141, 102, 48, 208, 104, 246, 152, 193, 74, 164, 145, 73, 100, 18, 153, 68, 38, 145, 73, 6, 7, 17, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 233, 185, 0, 196, 37, 140, 15, 119, 145, 123, 200, 96, 37, 210, 200, 36, 50, 137, 76, 34, 147, 200, 36, 131, 131, 136, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 244, 92, 0, 197, 41, 140, 15, 167, 65, 78, 34, 147, 232, 6, 185, 193, 74, 164, 145, 73, 100, 18, 153, 68, 38, 145, 73, 6, 7, 17, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 233, 185, 0, 198, 35, 140, 15, 127, 102, 131, 129, 70, 162, 210, 168, 52, 42, 141, 74, 163, 210, 168, 6, 55, 86, 26, 149, 70, 165, 81, 105, 84, 26, 149, 102, 160, 231, 2, 199, 20, 140, 15, 127, 102, 131, 129, 70, 41, 245, 191, 213, 14, 6, 58, 169, 165, 25, 0, 200, 24, 140, 15, 31, 173, 61, 106, 48, 208, 40, 165, 158, 14, 6, 34, 169, 183, 218, 193, 64, 207, 5, 0, 201, 24, 140, 15, 63, 165, 61, 106, 48, 208, 40, 165, 158, 14, 6, 34, 169, 183, 218, 193, 64, 207, 5, 0, 202, 27, 140, 15, 167, 65, 78, 162, 18, 233, 33, 131, 129, 70, 41, 245, 116, 48, 16, 73, 189, 213, 14, 6, 122, 46, 0, 203, 24, 140, 15, 119, 145, 123, 200, 96, 160, 81, 74, 61, 29, 12, 68, 82, 111, 181, 131, 129, 158, 11, 0, 204, 18, 140, 15, 39, 173, 61, 102, 48, 208, 73, 253, 191, 27, 12, 244, 156, 0, 205, 18, 140, 15, 55, 165, 61, 106, 48, 208, 73, 253, 191, 27, 12, 244, 156, 0, 206, 21, 140, 15, 167, 65, 78, 162, 18, 233, 17, 131, 129, 78, 234, 255, 221, 96, 160, 231, 4, 207, 18, 140, 15, 119, 145, 123, 196, 96, 160, 147, 250, 127, 55, 24, 232, 57, 1, 208, 35, 140, 15, 127, 70, 131, 27, 213, 68, 38, 145, 73, 100, 18, 153, 68, 166, 24, 108, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 106, 50, 184, 231, 4, 209, 44, 140, 15, 159, 141, 102, 48, 208, 104, 246, 8, 153, 100, 37, 89, 73, 6, 33, 201, 32, 36, 81, 104, 36, 10, 141, 196, 223, 40, 36, 26, 133, 68, 52, 136, 136, 6, 17, 213, 68, 181, 231, 2, 210, 36, 140, 15, 31, 173, 61, 106, 176, 18, 105, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 166, 17, 169, 6, 123, 110, 0, 211, 36, 140, 15, 63, 165, 61, 106, 176, 18, 105, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 166, 17, 169, 6, 123, 110, 0, 212, 39, 140, 15, 167, 65, 78, 162, 18, 233, 33, 131, 149, 72, 35, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 141, 72, 53, 216, 115, 3, 213, 40, 140, 15, 159, 141, 102, 48, 208, 104, 246, 152, 193, 74, 164, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 70, 164, 26, 236, 185, 1, 214, 36, 140, 15, 119, 145, 123, 200, 96, 37, 210, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 35, 82, 13, 246, 220, 0, 215, 24, 140, 15, 127, 158, 202, 52, 34, 149, 68, 55, 72, 74, 149, 131, 156, 68, 37, 210, 200, 244, 60, 5, 216, 42, 140, 15, 127, 122, 209, 96, 160, 17, 105, 84, 19, 209, 32, 34, 26, 68, 52, 10, 137, 70, 33, 241, 23, 26, 137, 66, 35, 25, 132, 36, 131, 144, 70, 36, 26, 12, 66, 82, 61, 3, 217, 37, 140, 15, 31, 173, 61, 70, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 166, 81, 137, 6, 3, 61, 23, 0, 218, 37, 140, 15, 63, 165, 61, 70, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 166, 81, 137, 6, 3, 61, 23, 0, 219, 39, 140, 15, 167, 65, 78, 162, 18, 233, 101, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 26, 149, 104, 48, 208, 115, 1, 220, 36, 140, 15, 119, 145, 123, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 70, 37, 26, 12, 244, 92, 0, 221, 29, 140, 15, 63, 165, 61, 70, 38, 145, 73, 100, 18, 153, 68, 38, 145, 105, 84, 162, 193, 64, 234, 203, 201, 224, 158, 19, 0, 222, 33, 140, 15, 127, 46, 164, 166, 131, 129, 72, 165, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 165, 25, 12, 68, 82, 123, 26, 0, 223, 34, 140, 15, 127, 102, 131, 156, 68, 37, 242, 111, 84, 138, 65, 74, 51, 82, 105, 100, 18, 153, 68, 38, 81, 132, 36, 10, 137, 70, 50, 200, 51, 3, 224, 29, 140, 15, 127, 176, 214, 61, 126, 48, 200, 154, 138, 6, 3, 141, 74, 34, 147, 200, 36, 50, 141, 74, 52, 24, 232, 185, 0, 225, 29, 140, 15, 127, 10, 165, 123, 146, 193, 32, 107, 42, 26, 12, 52, 42, 137, 76, 34, 147, 200, 52, 42, 209, 96, 160, 231, 2, 226, 33, 140, 15, 127, 184, 114, 144, 147, 168, 68, 122, 244, 96, 144, 53, 21, 13, 6, 26, 149, 68, 38, 145, 73, 100, 26, 149, 104, 48, 208, 115, 1, 227, 35, 140, 15, 127, 176, 72, 51, 200, 72, 44, 52, 154, 61, 124, 48, 200, 154, 138, 6, 3, 141, 74, 34, 147, 200, 36, 50, 141, 74, 52, 24, 232, 185, 0, 228, 29, 140, 15, 127, 66, 145, 123, 210, 193, 32, 107, 42, 26, 12, 52, 42, 137, 76, 34, 147, 200, 52, 42, 209, 96, 160, 231, 2, 229, 34, 140, 15, 127, 244, 32, 39, 145, 73, 116, 131, 60, 126, 48, 200, 154, 138, 6, 3, 141, 74, 34, 147, 200, 36, 50, 141, 74, 52, 24, 232, 185, 0, 230, 31, 140, 15, 127, 62, 156, 12, 82, 131, 132, 76, 34, 147, 136, 6, 3, 141, 66, 38, 145, 73, 100, 18, 157, 98, 183, 24, 228, 185, 0, 231, 19, 140, 15, 127, 190, 28, 12, 52, 74, 169, 223, 106, 7, 3, 157, 212, 210, 12, 232, 29, 140, 15, 127, 176, 214, 61, 197, 96, 160, 81, 73, 100, 18, 153, 68, 38, 25, 28, 68, 164, 182, 218, 193, 64, 207, 5, 0, 233, 29, 140, 15, 127, 10, 165, 123, 154, 193, 64, 163, 146, 200, 36, 50, 137, 76, 50, 56, 136, 72, 109, 181, 131, 129, 158, 11, 0, 234, 33, 140, 15, 127, 184, 114, 144, 147, 168, 68, 122, 248, 96, 160, 81, 73, 100, 18, 153, 68, 38, 25, 28, 68, 164, 182, 218, 193, 64, 207, 5, 0, 235, 29, 140, 15, 127, 66, 145, 123, 218, 193, 64, 163, 146, 200, 36, 50, 137, 76, 50, 56, 136, 72, 109, 181, 131, 129, 158, 11, 0, 236, 16, 140, 15, 127, 176, 214, 61, 197, 32, 234, 63, 29, 228, 185, 1, 237, 16, 140, 15, 127, 10, 165, 123, 154, 65, 212, 127, 58, 200, 115, 3, 238, 20, 140, 15, 127, 184, 114, 144, 147, 168, 68, 122, 248, 32, 234, 63, 29, 228, 185, 1, 239, 16, 140, 15, 127, 66, 145, 123, 218, 65, 212, 127, 58, 200, 115, 3, 240, 36, 140, 15, 127, 168, 68, 55, 72, 42, 7, 57, 137, 86, 55, 88, 137, 52, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 211, 136, 84, 131, 61, 55, 0, 241, 37, 140, 15, 127, 176, 72, 51, 200, 72, 44, 52, 154, 61, 122, 48, 16, 169, 52, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 61, 23, 0, 242, 30, 140, 15, 127, 176, 214, 61, 197, 96, 37, 210, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 35, 82, 13, 246, 220, 0, 243, 30, 140, 15, 127, 10, 165, 123, 154, 193, 74, 164, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 70, 164, 26, 236, 185, 1, 244, 34, 140, 15, 127, 184, 114, 144, 147, 168, 68, 122, 248, 96, 37, 210, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 35, 82, 13, 246, 220, 0, 245, 36, 140, 15, 127, 176, 72, 51, 200, 72, 44, 52, 154, 61, 126, 176, 18, 105, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 166, 17, 169, 6, 123, 110, 0, 246, 30, 140, 15, 127, 66, 145, 123, 218, 193, 74, 164, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 70, 164, 26, 236, 185, 1, 247, 17, 140, 15, 127, 190, 149, 234, 41, 6, 3, 61, 133, 84, 207, 115, 0, 248, 33, 140, 15, 127, 190, 202, 12, 22, 18, 209, 68, 53, 17, 13, 34, 26, 133, 196, 11, 141, 100, 16, 146, 172, 36, 35, 137, 98, 176, 201, 51, 7, 249, 31, 140, 15, 127, 176, 214, 61, 92, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 166, 81, 137, 6, 3, 61, 23, 0, 250, 31, 140, 15, 127, 10, 165, 123, 10, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 70, 37, 26, 12, 244, 92, 0, 251, 35, 140, 15, 127, 184, 114, 144, 147, 168, 68, 122, 176, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 163, 18, 13, 6, 122, 46, 0, 252, 31, 140, 15, 127, 66, 145, 123, 74, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 70, 37, 26, 12, 244, 92, 0, 253, 34, 140, 15, 127, 10, 165, 123, 10, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 70, 37, 26, 12, 164, 46, 53, 131, 129, 6, 254, 32, 140, 15, 127, 58, 169, 167, 131, 129, 72, 165, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 165, 25, 12, 68, 82, 47, 1, 255, 34, 140, 15, 127, 66, 145, 123, 74, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 70, 37, 26, 12, 164, 46, 53, 131, 129, 6, 0, 0, 0, 4, 255, 255, 1, 0, 39, 140, 15, 247, 193, 64, 15, 31, 172, 68, 26, 153, 68, 38, 145, 73, 100, 18, 153, 100, 112, 16, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 158, 11, 0, 1, 1, 31, 140, 15, 127, 78, 6, 3, 61, 122, 48, 200, 154, 138, 6, 3, 141, 74, 34, 147, 200, 36, 50, 141, 74, 52, 24, 232, 185, 0, 1, 2, 41, 140, 15, 31, 137, 76, 162, 27, 228, 65, 131, 149, 72, 35, 147, 200, 36, 50, 137, 76, 34, 147, 12, 14, 34, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 211, 115, 1, 1, 3, 34, 140, 15, 127, 74, 137, 76, 162, 27, 228, 241, 131, 65, 214, 84, 52, 24, 104, 84, 18, 153, 68, 38, 145, 105, 84, 162, 193, 64, 207, 5, 0, 1, 4, 39, 140, 15, 127, 102, 131, 149, 72, 35, 147, 200, 36, 50, 137, 76, 34, 147, 12, 14, 34, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 83, 14, 165, 218, 65, 24, 1, 5, 30, 140, 15, 127, 62, 28, 12, 178, 166, 162, 193, 64, 163, 146, 200, 36, 50, 137, 76, 163, 18, 13, 6, 74, 83, 237, 32, 12, 1, 6, 21, 140, 15, 63, 165, 61, 106, 48, 208, 40, 165, 254, 183, 218, 193, 64, 207, 5, 0, 1, 7, 21, 140, 15, 127, 10, 165, 123, 154, 193, 64, 163, 148, 250, 173, 118, 48, 208, 115, 1, 1, 8, 24, 140, 15, 167, 65, 78, 162, 18, 233, 33, 131, 129, 70, 41, 245, 191, 213, 14, 6, 122, 46, 0, 1, 9, 25, 140, 15, 127, 184, 114, 144, 147, 168, 68, 122, 248, 96, 160, 81, 74, 253, 86, 59, 24, 232, 185, 0, 1, 10, 21, 140, 15, 127, 136, 84, 143, 26, 12, 52, 74, 169, 255, 173, 118, 48, 208, 115, 1, 1, 11, 21, 140, 15, 127, 90, 169, 158, 124, 48, 208, 40, 165, 126, 171, 29, 12, 244, 92, 0, 1, 12, 24, 140, 15, 23, 145, 74, 162, 27, 228, 65, 131, 129, 70, 41, 245, 191, 213, 14, 6, 122, 46, 0, 1, 13, 25, 140, 15, 127, 172, 72, 37, 209, 13, 146, 122, 146, 193, 64, 163, 148, 250, 173, 118, 48, 208, 115, 1, 1, 14, 42, 140, 15, 23, 145, 74, 162, 27, 228, 33, 131, 129, 72, 165, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 165, 25, 12, 244, 220, 0, 1, 15, 36, 140, 15, 23, 145, 74, 162, 27, 228, 161, 30, 13, 6, 26, 149, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 26, 149, 104, 48, 208, 115, 1, 1, 16, 36, 140, 15, 127, 70, 131, 27, 213, 68, 38, 145, 73, 100, 18, 153, 68, 166, 24, 108, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 106, 50, 184, 231, 4, 1, 17, 34, 140, 15, 127, 198, 186, 193, 82, 42, 26, 12, 52, 42, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 52, 42, 209, 96, 160, 231, 2, 1, 18, 25, 140, 15, 247, 193, 64, 15, 31, 12, 52, 74, 169, 167, 131, 129, 72, 234, 173, 118, 48, 208, 115, 1, 1, 19, 30, 140, 15, 127, 78, 6, 3, 61, 124, 48, 208, 168, 36, 50, 137, 76, 34, 147, 12, 14, 34, 82, 91, 237, 96, 160, 231, 2, 1, 20, 28, 140, 15, 31, 137, 76, 162, 27, 228, 65, 131, 129, 70, 41, 245, 116, 48, 16, 73, 189, 213, 14, 6, 122, 46, 0, 1, 21, 33, 140, 15, 127, 74, 137, 76, 162, 27, 228, 41, 6, 3, 141, 74, 34, 147, 200, 36, 50, 201, 224, 32, 34, 181, 213, 14, 6, 122, 46, 0, 1, 22, 25, 140, 15, 127, 136, 84, 143, 26, 12, 52, 74, 169, 167, 131, 129, 72, 234, 173, 118, 48, 208, 115, 1, 1, 23, 29, 140, 15, 127, 90, 169, 158, 124, 48, 208, 168, 36, 50, 137, 76, 34, 147, 12, 14, 34, 82, 91, 237, 96, 160, 231, 2, 1, 24, 25, 140, 15, 127, 102, 131, 129, 70, 41, 245, 116, 48, 16, 73, 189, 213, 14, 6, 74, 83, 237, 32, 12, 1, 25, 30, 140, 15, 127, 190, 28, 12, 52, 42, 137, 76, 34, 147, 200, 36, 131, 131, 136, 212, 86, 59, 24, 40, 77, 181, 131, 48, 0, 1, 26, 28, 140, 15, 23, 145, 74, 162, 27, 228, 65, 131, 129, 70, 41, 245, 116, 48, 16, 73, 189, 213, 14, 6, 122, 46, 0, 1, 27, 34, 140, 15, 127, 172, 72, 37, 209, 13, 146, 122, 146, 193, 64, 163, 146, 200, 36, 50, 137, 76, 50, 56, 136, 72, 109, 181, 131, 129, 158, 11, 0, 1, 28, 35, 140, 15, 167, 65, 78, 162, 18, 233, 33, 131, 129, 70, 41, 245, 205, 64, 34, 147, 200, 36, 50, 137, 76, 34, 211, 168, 68, 131, 129, 158, 11, 0, 1, 29, 42, 140, 15, 127, 184, 114, 144, 147, 168, 68, 122, 248, 96, 160, 81, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 166, 81, 137, 6, 131, 168, 86, 170, 20, 13, 6, 25, 0, 1, 30, 35, 140, 15, 31, 137, 76, 162, 27, 228, 65, 131, 129, 70, 41, 245, 205, 64, 34, 147, 200, 36, 50, 137, 76, 34, 211, 168, 68, 131, 129, 158, 11, 0, 1, 31, 41, 140, 15, 127, 74, 137, 76, 162, 27, 228, 41, 6, 3, 141, 74, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 141, 74, 52, 24, 68, 181, 82, 165, 104, 48, 200, 0, 1, 32, 32, 140, 15, 127, 136, 84, 143, 26, 12, 52, 74, 169, 111, 6, 18, 153, 68, 38, 145, 73, 100, 18, 153, 70, 37, 26, 12, 244, 92, 0, 1, 33, 38, 140, 15, 127, 90, 169, 158, 124, 48, 208, 168, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 211, 168, 68, 131, 65, 84, 43, 85, 138, 6, 131, 12, 0, 1, 34, 31, 140, 15, 127, 102, 131, 129, 70, 41, 245, 205, 64, 34, 147, 200, 36, 50, 137, 76, 34, 211, 168, 68, 131, 129, 78, 106, 105, 6, 1, 35, 39, 140, 15, 127, 140, 212, 210, 158, 102, 48, 208, 168, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 211, 168, 68, 131, 65, 84, 43, 85, 138, 6, 131, 12, 0, 1, 36, 40, 140, 15, 167, 65, 78, 162, 18, 233, 101, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 25, 28, 68, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 166, 231, 2, 1, 37, 35, 140, 15, 167, 65, 78, 162, 18, 233, 165, 78, 7, 3, 145, 74, 35, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 211, 115, 1, 1, 38, 37, 140, 15, 127, 70, 50, 137, 76, 49, 120, 33, 147, 200, 36, 50, 137, 76, 50, 56, 136, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 207, 5, 0, 1, 39, 34, 140, 15, 127, 70, 202, 193, 78, 42, 29, 12, 68, 42, 141, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 207, 5, 0, 1, 40, 23, 140, 15, 159, 141, 102, 48, 208, 104, 246, 144, 193, 64, 39, 245, 255, 110, 48, 208, 115, 2, 1, 41, 23, 140, 15, 127, 176, 72, 51, 200, 72, 44, 52, 154, 61, 126, 16, 245, 159, 14, 242, 220, 0, 1, 42, 20, 140, 15, 247, 193, 64, 143, 30, 12, 116, 82, 255, 239, 6, 3, 61, 39, 0, 1, 43, 18, 140, 15, 127, 78, 6, 3, 61, 124, 16, 245, 159, 14, 242, 220, 0, 1, 44, 22, 140, 15, 31, 137, 76, 162, 27, 228, 49, 131, 129, 78, 234, 255, 221, 96, 160, 231, 4, 1, 45, 21, 140, 15, 127, 74, 137, 76, 162, 27, 228, 41, 6, 81, 255, 233, 32, 207, 13, 0, 1, 46, 20, 140, 15, 127, 86, 131, 129, 78, 234, 255, 221, 96, 160, 52, 213, 14, 210, 0, 1, 47, 20, 140, 15, 127, 134, 82, 61, 201, 32, 234, 63, 29, 36, 77, 181, 131, 56, 0, 1, 48, 20, 140, 15, 127, 136, 84, 15, 26, 12, 116, 82, 255, 239, 6, 3, 61, 39, 0, 1, 49, 15, 140, 15, 127, 190, 28, 68, 253, 167, 131, 60, 55, 0, 1, 50, 39, 140, 15, 127, 54, 131, 204, 32, 162, 210, 168, 52, 42, 141, 74, 163, 210, 168, 52, 42, 141, 74, 163, 210, 168, 52, 42, 141, 74, 35, 154, 12, 18, 131, 60, 55, 0, 1, 51, 22, 140, 15, 127, 86, 34, 247, 216, 65, 200, 255, 255, 209, 32, 34, 117, 169, 27, 132, 0, 1, 52, 22, 140, 15, 167, 65, 78, 162, 18, 233, 17, 131, 129, 78, 234, 255, 114, 54, 208, 115, 6, 1, 53, 21, 140, 15, 127, 184, 114, 144, 147, 168, 68, 122, 10, 169, 255, 47, 117, 131, 28, 0, 1, 54, 36, 140, 15, 127, 70, 50, 137, 76, 34, 147, 200, 36, 50, 137, 74, 35, 18, 13, 6, 41, 145, 149, 70, 38, 145, 73, 100, 18, 153, 68, 102, 106, 105, 7, 1, 55, 28, 140, 15, 127, 86, 82, 143, 188, 81, 73, 100, 3, 157, 66, 39, 145, 105, 84, 34, 43, 141, 74, 39, 181, 52, 3, 1, 56, 25, 140, 15, 127, 62, 20, 121, 163, 146, 200, 20, 186, 65, 80, 161, 147, 200, 52, 42, 145, 123, 78, 0, 1, 57, 17, 140, 15, 63, 165, 61, 70, 234, 255, 173, 118, 48, 208, 115, 1, 1, 58, 16, 140, 15, 63, 165, 61, 76, 234, 255, 233, 129, 158, 19, 0, 1, 59, 17, 140, 15, 127, 70, 82, 255, 111, 181, 131, 129, 78, 106, 105, 6, 1, 60, 16, 140, 15, 127, 118, 82, 255, 79, 15, 132, 82, 75, 51, 0, 1, 61, 21, 140, 15, 23, 145, 74, 162, 27, 228, 33, 82, 255, 111, 181, 131, 129, 158, 11, 0, 1, 62, 19, 140, 15, 23, 145, 74, 162, 27, 228, 81, 82, 255, 79, 15, 244, 156, 0, 1, 63, 17, 140, 15, 127, 70, 82, 127, 228, 169, 91, 237, 96, 160, 231, 2, 1, 64, 18, 140, 15, 127, 118, 82, 127, 34, 147, 200, 164, 78, 15, 244, 156, 0, 1, 65, 21, 140, 15, 127, 70, 82, 47, 116, 131, 224, 82, 121, 234, 86, 59, 24, 232, 185, 0, 1, 66, 20, 140, 15, 127, 118, 82, 47, 116, 131, 224, 82, 121, 234, 244, 64, 207, 9, 0, 1, 67, 41, 140, 15, 55, 165, 61, 72, 38, 89, 73, 86, 146, 65, 72, 50, 8, 73, 20, 26, 137, 66, 35, 241, 55, 10, 137, 70, 33, 17, 13, 34, 162, 65, 68, 53, 81, 237, 185, 0, 1, 68, 32, 140, 15, 127, 10, 165, 123, 138, 193, 64, 164, 210, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 244, 92, 0, 1, 69, 41, 140, 15, 127, 70, 50, 201, 74, 178, 146, 12, 66, 146, 65, 72, 162, 208, 72, 20, 26, 137, 191, 81, 72, 52, 10, 137, 104, 16, 17, 13, 34, 170, 137, 106, 38, 181, 180, 3, 1, 70, 30, 140, 15, 127, 190, 27, 12, 68, 42, 141, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 204, 212, 210, 14, 1, 71, 45, 140, 15, 23, 145, 74, 162, 27, 228, 33, 50, 201, 74, 178, 146, 12, 66, 146, 65, 72, 162, 208, 72, 20, 26, 137, 191, 81, 72, 52, 10, 137, 104, 16, 17, 13, 34, 170, 137, 106, 207, 5, 0, 1, 72, 36, 140, 15, 127, 172, 72, 37, 209, 13, 146, 122, 252, 96, 32, 82, 105, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 122, 46, 0, 1, 73, 32, 140, 15, 119, 169, 165, 61, 213, 96, 32, 82, 105, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 122, 46, 0, 1, 74, 41, 140, 15, 127, 70, 50, 201, 74, 178, 146, 12, 66, 146, 65, 72, 162, 208, 72, 20, 26, 137, 191, 81, 72, 52, 10, 137, 104, 16, 17, 13, 34, 170, 137, 106, 106, 41, 220, 3, 1, 75, 31, 140, 15, 127, 190, 27, 12, 68, 42, 141, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 106, 41, 220, 3, 1, 76, 38, 140, 15, 247, 193, 64, 15, 31, 172, 68, 26, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 105, 68, 170, 193, 158, 27, 0, 1, 77, 32, 140, 15, 127, 78, 6, 3, 61, 124, 176, 18, 105, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 166, 17, 169, 6, 123, 110, 0, 1, 78, 40, 140, 15, 31, 137, 76, 162, 27, 228, 65, 131, 149, 72, 35, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 141, 72, 53, 216, 115, 3, 1, 79, 34, 140, 15, 127, 74, 137, 76, 162, 27, 228, 41, 6, 43, 145, 70, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 26, 145, 106, 176, 231, 6, 1, 80, 40, 140, 15, 47, 137, 74, 162, 146, 232, 49, 131, 149, 72, 35, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 141, 72, 53, 216, 115, 3, 1, 81, 36, 140, 15, 127, 184, 68, 37, 81, 73, 84, 18, 61, 197, 96, 37, 210, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 35, 82, 13, 246, 220, 0, 1, 82, 37, 140, 15, 127, 102, 131, 129, 70, 162, 210, 168, 52, 42, 141, 74, 163, 210, 168, 52, 131, 140, 149, 70, 165, 81, 105, 84, 26, 153, 68, 55, 24, 232, 185, 0, 1, 83, 29, 140, 15, 127, 190, 92, 12, 50, 138, 133, 196, 159, 12, 38, 102, 18, 153, 68, 38, 209, 41, 118, 139, 65, 158, 11, 0, 1, 84, 39, 140, 15, 63, 165, 61, 102, 48, 16, 169, 52, 50, 137, 76, 34, 147, 200, 36, 42, 205, 96, 32, 82, 105, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 158, 11, 0, 1, 85, 19, 140, 15, 127, 10, 165, 123, 154, 193, 64, 163, 146, 72, 253, 123, 46, 1, 1, 86, 38, 140, 15, 127, 70, 131, 129, 72, 165, 145, 73, 100, 18, 153, 68, 38, 81, 105, 6, 3, 145, 74, 35, 147, 200, 36, 50, 137, 76, 34, 147, 200, 76, 45, 237, 0, 1, 87, 18, 140, 15, 127, 190, 28, 12, 52, 42, 137, 212, 63, 151, 90, 218, 1, 1, 88, 42, 140, 15, 23, 145, 74, 162, 27, 228, 33, 131, 129, 72, 165, 145, 73, 100, 18, 153, 68, 38, 81, 105, 6, 3, 145, 74, 35, 147, 200, 36, 50, 137, 76, 34, 147, 200, 244, 92, 0, 1, 89, 23, 140, 15, 127, 172, 72, 37, 209, 13, 146, 122, 146, 193, 64, 163, 146, 72, 253, 123, 46, 1, 1, 90, 24, 140, 15, 63, 165, 61, 106, 48, 208, 40, 165, 110, 181, 131, 173, 169, 75, 205, 96, 160, 231, 6, 1, 91, 22, 140, 15, 127, 10, 165, 123, 146, 193, 137, 212, 237, 96, 160, 149, 58, 25, 220, 115, 2, 1, 92, 28, 140, 15, 167, 65, 78, 162, 18, 233, 33, 131, 129, 70, 41, 117, 171, 29, 108, 77, 93, 106, 6, 3, 61, 55, 0, 1, 93, 26, 140, 15, 127, 184, 114, 144, 147, 168, 68, 122, 244, 224, 68, 234, 118, 48, 208, 74, 157, 12, 238, 57, 1, 1, 94, 24, 140, 15, 127, 102, 131, 129, 70, 41, 117, 171, 29, 108, 77, 93, 106, 6, 3, 161, 212, 210, 14, 1, 95, 21, 140, 15, 127, 62, 28, 156, 72, 221, 14, 6, 90, 169, 147, 193, 157, 212, 210, 14, 1, 96, 28, 140, 15, 23, 145, 74, 162, 27, 228, 65, 131, 129, 70, 41, 117, 171, 29, 108, 77, 93, 106, 6, 3, 61, 55, 0, 1, 97, 26, 140, 15, 127, 172, 72, 37, 209, 13, 146, 122, 138, 193, 137, 212, 237, 96, 160, 149, 58, 25, 220, 115, 2, 1, 98, 17, 140, 15, 127, 70, 131, 131, 152, 212, 255, 183, 82, 75, 51, 0, 1, 99, 20, 140, 15, 127, 118, 82, 135, 131, 65, 78, 234, 79, 15, 132, 82, 75, 51, 0, 1, 100, 20, 140, 15, 23, 145, 74, 162, 27, 228, 33, 131, 131, 152, 212, 255, 247, 92, 1, 1, 101, 23, 140, 15, 23, 145, 74, 162, 27, 228, 81, 82, 135, 131, 65, 78, 234, 79, 15, 244, 156, 0, 1, 102, 18, 140, 15, 127, 70, 131, 131, 152, 212, 135, 131, 161, 212, 239, 185, 2, 1, 103, 22, 140, 15, 127, 118, 82, 135, 131, 65, 78, 42, 28, 12, 114, 82, 79, 15, 244, 156, 0, 1, 104, 42, 140, 15, 159, 141, 102, 48, 208, 104, 246, 8, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 70, 37, 26, 12, 244, 92, 0, 1, 105, 38, 140, 15, 127, 176, 72, 51, 200, 72, 44, 52, 154, 61, 90, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 166, 81, 137, 6, 3, 61, 23, 0, 1, 106, 39, 140, 15, 247, 193, 64, 15, 150, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 105, 84, 162, 193, 64, 207, 5, 0, 1, 107, 33, 140, 15, 127, 78, 6, 3, 61, 88, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 166, 81, 137, 6, 3, 61, 23, 0, 1, 108, 41, 140, 15, 31, 137, 76, 162, 27, 228, 33, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 141, 74, 52, 24, 232, 185, 0, 1, 109, 35, 140, 15, 127, 74, 137, 76, 162, 27, 228, 225, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 141, 74, 52, 24, 232, 185, 0, 1, 110, 42, 140, 15, 167, 65, 78, 34, 147, 232, 6, 41, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 70, 37, 26, 12, 244, 92, 0, 1, 111, 37, 140, 15, 127, 244, 32, 39, 145, 73, 116, 131, 60, 92, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 166, 81, 137, 6, 3, 61, 23, 0, 1, 112, 41, 140, 15, 47, 137, 74, 162, 146, 232, 17, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 141, 74, 52, 24, 232, 185, 0, 1, 113, 37, 140, 15, 127, 184, 68, 37, 81, 73, 84, 18, 61, 92, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 166, 81, 137, 6, 3, 61, 23, 0, 1, 114, 39, 140, 15, 127, 70, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 141, 74, 52, 24, 40, 77, 181, 131, 48, 0, 1, 115, 32, 140, 15, 127, 190, 147, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 105, 84, 162, 193, 64, 105, 170, 29, 132, 1, 1, 116, 42, 140, 15, 167, 65, 78, 162, 18, 233, 101, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 196, 139, 65, 66, 50, 136, 12, 34, 163, 137, 76, 207, 5, 0, 1, 117, 29, 140, 15, 127, 184, 114, 144, 147, 168, 68, 122, 176, 76, 34, 147, 200, 36, 254, 255, 55, 10, 137, 72, 50, 200, 115, 1, 1, 118, 32, 140, 15, 167, 65, 78, 162, 18, 233, 101, 18, 153, 68, 38, 145, 73, 100, 18, 153, 70, 37, 26, 12, 164, 190, 156, 12, 238, 57, 1, 1, 119, 39, 140, 15, 127, 184, 114, 144, 147, 168, 68, 122, 176, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 163, 18, 13, 6, 82, 151, 154, 193, 64, 3, 1, 120, 29, 140, 15, 119, 145, 123, 153, 68, 38, 145, 73, 100, 18, 153, 68, 166, 81, 137, 6, 3, 169, 47, 39, 131, 123, 78, 0, 1, 121, 20, 140, 15, 63, 165, 61, 102, 112, 16, 117, 233, 159, 154, 14, 14, 242, 92, 0, 1, 122, 20, 140, 15, 127, 10, 165, 123, 138, 193, 65, 84, 233, 159, 14, 14, 242, 92, 0, 1, 123, 21, 140, 15, 127, 136, 84, 143, 25, 28, 68, 93, 250, 167, 166, 131, 131, 60, 23, 0, 1, 124, 20, 140, 15, 127, 90, 169, 158, 120, 112, 16, 85, 250, 167, 131, 131, 60, 23, 0, 1, 125, 23, 140, 15, 23, 145, 74, 162, 27, 228, 33, 131, 131, 168, 75, 255, 212, 116, 112, 144, 231, 2, 1, 126, 24, 140, 15, 127, 172, 72, 37, 209, 13, 146, 122, 252, 224, 32, 170, 244, 79, 7, 7, 121, 46, 0, 1, 127, 17, 140, 15, 127, 134, 3, 217, 82, 42, 28, 68, 253, 239, 57, 3, 1, 205, 41, 140, 15, 23, 145, 74, 162, 27, 228, 65, 131, 149, 72, 35, 147, 200, 36, 50, 137, 76, 34, 147, 12, 14, 34, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 211, 115, 1, 1, 206, 34, 140, 15, 127, 172, 72, 37, 209, 13, 146, 122, 138, 193, 32, 107, 42, 26, 12, 52, 42, 137, 76, 34, 147, 200, 52, 42, 209, 96, 160, 231, 2, 1, 207, 22, 140, 15, 23, 145, 74, 162, 27, 228, 49, 131, 129, 78, 234, 255, 221, 96, 160, 231, 4, 1, 208, 21, 140, 15, 127, 172, 72, 37, 209, 13, 146, 122, 146, 65, 212, 127, 58, 200, 115, 3, 1, 209, 40, 140, 15, 23, 145, 74, 162, 27, 228, 65, 131, 149, 72, 35, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 141, 72, 53, 216, 115, 3, 1, 210, 35, 140, 15, 127, 172, 72, 37, 209, 13, 146, 122, 146, 193, 74, 164, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 70, 164, 26, 236, 185, 1, 1, 211, 41, 140, 15, 23, 145, 74, 162, 27, 228, 33, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 141, 74, 52, 24, 232, 185, 0, 1, 212, 36, 140, 15, 127, 172, 72, 37, 209, 13, 146, 122, 188, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 163, 18, 13, 6, 122, 46, 0, 1, 226, 39, 140, 15, 247, 193, 64, 15, 31, 12, 52, 18, 149, 70, 165, 81, 105, 84, 26, 149, 70, 53, 184, 177, 210, 168, 52, 42, 141, 74, 163, 210, 168, 52, 3, 61, 23, 0, 1, 227, 35, 140, 15, 127, 78, 6, 3, 61, 122, 50, 72, 13, 18, 50, 137, 76, 34, 26, 12, 52, 10, 153, 68, 38, 145, 73, 116, 138, 221, 98, 144, 231, 2, 1, 230, 35, 140, 15, 23, 145, 74, 162, 27, 228, 65, 131, 129, 70, 41, 245, 205, 64, 34, 147, 200, 36, 50, 137, 76, 34, 211, 168, 68, 131, 129, 158, 11, 0, 1, 231, 42, 140, 15, 127, 172, 72, 37, 209, 13, 146, 122, 146, 193, 64, 163, 146, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 163, 18, 13, 6, 81, 173, 84, 41, 26, 12, 50, 0, 1, 232, 40, 140, 15, 23, 145, 74, 162, 27, 228, 33, 50, 137, 76, 34, 147, 200, 36, 50, 137, 74, 35, 18, 13, 6, 41, 145, 149, 70, 38, 145, 73, 100, 18, 153, 68, 166, 231, 2, 1, 233, 32, 140, 15, 23, 145, 74, 162, 27, 228, 49, 82, 143, 188, 81, 73, 100, 3, 157, 66, 39, 145, 105, 84, 34, 43, 141, 74, 207, 5, 0, 1, 234, 38, 140, 15, 127, 102, 131, 149, 72, 35, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 141, 72, 53, 24, 42, 165, 218, 65, 30, 1, 235, 32, 140, 15, 127, 190, 28, 172, 68, 26, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 105, 68, 170, 193, 80, 41, 213, 14, 242, 0, 1, 236, 41, 140, 15, 247, 193, 64, 15, 31, 172, 68, 26, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 105, 68, 170, 193, 80, 41, 213, 14, 242, 0, 1, 237, 35, 140, 15, 127, 78, 6, 3, 61, 124, 176, 18, 105, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 166, 17, 169, 6, 67, 165, 84, 59, 200, 3, 1, 240, 21, 140, 15, 127, 172, 72, 37, 209, 13, 146, 122, 34, 169, 255, 47, 117, 131, 28, 0, 1, 244, 31, 140, 15, 63, 165, 61, 106, 48, 208, 40, 165, 190, 25, 72, 100, 18, 153, 68, 38, 145, 73, 100, 26, 149, 104, 48, 208, 115, 1, 1, 245, 38, 140, 15, 127, 10, 165, 123, 154, 193, 64, 163, 146, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 163, 18, 13, 6, 81, 173, 84, 41, 26, 12, 50, 0, 1, 252, 38, 140, 15, 63, 165, 61, 106, 48, 208, 72, 84, 26, 149, 70, 165, 81, 105, 84, 26, 213, 224, 198, 74, 163, 210, 168, 52, 42, 141, 74, 163, 210, 12, 244, 92, 0, 1, 253, 34, 140, 15, 127, 10, 165, 123, 146, 131, 212, 32, 33, 147, 200, 36, 162, 193, 64, 163, 144, 73, 100, 18, 153, 68, 167, 216, 45, 6, 121, 46, 0, 1, 254, 44, 140, 15, 63, 165, 185, 104, 48, 208, 136, 52, 170, 137, 104, 16, 17, 13, 34, 26, 133, 68, 163, 144, 248, 11, 141, 68, 161, 145, 12, 66, 146, 65, 72, 35, 18, 13, 6, 33, 169, 158, 1, 1, 255, 37, 140, 15, 127, 10, 165, 123, 120, 102, 176, 144, 136, 38, 170, 137, 104, 16, 209, 40, 36, 94, 104, 36, 131, 144, 100, 37, 25, 73, 20, 131, 77, 158, 57, 0, 2, 0, 41, 140, 15, 15, 137, 78, 162, 147, 232, 49, 131, 149, 72, 35, 147, 200, 36, 50, 137, 76, 34, 147, 12, 14, 34, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 211, 115, 1, 2, 1, 35, 140, 15, 127, 168, 68, 39, 209, 73, 116, 18, 61, 122, 48, 200, 154, 138, 6, 3, 141, 74, 34, 147, 200, 36, 50, 141, 74, 52, 24, 232, 185, 0, 2, 2, 41, 140, 15, 167, 65, 78, 34, 147, 232, 49, 131, 149, 72, 35, 147, 200, 36, 50, 137, 76, 34, 147, 12, 14, 34, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 211, 115, 1, 2, 3, 34, 140, 15, 127, 210, 65, 78, 34, 147, 232, 225, 131, 65, 214, 84, 52, 24, 104, 84, 18, 153, 68, 38, 145, 105, 84, 162, 193, 64, 207, 5, 0, 2, 4, 28, 140, 15, 15, 137, 78, 162, 147, 232, 49, 131, 129, 70, 41, 245, 116, 48, 16, 73, 189, 213, 14, 6, 122, 46, 0, 2, 5, 34, 140, 15, 127, 168, 68, 39, 209, 73, 116, 18, 61, 124, 48, 208, 168, 36, 50, 137, 76, 34, 147, 12, 14, 34, 82, 91, 237, 96, 160, 231, 2, 2, 6, 28, 140, 15, 167, 65, 78, 34, 147, 232, 49, 131, 129, 70, 41, 245, 116, 48, 16, 73, 189, 213, 14, 6, 122, 46, 0, 2, 7, 33, 140, 15, 127, 210, 65, 78, 34, 147, 232, 241, 131, 129, 70, 37, 145, 73, 100, 18, 153, 100, 112, 16, 145, 218, 106, 7, 3, 61, 23, 0, 2, 8, 22, 140, 15, 15, 137, 78, 162, 147, 232, 33, 131, 129, 78, 234, 255, 221, 96, 160, 231, 4, 2, 9, 22, 140, 15, 127, 168, 68, 39, 209, 73, 116, 18, 61, 124, 16, 245, 159, 14, 242, 220, 0, 2, 10, 22, 140, 15, 167, 65, 78, 34, 147, 232, 33, 131, 129, 78, 234, 255, 221, 96, 160, 231, 4, 2, 11, 20, 140, 15, 127, 210, 65, 78, 34, 147, 232, 241, 131, 168, 255, 116, 144, 231, 6, 2, 12, 40, 140, 15, 15, 137, 78, 162, 147, 232, 49, 131, 149, 72, 35, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 141, 72, 53, 216, 115, 3, 2, 13, 36, 140, 15, 127, 168, 68, 39, 209, 73, 116, 18, 61, 124, 176, 18, 105, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 166, 17, 169, 6, 123, 110, 0, 2, 14, 40, 140, 15, 167, 65, 78, 34, 147, 232, 49, 131, 149, 72, 35, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 141, 72, 53, 216, 115, 3, 2, 15, 34, 140, 15, 127, 210, 65, 78, 34, 147, 232, 241, 131, 149, 72, 35, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 141, 72, 53, 216, 115, 3, 2, 16, 42, 140, 15, 15, 137, 78, 162, 147, 232, 17, 131, 129, 72, 165, 145, 73, 100, 18, 153, 68, 38, 81, 105, 6, 3, 145, 74, 35, 147, 200, 36, 50, 137, 76, 34, 147, 200, 244, 92, 0, 2, 17, 24, 140, 15, 127, 168, 68, 39, 209, 73, 116, 18, 61, 124, 48, 208, 168, 36, 82, 255, 158, 75, 0, 2, 18, 42, 140, 15, 167, 65, 78, 34, 147, 232, 17, 131, 129, 72, 165, 145, 73, 100, 18, 153, 68, 38, 81, 105, 6, 3, 145, 74, 35, 147, 200, 36, 50, 137, 76, 34, 147, 200, 244, 92, 0, 2, 19, 22, 140, 15, 127, 210, 65, 78, 34, 147, 232, 241, 131, 129, 70, 37, 145, 250, 247, 92, 2, 2, 20, 41, 140, 15, 15, 137, 78, 162, 147, 232, 17, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 141, 74, 52, 24, 232, 185, 0, 2, 21, 37, 140, 15, 127, 168, 68, 39, 209, 73, 116, 18, 61, 88, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 166, 81, 137, 6, 3, 61, 23, 0, 2, 22, 41, 140, 15, 167, 65, 78, 34, 147, 232, 17, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 141, 74, 52, 24, 232, 185, 0, 2, 23, 35, 140, 15, 127, 210, 65, 78, 34, 147, 232, 209, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 200, 36, 50, 141, 74, 52, 24, 232, 185, 0, 2, 30, 41, 140, 15, 23, 145, 74, 162, 27, 228, 33, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 147, 12, 14, 34, 50, 137, 76, 34, 147, 200, 36, 50, 137, 76, 34, 211, 115, 1, 2, 31, 36, 140, 15, 23, 145, 74, 162, 27, 228, 33, 82, 167, 131, 129, 72, 165, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 233, 185, 0, 2, 38, 39, 140, 15, 127, 136, 84, 143, 26, 172, 68, 26, 153, 68, 38, 145, 73, 100, 18, 153, 100, 112, 16, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 158, 11, 0, 2, 39, 30, 140, 15, 127, 90, 169, 158, 122, 48, 200, 154, 138, 6, 3, 141, 74, 34, 147, 200, 36, 50, 141, 74, 52, 24, 232, 185, 0, 2, 40, 25, 140, 15, 127, 102, 131, 129, 70, 41, 245, 116, 48, 16, 73, 189, 213, 14, 6, 58, 169, 165, 25, 0, 2, 41, 29, 140, 15, 127, 190, 28, 12, 52, 42, 137, 76, 34, 147, 200, 36, 131, 131, 136, 212, 86, 59, 24, 232, 164, 150, 102, 0, 2, 46, 38, 140, 15, 127, 136, 84, 143, 26, 172, 68, 26, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 38, 145, 105, 68, 170, 193, 158, 27, 0, 2, 47, 31, 140, 15, 127, 90, 169, 158, 124, 176, 18, 105, 100, 18, 153, 68, 38, 145, 73, 100, 18, 153, 68, 166, 17, 169, 6, 123, 110, 0, 32, 172, 30, 140, 15, 127, 134, 131, 156, 68, 37, 114, 35, 21, 14, 6, 50, 169, 112, 48, 144, 73, 181, 34, 87, 18, 221, 32, 207, 12, 0, 0]; + +// src/font/types.ts +class MonoDisplayFont { + FONT_DATA; + constructor(font_data) { + this.FONT_DATA = new Uint8Array(font_data.length); + for (let i = 0;i < font_data.length; i++) { + this.FONT_DATA[i] = font_data[i] || 0; + } + } +} +// src/font/u8g2.ts +var HDR_SIZE = 23; + +class BitReader { + data; + ptr; + bitPos; + constructor(data, startByte) { + this.data = data; + this.ptr = startByte; + this.bitPos = 0; + } + read(cnt) { + let val = this.data[this.ptr] >> this.bitPos & 255; + const newPos = this.bitPos + cnt; + if (newPos >= 8) { + this.ptr++; + val |= this.data[this.ptr] << 8 - this.bitPos; + this.bitPos = newPos - 8; + } else { + this.bitPos = newPos; + } + return val & (1 << cnt) - 1; + } + readSigned(cnt) { + return this.read(cnt) - (1 << cnt - 1); + } +} +function readU16BE(data, off) { + return (data[off] << 8 | data[off + 1]) >>> 0; +} +function parseHeader(font) { + return { + m0: font[2], + m1: font[3], + bitsPerW: font[4], + bitsPerH: font[5], + bitsPerX: font[6], + bitsPerY: font[7], + bitsPerDx: font[8], + ascentA: font[13], + startUpper: HDR_SIZE + readU16BE(font, 17), + startLower: HDR_SIZE + readU16BE(font, 19), + startUnicode: HDR_SIZE + readU16BE(font, 21) + }; +} +function decodeRLE(br, w, h, m0, m1) { + const total = w * h; + const bits = new Uint8Array(total); + let filled = 0; + while (filled < total) { + const zeros = br.read(m0); + const ones = br.read(m1); + let reps = 1; + while (br.read(1) === 1) + reps++; + for (let r = 0;r < reps && filled < total; r++) { + for (let p = 0;p < zeros && filled < total; p++) + bits[filled++] = 0; + for (let p = 0;p < ones && filled < total; p++) + bits[filled++] = 1; + } + } + return bits; +} +function decodeGlyphAt(font, byteOffset, hdr) { + const br = new BitReader(font, byteOffset); + const w = br.read(hdr.bitsPerW); + const h = br.read(hdr.bitsPerH); + br.readSigned(hdr.bitsPerX); + const yB = br.readSigned(hdr.bitsPerY); + const dx = br.readSigned(hdr.bitsPerDx); + if (w === 0 || h === 0) { + return { cols: new Uint8Array(1), w: 1, h: 0, dx: Math.max(dx, 1), yOff: 0 }; + } + const bits = decodeRLE(br, w, h, hdr.m0, hdr.m1); + const cols = new Uint32Array(w); + for (let row = 0;row < h; row++) { + for (let col = 0;col < w; col++) { + if (bits[row * w + col]) + cols[col] |= 1 << row; + } + } + const yOff = hdr.ascentA - yB - h; + return { cols, w, h, dx, yOff }; +} +function u8g2Glyph(cp, font) { + if (font.length < HDR_SIZE) + return null; + const hdr = parseHeader(font); + try { + if (cp >= 256) { + const tableBase = hdr.startUnicode; + if (tableBase + 4 > font.length) + return null; + let glyphStart = tableBase; + let pos = tableBase; + while (pos + 4 <= font.length) { + const delta = readU16BE(font, pos); + const lastEncoding = readU16BE(font, pos + 2); + pos += 4; + if (lastEncoding === 65535) { + glyphStart = tableBase + delta; + break; + } + if (lastEncoding >= cp) { + glyphStart = tableBase + delta; + break; + } + } + let recPos = glyphStart; + while (recPos + 3 <= font.length) { + const recStart = recPos; + const encoding = readU16BE(font, recPos); + const jump = font[recPos + 2]; + if (jump === 0) + break; + recPos += 3; + if (encoding === cp) + return decodeGlyphAt(font, recPos, hdr); + recPos = recStart + jump; + } + return null; + } else { + let recPos; + if (cp >= 97) + recPos = hdr.startLower; + else if (cp >= 65) + recPos = hdr.startUpper; + else + recPos = HDR_SIZE; + while (recPos + 2 <= font.length) { + const recStart = recPos; + const encoding = font[recPos]; + const jump = font[recPos + 1]; + if (jump === 0) + break; + recPos += 2; + if (encoding === cp) + return decodeGlyphAt(font, recPos, hdr); + recPos = recStart + jump; + } + return null; + } + } catch { + return null; + } +} +function rasterizeText(text, cellHeight, font) { + const hdr = parseHeader(font.FONT_DATA); + const glyphs = []; + for (const char of text) { + const cp = char.codePointAt(0) ?? 63; + const g = u8g2Glyph(cp, font.FONT_DATA); + if (g) + glyphs.push(g); + } + if (glyphs.length === 0) { + return { pixels: new Uint8Array(cellHeight), width: 1, height: cellHeight }; + } + const totalW = glyphs.reduce((sum, g, i) => sum + (i < glyphs.length - 1 ? g.dx : g.w), 0); + const pixels = new Uint8Array(totalW * cellHeight); + const descent = font.FONT_DATA[14] > 127 ? font.FONT_DATA[14] - 256 : font.FONT_DATA[14]; + const fontH = hdr.ascentA - descent; + const topPad = Math.floor((cellHeight - fontH) / 2); + const baseline = topPad + hdr.ascentA; + let x = 0; + for (const g of glyphs) { + const top = baseline - hdr.ascentA + g.yOff; + for (let col = 0;col < g.w; col++) { + const colByte = g.cols[col] ?? 0; + for (let row = 0;row < g.h; row++) { + if (colByte >> row & 1) { + const py = top + row; + if (py >= 0 && py < cellHeight) { + pixels[py * totalW + x + col] = 1; + } + } + } + } + x += g.dx; + } + return { pixels, width: totalW, height: cellHeight }; +} + +// src/font/index.ts +var u8g2_font_NokiaSmallPlain_tf = new MonoDisplayFont(u8g2_font_NokiaSmallPlain_tf_u8g2font); +var u8g2_font_5x7_mf = new MonoDisplayFont(u8g2_font_5x7_mf_u8g2font); +var u8g2_font_5x7_tf = new MonoDisplayFont(u8g2_font_5x7_tf_u8g2font); +var u8g2_font_HelvetiPixel_tr = new MonoDisplayFont(u8g2_font_HelvetiPixel_tr_u8g2font); +var u8g2_font_spleen12x24_me = new MonoDisplayFont(u8g2_font_spleen12x24_me_u8g2font); +var u8g2_font_smolfont_tf = new MonoDisplayFont(u8g2_font_smolfont_tf_u8g2font); +var u8g2_font_micropixel_tf = new MonoDisplayFont(u8g2_font_micropixel_tf_u8g2font); +var u8g2_font_micropixel_tr = new MonoDisplayFont(u8g2_font_micropixel_tr_u8g2font); + +// src/renderer.ts +class MonoDisplayRenderer { + ctx; + opts; + tickTimer = null; + tickCount = 0; + animState = new Map; + hScrollPos = new Map; + vScrollPos = new Map; + customFonts = []; + textCache = new Map; + static builtinFonts = { + NokiaSmallPlain_tf: u8g2_font_NokiaSmallPlain_tf, + "5x7_mf": u8g2_font_5x7_mf + }; + constructor(canvas, opts) { + const ctx = canvas.getContext("2d"); + if (!ctx) + throw new Error("Cannot get 2D canvas context"); + this.ctx = ctx; + this.opts = opts; + } + stop() { + if (this.tickTimer !== null) { + clearInterval(this.tickTimer); + this.tickTimer = null; + } + this.tickCount = 0; + this.animState.clear(); + this.hScrollPos.clear(); + this.vScrollPos.clear(); + this.textCache.clear(); + } + render(file) { + this.stop(); + const { displayWidth: dw, displayHeight: dh, scale: s } = this.opts; + this.ctx.canvas.width = dw * s; + this.ctx.canvas.height = dh * s; + this.customFonts = []; + for (const section of file.sections) { + if (section.sectionType === 32 /* CustomFont */) { + this.customFonts.push(section.fontData); + } + } + const tick = () => { + this.#renderFile(file); + this.tickCount++; + }; + tick(); + this.tickTimer = setInterval(tick, 1000 / this.opts.fps); + } + #renderFile(file) { + const now = BigInt(Math.floor(this.opts.now().getTime() / 1000)); + let cleared = false; + let elementId = 0; + this.ctx.fillStyle = this.opts.offColor; + this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); + for (const section of file.sections) { + if (section.sectionType === 2 /* ElementsTimespan */) { + if (now < section.startTimestamp || now >= section.endTimestamp) { + elementId += section.elements.length; + continue; + } + } + if (section.sectionType !== 1 /* ElementsAlways */ && section.sectionType !== 2 /* ElementsTimespan */) { + continue; + } + const elSection = section; + if (elSection.flags.clearBuffer && !cleared) { + this.ctx.fillStyle = this.opts.offColor; + this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); + cleared = true; + } + for (const el of elSection.elements) { + this.#renderElement(el, elementId++); + } + } + } + #renderElement(el, id) { + switch (el.type) { + case 1 /* Image2D */: + this.#blitImage(el.image, el.xOffset, el.yOffset); + break; + case 2 /* Animation */: + this.#renderAnimation(el, id); + break; + case 3 /* HorizontalScroll */: + this.#renderHScroll(el, id); + break; + case 4 /* VerticalScroll */: + this.#renderVScroll(el, id); + break; + case 5 /* Line */: + this.#renderLine(el); + break; + case 16 /* ClippedText */: + this.#renderClippedText(el); + break; + case 17 /* HScrollText */: + this.#renderHScrollText(el, id); + break; + case 32 /* CurrentTime */: + this.#renderCurrentTime(el); + break; + } + } + #blitImage(img, ox, oy) { + const { ctx, opts: { onColor, scale: s } } = this; + ctx.fillStyle = onColor; + for (let y = 0;y < img.height; y++) { + for (let x = 0;x < img.width; x++) { + if (img.pixels[y * img.width + x]) { + ctx.fillRect((ox + x) * s, (oy + y) * s, s, s); + } + } + } + } + #blitClipped(img, ox, oy, viewW, viewH, srcX, srcY) { + const { ctx, opts: { onColor, scale: s } } = this; + ctx.fillStyle = onColor; + for (let y = 0;y < viewH; y++) { + for (let x = 0;x < viewW; x++) { + const sx = Math.floor(srcX) + x; + const sy = Math.floor(srcY) + y; + if (sx < 0 || sx >= img.width || sy < 0 || sy >= img.height) + continue; + if (img.pixels[sy * img.width + sx]) { + ctx.fillRect((ox + x) * s, (oy + y) * s, s, s); + } + } + } + } + #renderAnimation(el, id) { + if (el.frames.length === 0) + return; + let state = this.animState.get(id); + if (!state) { + state = { frame: 0, counter: 0 }; + this.animState.set(id, state); + } + this.#blitImage(el.frames[state.frame], el.xOffset, el.yOffset); + state.counter++; + if (state.counter > el.updateInterval) { + state.counter = 0; + state.frame = (state.frame + 1) % el.frames.length; + if (!this.opts.loop && state.frame === 0) + state.frame = el.frames.length - 1; + } + } + #renderHScroll(el, id) { + let pos = this.hScrollPos.get(id) ?? 0; + this.#blitClipped(el.content, el.xOffset, el.yOffset, el.width, el.height, pos, 0); + const speed = (el.scrollSpeed + 1) / 16; + pos += el.flags.invertDirection ? -speed : speed; + if (el.flags.endless) { + if (pos >= el.contentWidth) + pos -= el.contentWidth; + if (pos < 0) + pos += el.contentWidth; + } else { + pos = Math.max(0, Math.min(pos, el.contentWidth - el.width)); + } + this.hScrollPos.set(id, pos); + } + #renderVScroll(el, id) { + let pos = this.vScrollPos.get(id) ?? 0; + this.#blitClipped(el.content, el.xOffset, el.yOffset, el.width, el.height, 0, pos); + const speed = (el.scrollSpeed + 1) / 16; + pos += el.flags.invertDirection ? -speed : speed; + if (el.flags.endless) { + if (pos >= el.contentHeight) + pos -= el.contentHeight; + if (pos < 0) + pos += el.contentHeight; + } else { + pos = Math.max(0, Math.min(pos, el.contentHeight - el.height)); + } + this.vScrollPos.set(id, pos); + } + #renderLine(el) { + const { ctx, opts: { onColor, offColor, scale: s } } = this; + ctx.fillStyle = el.invertPixels ? offColor : onColor; + let [x0, y0, x1, y1] = [el.xOrigin, el.yOrigin, el.xTarget, el.yTarget]; + const dx = Math.abs(x1 - x0), sx = x0 < x1 ? 1 : -1; + const dy = -Math.abs(y1 - y0), sy = y0 < y1 ? 1 : -1; + let err = dx + dy; + while (true) { + ctx.fillRect(x0 * s, y0 * s, s, s); + if (x0 === x1 && y0 === y1) + break; + const e2 = 2 * err; + if (e2 >= dy) { + err += dy; + x0 += sx; + } + if (e2 <= dx) { + err += dx; + y0 += sy; + } + } + } + #getTextImage(text, height, fontIndex) { + const key = `${text}|${fontIndex}|${height}`; + const cached = this.textCache.get(key); + if (cached) + return cached; + fontIndex = Math.min(fontIndex, Object.keys(MonoDisplayRenderer.builtinFonts).length - 1); + const builtinFont = Object.values(MonoDisplayRenderer.builtinFonts).at(fontIndex) || u8g2_font_NokiaSmallPlain_tf; + const customFont = this.customFonts[fontIndex - 32768]; + const selectedFont = fontIndex >= 32768 ? customFont || builtinFont : builtinFont; + const img = rasterizeText(text, height, selectedFont); + this.textCache.set(key, img); + return img; + } + #renderClippedText(el) { + const img = this.#getTextImage(el.text, el.height, el.fontIndex); + this.#blitClipped(img, el.xOffset, el.yOffset, el.width, el.height, 0, 0); + } + #renderHScrollText(el, id) { + const img = this.#getTextImage(el.text, el.height, el.fontIndex); + let pos = this.hScrollPos.get(id) ?? 0; + this.#blitClipped(img, el.xOffset, el.yOffset, el.width, el.height, pos, 0); + const speed = (el.scrollSpeed + 1) / 16; + pos += el.flags.invertDirection ? -speed : speed; + if (el.flags.endless) { + if (pos >= img.width) + pos -= img.width; + if (pos < 0) + pos += img.width; + } else { + pos = Math.max(0, Math.min(pos, img.width - el.width)); + } + this.hScrollPos.set(id, pos); + } + #renderCurrentTime(el) { + const d = new Date(this.opts.now().getTime() + el.utcOffsetMinutes * 60000); + const { clock12h, showHours, showMinutes, showSeconds } = el.flags; + const parts = []; + if (showHours) { + const h = d.getUTCHours(); + parts.push(String(clock12h ? h % 12 || 12 : h).padStart(2, "0")); + } + if (showMinutes) + parts.push(String(d.getUTCMinutes()).padStart(2, "0")); + if (showSeconds) + parts.push(String(d.getUTCSeconds()).padStart(2, "0")); + const text = parts.join(":"); + if (!text) + return; + const img = this.#getTextImage(text, el.height, el.fontIndex); + this.#blitClipped(img, el.xOffset, el.yOffset, el.width, el.height, 0, 0); + } +} +// src/driver.ts +class MonoDisplayDriver { + canvas; + opts; + parser; + renderer = null; + constructor(canvasId, options = {}) { + const el = document.getElementById(canvasId); + if (!el || el.tagName !== "CANVAS") + throw new Error(`#${canvasId} is not a `); + this.canvas = el; + this.opts = { + onColor: options.onColor ?? "#EC0", + offColor: options.offColor ?? "#000000", + scale: options.scale ?? 1, + displayWidth: options.displayWidth ?? 120, + displayHeight: options.displayHeight ?? 60, + fps: options.fps ?? 25, + loop: options.loop ?? true, + onError: options.onError ?? ((e) => { + throw e; + }), + now: options.now ?? (() => new Date) + }; + this.parser = new MonoDisplayParser; + } + async load(loader) { + try { + const buffer = await loader(); + const file = this.parser.parse(buffer); + if (!this.renderer) + this.renderer = new MonoDisplayRenderer(this.canvas, this.opts); + this.renderer.render(file); + } catch (err) { + this.opts.onError(err instanceof Error ? err : new Error(String(err))); + } + } + stop() { + this.renderer?.stop(); + } +} +// src/file.ts +class MonoDisplayFile { + sections; + version; + constructor(sections, version = 1) { + this.sections = sections; + this.version = version; + } + toBuffer() { + const sections = []; + for (const section of this.sections) { + switch (section.sectionType) { + case 1 /* ElementsAlways */: { + sections.push(this.#encodeElementsAlwaysSection(section)); + break; + } + case 2 /* ElementsTimespan */: { + sections.push(this.#encodeElementsTimespanSection(section)); + break; + } + case 32 /* CustomFont */: { + sections.push(this.#encodeCustomFont(section)); + break; + } + } + } + const hdrBuf = new ArrayBuffer(12); + const hdrView = new DataView(hdrBuf); + hdrView.setUint32(0, MONOFORMAT_MAGIC_HEADER, true); + hdrView.setUint32(4, this.version, true); + hdrView.setUint16(8, sections.length, true); + hdrView.setUint16(10, 0, true); + return this.#concat(new Uint8Array(hdrBuf), ...sections); + } + #encodeElementsAlwaysSection(section) { + const flagBits = (section.flags.drawFront ?? true ? 1 : 0) | (section.flags.drawBack ?? false ? 2 : 0) | (section.flags.clearBuffer ?? false ? 4 : 0); + const encodedEls = section.elements.map((el) => this.#encodeElement(el)); + const sectionDataSize = 4 + encodedEls.reduce((s, e) => s + e.byteLength, 0); + const sectionSize = 4 + sectionDataSize; + const hdr = new Uint8Array(4 + 4); + const v = new DataView(hdr.buffer); + v.setUint8(0, section.sectionType); + v.setUint8(1, sectionSize & 255); + v.setUint8(2, sectionSize >> 8 & 255); + v.setUint8(3, sectionSize >> 16 & 255); + v.setUint16(4, flagBits, true); + v.setUint16(6, section.elements.length, true); + return this.#concat(hdr, ...encodedEls); + } + #encodeElementsTimespanSection(section) { + const flagBits = (section.flags.drawFront ?? true ? 1 : 0) | (section.flags.drawBack ?? false ? 2 : 0) | (section.flags.clearBuffer ?? false ? 4 : 0); + const encodedEls = section.elements.map((el) => this.#encodeElement(el)); + const sectionDataSize = 20 + encodedEls.reduce((s, e) => s + e.byteLength, 0); + const sectionSize = 4 + sectionDataSize; + const hdr = new Uint8Array(4 + 20); + const v = new DataView(hdr.buffer); + v.setUint8(0, section.sectionType); + v.setUint8(1, sectionSize & 255); + v.setUint8(2, sectionSize >> 8 & 255); + v.setUint8(3, sectionSize >> 16 & 255); + v.setUint16(4, flagBits, true); + v.setUint16(6, section.elements.length, true); + const start = section.startTimestamp ?? 0n; + const end = section.endTimestamp ?? 0n; + v.setUint32(8, Number(start & 0xFFFFFFFFn), true); + v.setUint32(12, Number(start >> 32n & 0xFFFFFFFFn), true); + v.setUint32(16, Number(end & 0xFFFFFFFFn), true); + v.setUint32(20, Number(end >> 32n & 0xFFFFFFFFn), true); + return this.#concat(hdr, ...encodedEls); + } + #encodeCustomFont(section) { + const sectionSize = 4 + section.fontData.length; + const hdr = new Uint8Array(4 + 4); + const v = new DataView(hdr.buffer); + v.setUint8(0, section.sectionType); + v.setUint8(1, sectionSize & 255); + v.setUint8(2, sectionSize >> 8 & 255); + v.setUint8(3, sectionSize >> 16 & 255); + v.setUint8(4, section.fontData.length & 255); + v.setUint8(5, section.fontData.length >> 8 & 255); + v.setUint8(6, section.fontData.length >> 16 & 255); + v.setUint8(7, 0); + return this.#concat(hdr, section.fontData); + } + #encodeElement(el) { + console.log("encode", el); + switch (el.type) { + case 1 /* Image2D */: + return this.#encodeImage2D(el); + case 2 /* Animation */: + return this.#encodeAnimation(el); + case 3 /* HorizontalScroll */: + return this.#encodeHScroll(el); + case 4 /* VerticalScroll */: + return this.#encodeVScroll(el); + case 5 /* Line */: + return this.#encodeLine(el); + case 16 /* ClippedText */: + return this.#encodeClippedText(el); + case 17 /* HScrollText */: + return this.#encodeHScrollText(el); + case 32 /* CurrentTime */: + return this.#encodeCurrentTime(el); + default: + return new Uint8Array; + } + } + #encodeImage2D(el) { + const packed = packPixels(el.image.pixels, el.image.width, el.image.height); + const fixed = 12; + const rawSize = fixed + packed.byteLength; + const total = rawSize + pad32(rawSize); + const out = new Uint8Array(total); + const v = new DataView(out.buffer); + v.setUint16(0, 1 /* Image2D */, true); + v.setUint16(2, el.xOffset ?? 0, true); + v.setUint16(4, el.yOffset ?? 0, true); + v.setUint16(6, el.image.width, true); + v.setUint16(8, el.image.height, true); + v.setUint16(10, 0, true); + out.set(packed, 12); + return out; + } + #encodeAnimation(el) { + const frameBytes = packedSize(el.width, el.height); + const fixed = 16; + const rawSize = fixed + el.frames.length * frameBytes; + const total = rawSize + pad32(rawSize); + const out = new Uint8Array(total); + const v = new DataView(out.buffer); + v.setUint16(0, 2 /* Animation */, true); + v.setUint16(2, el.xOffset ?? 0, true); + v.setUint16(4, el.yOffset ?? 0, true); + v.setUint16(6, el.width, true); + v.setUint16(8, el.height, true); + v.setUint16(10, el.frames.length, true); + v.setUint16(12, el.updateInterval ?? 0, true); + v.setUint16(14, 0, true); + let off = 16; + for (const f of el.frames) { + out.set(packPixels(f.pixels, el.width, el.height), off); + off += frameBytes; + } + return out; + } + #encodeScrollFlags(f) { + return (f?.endless ? 1 : 0) | (f?.invertDirection ? 2 : 0) | (f?.padStart ? 4 : 0) | (f?.padEnd ? 8 : 0); + } + #encodeHScroll(el) { + const packed = packPixels(el.content.pixels, el.contentWidth, el.height); + const fixed = 16; + const rawSize = fixed + packed.byteLength; + const total = rawSize + pad32(rawSize); + const out = new Uint8Array(total); + const v = new DataView(out.buffer); + v.setUint16(0, 3 /* HorizontalScroll */, true); + v.setUint16(2, el.xOffset ?? 0, true); + v.setUint16(4, el.yOffset ?? 0, true); + v.setUint16(6, el.width, true); + v.setUint16(8, el.height, true); + v.setUint16(10, el.contentWidth, true); + v.setUint8(12, this.#encodeScrollFlags(el.flags)); + v.setUint8(13, el.scrollSpeed ?? 0); + v.setUint16(14, 0, true); + out.set(packed, 16); + return out; + } + #encodeVScroll(el) { + const packed = packPixels(el.content.pixels, el.width, el.contentHeight); + const fixed = 16; + const rawSize = fixed + packed.byteLength; + const total = rawSize + pad32(rawSize); + const out = new Uint8Array(total); + const v = new DataView(out.buffer); + v.setUint16(0, 4 /* VerticalScroll */, true); + v.setUint16(2, el.xOffset ?? 0, true); + v.setUint16(4, el.yOffset ?? 0, true); + v.setUint16(6, el.width, true); + v.setUint16(8, el.height, true); + v.setUint16(10, el.contentHeight, true); + v.setUint8(12, this.#encodeScrollFlags(el.flags)); + v.setUint8(13, el.scrollSpeed ?? 0); + v.setUint16(14, 0, true); + out.set(packed, 16); + return out; + } + #encodeLine(el) { + const out = new Uint8Array(12); + const v = new DataView(out.buffer); + v.setUint16(0, 5 /* Line */, true); + v.setUint16(2, el.xOrigin, true); + v.setUint16(4, el.yOrigin, true); + v.setUint16(6, el.xTarget, true); + v.setUint16(8, el.yTarget, true); + v.setUint8(10, el.lineStyle ?? 0); + v.setUint8(11, el.invertPixels ? 1 : 0); + return out; + } + #encodeClippedText(el) { + const enc = new TextEncoder().encode(el.text); + const fixed = 14; + const rawSize = fixed + enc.byteLength; + const total = rawSize + pad32(rawSize); + const out = new Uint8Array(total); + const v = new DataView(out.buffer); + v.setUint16(0, 16 /* ClippedText */, true); + v.setUint16(2, el.xOffset ?? 0, true); + v.setUint16(4, el.yOffset ?? 0, true); + v.setUint16(6, el.width, true); + v.setUint16(8, el.height, true); + v.setUint16(10, el.fontIndex ?? 0, true); + v.setUint16(12, enc.byteLength, true); + out.set(enc, 14); + return out; + } + #encodeHScrollText(el) { + const enc = new TextEncoder().encode(el.text); + const fixed = 16; + const rawSize = fixed + enc.byteLength; + const total = rawSize + pad32(rawSize); + const out = new Uint8Array(total); + const v = new DataView(out.buffer); + v.setUint16(0, 17 /* HScrollText */, true); + v.setUint16(2, el.xOffset ?? 0, true); + v.setUint16(4, el.yOffset ?? 0, true); + v.setUint16(6, el.width, true); + v.setUint16(8, el.height, true); + v.setUint8(10, this.#encodeScrollFlags(el.flags)); + console.log("encodeScrollFlags", el.flags, this.#encodeScrollFlags(el.flags)); + v.setUint8(11, el.scrollSpeed ?? 0); + v.setUint16(12, el.fontIndex ?? 0, true); + v.setUint16(14, enc.byteLength, true); + out.set(enc, 16); + return out; + } + #encodeCurrentTime(el) { + const out = new Uint8Array(16); + const v = new DataView(out.buffer); + const f = el.flags; + const flagsByte = (f?.clock12h ? 1 : 0) | (f?.showHours ?? true ? 2 : 0) | (f?.showMinutes ?? true ? 4 : 0) | (f?.showSeconds ? 8 : 0); + v.setUint16(0, 32 /* CurrentTime */, true); + v.setUint16(2, el.xOffset ?? 0, true); + v.setUint16(4, el.yOffset ?? 0, true); + v.setUint16(6, el.width, true); + v.setUint16(8, el.height, true); + v.setUint16(10, el.fontIndex ?? 0, true); + v.setInt16(12, el.utcOffsetMinutes ?? 0, true); + v.setUint8(14, flagsByte); + v.setUint8(15, 0); + return out; + } + #concat(...parts) { + const total = parts.reduce((s, p) => s + p.byteLength, 0); + const out = new Uint8Array(total); + let off = 0; + for (const p of parts) { + out.set(p, off); + off += p.byteLength; + } + return out; + } +} +async function loadBinFile(url) { + const res = await fetch(url); + if (!res.ok) + throw new Error(`loadBinFile: HTTP ${res.status} for ${url}`); + return res.arrayBuffer(); +} +function buildBinBuffer(el) { + return new MonoDisplayFile([{ + sectionType: 1 /* ElementsAlways */, + elements: [el], + flags: { + drawFront: true, + drawBack: true, + clearBuffer: true + } + }]).toBuffer(); +} +// src/themes.ts +var _THEMES = ["dark", "light", "auto"]; +function _getTheme() { + return document.documentElement.getAttribute("data-theme") || "auto"; +} +function _applyTheme(t) { + if (t === "auto") { + document.documentElement.removeAttribute("data-theme"); + localStorage.removeItem("theme"); + } else { + document.documentElement.setAttribute("data-theme", t); + localStorage.setItem("theme", t); + } + _updateThemeBtn(); +} +function cycleTheme() { + var cur = _getTheme(), idx = _THEMES.indexOf(cur); + _applyTheme(_THEMES[(idx + 1) % _THEMES.length]); +} +function _detectDR() { + return !!(document.querySelector('meta[name="darkreader"]') || document.querySelector("style[data-darkreader-style]") || document.documentElement.getAttribute("data-darkreader-mode")); +} +function _updateThemeBtn() { + var btn = document.getElementById("theme-toggle"); + if (!btn) + return; + var t = _getTheme(), dr = _detectDR(); + var lbl = { dark: "☾ dark", light: "☀ light", auto: "⊙ auto" }; + btn.textContent = (dr ? "DR · " : "") + (lbl[t] || lbl.auto); + btn.title = dr ? "DarkReader active - controlling theme" : "Theme: " + t + " (click to cycle)"; +} +window.addEventListener("DOMContentLoaded", _updateThemeBtn); + +// src/browser.ts +globalThis.MonoDisplay = exports_src; +var W = 120; +var H = 60; +var file = new MonoDisplayFile([]); +var activeSecIndex = null; +var activeElIndex = null; +var currentFilename = "untitled"; +var isDirty = false; +var EL_FIELDS = { + Image2D: [ + { key: "xOffset", label: "X offset", type: "number", default: 0 }, + { key: "yOffset", label: "Y offset", type: "number", default: 0 }, + { key: "width", label: "Width", type: "number", default: W }, + { key: "height", label: "Height", type: "number", default: H } + ], + Animation: [ + { key: "xOffset", label: "X offset", type: "number", default: 0 }, + { key: "yOffset", label: "Y offset", type: "number", default: 0 }, + { key: "width", label: "Width", type: "number", default: W }, + { key: "height", label: "Height", type: "number", default: H }, + { key: "updateInterval", label: "Update interval", type: "number", default: 12 }, + { key: "frames", label: "Frames", type: "list", default: [] } + ], + ClippedText: [ + { key: "text", label: "Text", type: "text", default: "Hello, World!", full: true }, + { key: "xOffset", label: "X offset", type: "number", default: 0 }, + { key: "yOffset", label: "Y offset", type: "number", default: 10 }, + { key: "width", label: "Width", type: "number", default: 30 }, + { key: "height", label: "Height", type: "number", default: 10 }, + { key: "fontIndex", label: "Font", type: "font", default: 0 } + ], + HScrollText: [ + { key: "text", label: "Text", type: "text", default: "Scrolling text - ", full: true }, + { key: "xOffset", label: "X offset", type: "number", default: 0 }, + { key: "yOffset", label: "Y offset", type: "number", default: 10 }, + { key: "width", label: "Width", type: "number", default: 30 }, + { key: "height", label: "Height", type: "number", default: 10 }, + { key: "scrollSpeed", label: "Scroll speed", type: "number", default: 50 }, + { key: "fontIndex", label: "Font", type: "font", default: 0 } + ], + VScrollText: [ + { key: "text", label: "Text", type: "text", default: "Scrolling text - ", full: true }, + { key: "xOffset", label: "X offset", type: "number", default: 0 }, + { key: "yOffset", label: "Y offset", type: "number", default: 32 }, + { key: "width", label: "Width", type: "number", default: 30 }, + { key: "height", label: "Height", type: "number", default: 10 }, + { key: "scrollSpeed", label: "Scroll speed", type: "number", default: 50 }, + { key: "fontIndex", label: "Font", type: "font", default: 0 } + ], + CurrentTime: [ + { key: "xOffset", label: "X offset", type: "number", default: 0 }, + { key: "yOffset", label: "Y offset", type: "number", default: 8 }, + { key: "width", label: "Width", type: "number", default: W }, + { key: "height", label: "Height", type: "number", default: H }, + { key: "utcOffsetMinutes", label: "UTC offset (min)", type: "number", default: 120 }, + { key: "fontIndex", label: "Font", type: "font", default: 0 } + ] +}; +var EL_FLAGS = { + HScrollText: [ + { key: "endless", label: "Endless" }, + { key: "invertDirection", label: "Invert direction" }, + { key: "padBefore", label: "Pad Before" }, + { key: "padAfter", label: "Pad After" } + ], + CurrentTime: [ + { key: "clock12h", label: "12h mode", default: false }, + { key: "showHours", label: "Show hours", default: true }, + { key: "showSeconds", label: "Show seconds", default: true } + ] +}; +var EL_TYPES = Object.keys(EL_FIELDS).map((x) => StringToElementType[x]); +var DEFAULT_FONTS = Object.keys(MonoDisplayRenderer.builtinFonts); +function showConfirm(msg, buttons) { + return new Promise((resolve) => { + const overlay = document.getElementById("confirm-overlay"); + const msgEl = document.getElementById("confirm-msg"); + const btnsEl = document.getElementById("confirm-btns"); + if (msgEl) + msgEl.textContent = msg; + if (btnsEl) + btnsEl.innerHTML = ""; + buttons.forEach((b) => { + const btn = document.createElement("button"); + btn.className = "cb" + (b.primary ? " primary" : ""); + btn.textContent = b.label; + btn.onclick = () => { + overlay?.classList.remove("show"); + resolve(b.action); + }; + btnsEl?.appendChild(btn); + }); + overlay?.classList.add("show"); + }); +} +var STORAGE_KEY = "monodisplay_autosave"; +function saveToStorage() { + try { + const buf = file.toBuffer(); + const b64 = btoa(String.fromCharCode(...buf)); + localStorage.setItem(STORAGE_KEY, JSON.stringify({ filename: currentFilename, data: b64 })); + } catch {} +} +function loadFromStorage() { + try { + const raw = localStorage.getItem(STORAGE_KEY); + if (!raw) + return; + const { filename, data } = JSON.parse(raw); + const bytes = Uint8Array.from(atob(data), (c) => c.charCodeAt(0)); + const parsed = new MonoDisplayParser().parse(bytes.buffer); + file = new MonoDisplayFile(parsed.sections); + currentFilename = filename; + const filenameEl = document.getElementById("filename"); + if (filenameEl) + filenameEl.textContent = filename; + isDirty = true; + document.getElementById("filename")?.classList.add("dirty"); + import_mithril.default.redraw(); + } catch {} +} +function markDirty() { + isDirty = true; + document.getElementById("filename")?.classList.add("dirty"); + saveToStorage(); +} +function markClean() { + isDirty = false; + document.getElementById("filename")?.classList.remove("dirty"); +} +async function guardDirty() { + if (!isDirty || !file.sections.length) + return true; + return showConfirm("You have unsaved changes. Loading a demo will replace the current editor state.", [{ label: "Cancel", action: false }, { label: "Load anyway", primary: true, action: true }]); +} +function newSec() { + return { + sectionType: 1 /* ElementsAlways */, + elements: [], + flags: { + clearBuffer: true, + drawBack: true, + drawFront: true + } + }; +} +function createNewElement(type) { + const fields = {}; + (EL_FIELDS[ElementTypeToString[type]] || []).forEach((f) => fields[f.key] = f.default); + const flags = {}; + (EL_FLAGS[ElementTypeToString[type]] || []).forEach((f) => flags[f.key] = f.default ?? false); + const el = { type, ...fields, flags }; + if (type === 1 /* Image2D */) { + el.image = { pixels: new Uint8Array(el.width * el.height), width: el.width, height: el.height }; + } + if (type === 2 /* Animation */) { + el.frames = [{ pixels: new Uint8Array(el.width * el.height), width: el.width, height: el.height }]; + } + return el; +} +function getSectionByIndex(i) { + return i !== null ? file.sections[i] : undefined; +} +function getElementByIndex(si, ei) { + const s = getSectionByIndex(si); + return s && "elements" in s ? s.elements[ei] : undefined; +} +function getCustomFonts() { + return file.sections.filter((s) => s.sectionType === 32 /* CustomFont */).map((_, i) => ({ fontname: `CustomFont ${i}`, index: 32768 + i })); +} +function elSummary(el) { + switch (el.type) { + case 16 /* ClippedText */: + case 17 /* HScrollText */: + return el.text.slice(0, 24) + (el.text.length > 24 ? "..." : ""); + case 32 /* CurrentTime */: + case 1 /* Image2D */: + case 3 /* HorizontalScroll */: + case 4 /* VerticalScroll */: + return `${el.xOffset ?? 0}, ${el.yOffset ?? 0}`; + default: + return el.type.toString(); + } +} +function addSection() { + file.sections.push(newSec()); + activeSecIndex = file.sections.length - 1; + activeElIndex = null; + markDirty(); + triggerPreview(); + import_mithril.default.redraw(); +} +function removeSection(si) { + file.sections.splice(si, 1); + if (activeSecIndex === si) { + activeSecIndex = file.sections.length ? file.sections.length - 1 : null; + activeElIndex = null; + } + markDirty(); + triggerPreview(); +} +function toggleSection(si) { + activeSecIndex = si; +} +function setSectionFlag(flag, val) { + if (activeSecIndex === null) + return; + const s = getSectionByIndex(activeSecIndex); + if (!s) + return; + s.flags[flag] = val; + markDirty(); + triggerPreview(); +} +function setSectionField(si, key, val) { + const s = getSectionByIndex(si); + if (!s) + return; + s[key] = val; + markDirty(); + triggerPreview(); +} +function setSectionType(si, sectionType) { + const sec = getSectionByIndex(si); + if (!sec) + return; + const wasElementsSection = sec.sectionType !== 32 /* CustomFont */; + sec.sectionType = StringToSectionType[sectionType]; + if (sec.sectionType === 32 /* CustomFont */) { + sec.fontData = new Uint8Array; + delete sec.elements; + delete sec.flags; + } else { + if (!wasElementsSection) { + sec.elements = []; + sec.flags = {}; + } + delete sec.fontData; + if (sec.sectionType === 2 /* ElementsTimespan */) { + const now = BigInt(Math.floor(Date.now() / 1000)); + sec.startTimestamp = now; + sec.endTimestamp = now + 3600n; + } else { + delete sec.startTimestamp; + delete sec.endTimestamp; + } + } + markDirty(); + triggerPreview(); +} +function addElement(si) { + const s = getSectionByIndex(si); + if (!s || !("elements" in s)) + return; + s.elements.push(createNewElement(17 /* HScrollText */)); + activeElIndex = s.elements.length - 1; + activeSecIndex = si; + markDirty(); + triggerPreview(); +} +function removeElement(si, ei) { + const s = getSectionByIndex(si); + if (!s || !("elements" in s)) + return; + s.elements.splice(ei, 1); + activeElIndex = null; + markDirty(); + triggerPreview(); +} +function selectElement(si, ei) { + if (activeSecIndex === si && activeElIndex === ei) { + activeElIndex = null; + } else { + activeSecIndex = si; + activeElIndex = ei; + } +} +function changeElType(si, ei, typeString) { + const el = getElementByIndex(si, ei); + if (!el) + return; + const fresh = createNewElement(StringToElementType[typeString]); + Object.assign(el, fresh); + markDirty(); + triggerPreview(); +} +function setElField(si, ei, key, val) { + const el = getElementByIndex(si, ei); + if (!el) + return; + el[key] = val; + markDirty(); + triggerPreview(); +} +function setElFlag(si, ei, key, val) { + const el = getElementByIndex(si, ei); + if (!el) + return; + el.flags[key] = val; + markDirty(); + triggerPreview(); +} +var DEMO = { + Checkerboard() { + const pixels = new Uint8Array(W * H); + for (let y = 0;y < H; y++) + for (let x = 0;x < W; x++) + pixels[y * W + x] = (x + y) % 2; + return new MonoDisplayFile([{ + sectionType: 1 /* ElementsAlways */, + elements: [{ type: 1 /* Image2D */, image: { pixels, height: H, width: W }, xOffset: 0, yOffset: 0 }], + flags: { clearBuffer: true, drawFront: true, drawBack: true } + }]); + }, + Blink() { + return new MonoDisplayFile([{ + sectionType: 1 /* ElementsAlways */, + elements: [{ + type: 2 /* Animation */, + width: W, + height: H, + updateInterval: 12, + xOffset: 0, + yOffset: 0, + frames: [ + { pixels: new Uint8Array(W * H).fill(1), width: 0, height: 0 }, + { pixels: new Uint8Array(W * H).fill(0), width: 0, height: 0 } + ] + }], + flags: { clearBuffer: true, drawFront: true, drawBack: true } + }]); + }, + Text() { + return new MonoDisplayFile([{ + sectionType: 1 /* ElementsAlways */, + elements: [{ type: 16 /* ClippedText */, fontIndex: 0, text: "Hello, World!", xOffset: 0, yOffset: 16, width: 60, height: 10 }], + flags: { clearBuffer: true, drawFront: true, drawBack: true } + }]); + }, + Scrolltext() { + return new MonoDisplayFile([{ + sectionType: 1 /* ElementsAlways */, + elements: [{ + type: 17 /* HScrollText */, + fontIndex: 0, + text: "MONO DISPLAY - scrolling ticker - \uD83D\uDE80 ", + xOffset: 0, + yOffset: 32, + width: W, + height: 16, + scrollSpeed: 50, + flags: { endless: true, invertDirection: false, padStart: false, padEnd: false } + }], + flags: { clearBuffer: true, drawFront: true, drawBack: true } + }]); + }, + Time() { + return new MonoDisplayFile([{ + sectionType: 1 /* ElementsAlways */, + elements: [ + { type: 32 /* CurrentTime */, fontIndex: 0, flags: {}, xOffset: 0, yOffset: 8, width: W, height: 16, utcOffsetMinutes: 120 }, + { type: 32 /* CurrentTime */, fontIndex: 0, flags: { clock12h: true }, xOffset: 40, yOffset: 16, width: W, height: 16, utcOffsetMinutes: 120 }, + { type: 32 /* CurrentTime */, fontIndex: 0, flags: { clock12h: true, showHours: true }, xOffset: 0, yOffset: 24, width: W, height: 16, utcOffsetMinutes: 120 }, + { type: 32 /* CurrentTime */, fontIndex: 0, flags: { clock12h: true, showHours: false }, xOffset: 40, yOffset: 32, width: W, height: 16, utcOffsetMinutes: 120 }, + { type: 32 /* CurrentTime */, fontIndex: 0, flags: { clock12h: true, showSeconds: true }, xOffset: 80, yOffset: 32, width: W, height: 16, utcOffsetMinutes: 120 } + ], + flags: { clearBuffer: true, drawFront: true, drawBack: true } + }]); + } +}; +var previewTimer = null; +function triggerPreview() { + if (previewTimer) + clearTimeout(previewTimer); + previewTimer = setTimeout(() => { + try { + buildPreview(); + } catch (e) { + console.warn("preview", e); + } + }, 150); +} +function buildPreview() { + if (!window.MonoDisplay) + return; + if (!window._mdDriver) + window._mdDriver = new MonoDisplayDriver("canvas_root", { onColor: "#EC0", offColor: "#000", fps: 25 }); + window._mdDriver.load(() => Promise.resolve(file.toBuffer())); +} +function loadBin(input) { + const binFile = input.files?.[0]; + if (!binFile) + return; + currentFilename = binFile.name; + const filenameEl = document.getElementById("filename"); + if (filenameEl) + filenameEl.textContent = binFile.name; + const reader = new FileReader; + reader.onload = (e) => { + try { + const parsed = new window.MonoDisplay.MonoDisplayParser().parse(e.target.result); + file = new MonoDisplayFile(parsed.sections); + activeSecIndex = null; + activeElIndex = null; + triggerPreview(); + import_mithril.default.redraw(); + } catch (err) { + console.warn("Could not parse sections from bin:", err); + } + }; + reader.readAsArrayBuffer(binFile); + input.value = ""; + markClean(); +} +function exportBin() { + if (!window.MonoDisplay) { + alert("MonoDisplay library not loaded."); + return; + } + const blob = new Blob([file.toBuffer()], { type: "application/octet-stream" }); + const a = document.createElement("a"); + a.href = URL.createObjectURL(blob); + a.download = currentFilename.replace(/\.bin$/, "") + ".bin"; + a.click(); + markClean(); +} +function FieldInput(si, ei, field, el) { + const val = el[field.key] ?? field.default; + switch (field.type) { + case "text": + return import_mithril.default("textarea", { + onchange: (e) => setElField(si, ei, field.key, e.target.value) + }, val); + case "font": + return import_mithril.default("select", { + onchange: (e) => setElField(si, ei, field.key, +e.target.value) + }, [ + ...DEFAULT_FONTS.map((name, i) => import_mithril.default("option", { value: i, selected: val === i }, name)), + ...getCustomFonts().map((cf) => import_mithril.default("option", { value: cf.index, selected: val === cf.index }, cf.fontname)) + ]); + case "list": + return import_mithril.default("span"); + default: + return import_mithril.default("input[type=number]", { + value: val, + onchange: (e) => setElField(si, ei, field.key, +e.target.value) + }); + } +} +var PIXEL_SCALE = 4; +function getPixelIndex(img, x, y) { + return y * img.width + x; +} +function drawPixelCanvas(canvas, img) { + const ctx = canvas.getContext("2d"); + ctx.fillStyle = "#000"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.fillStyle = "#EC0"; + for (let y = 0;y < img.height; y++) { + for (let x = 0;x < img.width; x++) { + if (img.pixels[getPixelIndex(img, x, y)]) { + ctx.fillRect(x * PIXEL_SCALE, y * PIXEL_SCALE, PIXEL_SCALE, PIXEL_SCALE); + } + } + } +} +var PixelCanvas = { + drawing: false, + drawValue: 1, + view({ attrs: { img, onpaint }, state }) { + function pixelFromEvent(e) { + const canvas = e.currentTarget; + const rect = canvas.getBoundingClientRect(); + const scaleX = canvas.width / rect.width; + const scaleY = canvas.height / rect.height; + const x = Math.floor((e.clientX - rect.left) * scaleX / PIXEL_SCALE); + const y = Math.floor((e.clientY - rect.top) * scaleY / PIXEL_SCALE); + if (x < 0 || x >= img.width || y < 0 || y >= img.height) + return null; + return { x, y }; + } + function paint(e) { + const p = pixelFromEvent(e); + if (!p) + return; + img.pixels[getPixelIndex(img, p.x, p.y)] = state.drawValue; + onpaint(); + drawPixelCanvas(e.currentTarget, img); + } + return import_mithril.default("canvas.pixel-editor", { + width: img.width * PIXEL_SCALE, + height: img.height * PIXEL_SCALE, + oncreate: ({ dom }) => drawPixelCanvas(dom, img), + onupdate: ({ dom }) => drawPixelCanvas(dom, img), + onmousedown: (e) => { + state.drawing = true; + const p = pixelFromEvent(e); + if (p) + state.drawValue = img.pixels[getPixelIndex(img, p.x, p.y)] ? 0 : 1; + paint(e); + }, + onmousemove: (e) => { + if (state.drawing) + paint(e); + }, + onmouseup: () => { + state.drawing = false; + }, + onmouseleave: () => { + state.drawing = false; + } + }); + } +}; +var Image2DEditor = { + view({ attrs: { si, ei, el } }) { + if (!el.image) { + const w = el.width || W; + const h = el.height || H; + el.image = { pixels: new Uint8Array(w * h), width: w, height: h }; + } + return import_mithril.default(PixelCanvas, { + img: el.image, + onpaint: () => { + markDirty(); + triggerPreview(); + } + }); + } +}; +var AnimationEditor = { + activeFrame: 0, + view({ attrs: { si, ei, el }, state }) { + const w = el.width || W; + const h = el.height || H; + if (!el.frames) + el.frames = []; + if (!el.frames.length) { + el.frames.push({ pixels: new Uint8Array(w * h), width: w, height: h }); + } + state.activeFrame = Math.min(state.activeFrame, el.frames.length - 1); + const frame = el.frames[state.activeFrame]; + function addFrame() { + el.frames.push({ pixels: new Uint8Array(w * h), width: w, height: h }); + state.activeFrame = el.frames.length - 1; + markDirty(); + triggerPreview(); + import_mithril.default.redraw(); + } + function removeFrame(fi) { + el.frames.splice(fi, 1); + if (!el.frames.length) + el.frames.push({ pixels: new Uint8Array(w * h), width: w, height: h }); + state.activeFrame = Math.min(state.activeFrame, el.frames.length - 1); + markDirty(); + triggerPreview(); + } + return import_mithril.default(".anim-editor", import_mithril.default(".anim-frame-tabs", { key: "tabs" }, el.frames.map((_, fi) => import_mithril.default(".anim-frame-tab", { + class: fi === state.activeFrame ? "active" : "", + onclick: () => { + state.activeFrame = fi; + } + }, import_mithril.default("span", fi + 1), el.frames.length > 1 ? import_mithril.default("button.x-btn-sm", { + onclick: (e) => { + e.stopPropagation(); + removeFrame(fi); + } + }, "×") : null)), import_mithril.default("button.add-el", { onclick: addFrame }, "+ frame")), import_mithril.default("div", { key: state.activeFrame }, import_mithril.default(PixelCanvas, { + img: frame, + onpaint: () => { + markDirty(); + triggerPreview(); + } + }))); + } +}; +var ElementItem = { + view({ attrs: { si, ei, el } }) { + const isActive = activeSecIndex === si && activeElIndex === ei; + const typeStr = ElementTypeToString[el.type]; + const fields = EL_FIELDS[typeStr] || []; + const flags = EL_FLAGS[typeStr] || []; + return import_mithril.default(".el-item", { class: isActive ? "active" : "" }, import_mithril.default(".el-item-hdr", { onclick: () => selectElement(si, ei) }, import_mithril.default("span.el-type", typeStr), import_mithril.default("span.el-name", elSummary(el)), import_mithril.default("button.x-btn", { + title: "Remove element", + onclick: (e) => { + e.stopPropagation(); + removeElement(si, ei); + } + }, "X")), isActive && import_mithril.default(".el-fields", import_mithril.default(".field.full", import_mithril.default("label", "Type"), import_mithril.default("select", { + onchange: (e) => changeElType(si, ei, e.target.value) + }, EL_TYPES.map((t) => import_mithril.default("option", { value: ElementTypeToString[t], selected: t === el.type }, ElementTypeToString[t])))), import_mithril.default(".fields-grid", fields.map((field) => import_mithril.default(`.field${field.full ? ".full" : ""}`, import_mithril.default("label", field.label), FieldInput(si, ei, field, el)))), flags.length ? import_mithril.default(".field.full", import_mithril.default("label", "Flags"), import_mithril.default(".flags-row", flags.map((flag) => import_mithril.default("label.flag-check", import_mithril.default("input[type=checkbox]", { + checked: !!el.flags?.[flag.key], + onchange: (e) => setElFlag(si, ei, flag.key, e.target.checked) + }), " " + flag.label)))) : null, el.type === 1 /* Image2D */ ? import_mithril.default(".field.full", import_mithril.default("label", "Pixels"), import_mithril.default(Image2DEditor, { si, ei, el })) : el.type === 2 /* Animation */ ? import_mithril.default(".field.full", import_mithril.default("label", "Frames"), import_mithril.default(AnimationEditor, { si, ei, el })) : null)); + } +}; +var SectionCard = { + view({ attrs: { si } }) { + const section = file.sections[si]; + if (!section) + return null; + const isActive = activeSecIndex === si; + const typeStr = SectionTypeToString[section.sectionType]; + const hdr = import_mithril.default(`.sec-hdr${isActive ? ".active" : ""}`, { onclick: () => toggleSection(si) }, import_mithril.default("span.sec-arrow", "▶"), import_mithril.default("span.sec-label", typeStr), import_mithril.default("button.x-btn", { + title: "Remove section", + onclick: (e) => { + e.stopPropagation(); + removeSection(si); + } + }, "X")); + if (section.sectionType === 32 /* CustomFont */) { + return import_mithril.default(`.sec-card.open${isActive ? ".active" : ""}`, import_mithril.default(`.sec-hdr${isActive ? ".active" : ""}`, { onclick: () => toggleSection(si) }, import_mithril.default("span.sec-arrow", "▶"), import_mithril.default("span.sec-label", "Custom Font"), import_mithril.default("span.sec-badge", "1 el"), import_mithril.default("button.x-btn", { + title: "Remove section", + onclick: (e) => { + e.stopPropagation(); + removeSection(si); + } + }, "X")), import_mithril.default(".el-list", "?")); + } + const elSection = section; + return import_mithril.default(`.sec-card.open${isActive ? ".active" : ""}`, import_mithril.default(`.sec-hdr${isActive ? ".active" : ""}`, { onclick: () => toggleSection(si) }, import_mithril.default("span.sec-arrow", "▶"), import_mithril.default("span.sec-label", typeStr), import_mithril.default("span.sec-badge", `${elSection.elements.length} el`), import_mithril.default("button.x-btn", { + title: "Remove section", + onclick: (e) => { + e.stopPropagation(); + removeSection(si); + } + }, "X")), section.sectionType === 2 /* ElementsTimespan */ ? import_mithril.default(".el-list", [["Start Time", "startTimestamp"], ["Stop Time", "endTimestamp"]].map(([label, key]) => { + let posix = section[key]; + if (!posix) { + posix = BigInt(Math.floor(Date.now() / 1000)); + setSectionField(si, key, posix); + } + const dtLocal = new Date(Number(posix) * 1000).toISOString().slice(0, 16); + return import_mithril.default(".field", import_mithril.default("label", label), import_mithril.default("input[type=datetime-local]", { + value: dtLocal, + onchange: (e) => { + const ms = new Date(e.target.value).getTime(); + setSectionField(si, key, BigInt(Math.floor(ms / 1000))); + } + })); + })) : null, import_mithril.default(".el-list", elSection.elements.map((el, ei) => import_mithril.default(ElementItem, { key: String(ei), si, ei, el })), import_mithril.default("button.add-el", { onclick: () => addElement(si) }, "+ add element"))); + } +}; +var SectionsList = { + view() { + if (!file.sections.length) + return import_mithril.default(".empty-state", import_mithril.default.trust("No sections yet.
Use Add section to get started.")); + if (activeSecIndex === null && file.sections.length === 1) + activeSecIndex = 0; + return import_mithril.default("", file.sections.map((_, si) => import_mithril.default(SectionCard, { key: String(si), si }))); + } +}; +var MetaPanel = { + view() { + const section = getSectionByIndex(activeSecIndex); + return import_mithril.default("", import_mithril.default(".meta-row", import_mithril.default("label", "Selected section"), section ? import_mithril.default("select.meta-val.green", { + value: SectionTypeToString[section.sectionType], + onchange: (e) => activeSecIndex !== null && setSectionType(activeSecIndex, e.target.value) + }, Object.values(SectionTypeToString).map((name) => import_mithril.default("option", { value: name, selected: name === SectionTypeToString[section.sectionType] }, name))) : import_mithril.default("span.meta-val.green", "-")), import_mithril.default(".meta-row", import_mithril.default("label", "Elements"), import_mithril.default("span.meta-val", section && "elements" in section ? `${section.elements.length} element(s)` : "-")), import_mithril.default(".meta-row", import_mithril.default("label", "Section flags"), ["drawFront", "drawBack", "clearBuffer"].map((flag, i) => import_mithril.default("label.flag-check", { style: i > 0 ? "margin-left:-8px" : "" }, import_mithril.default("input[type=checkbox]", { + checked: section ? !!section.flags?.[flag] : false, + onchange: (e) => setSectionFlag(flag, e.target.checked) + }), " " + flag)))); + } +}; +var activeDemoName = null; +var DemoButtons = { + view() { + return Object.entries(DEMO).map(([name, fn]) => import_mithril.default("button.tb-btn", { + style: activeDemoName === name ? "color:#33ff66" : "", + onclick: async () => { + const ok = await guardDirty(); + if (!ok) + return; + file = fn(); + activeSecIndex = null; + activeElIndex = null; + currentFilename = name.toLowerCase(); + const filenameEl = document.getElementById("filename"); + if (filenameEl) + filenameEl.textContent = currentFilename; + activeDemoName = name; + markClean(); + triggerPreview(); + import_mithril.default.redraw(); + } + }, name)); + } +}; +import_mithril.default.mount(document.getElementById("sections-wrap"), SectionsList); +import_mithril.default.mount(document.getElementById("demo-btns"), DemoButtons); +import_mithril.default.mount(document.getElementById("sec-meta"), MetaPanel); +async function clearAll() { + if (isDirty && file.sections.length) { + const ok = await showConfirm("Clear all sections?", [ + { label: "Cancel", action: false }, + { label: "Clear", primary: true, action: true } + ]); + if (!ok) + return; + } + file = new MonoDisplayFile([]); + activeSecIndex = null; + activeElIndex = null; + currentFilename = "untitled"; + const filenameEl = document.getElementById("filename"); + if (filenameEl) + filenameEl.textContent = "untitled"; + localStorage.removeItem(STORAGE_KEY); + markClean(); + triggerPreview(); + import_mithril.default.redraw(); +} +Object.assign(window, { loadBin, exportBin, addSection, clearAll, cycleTheme }); +loadFromStorage(); +triggerPreview(); diff --git a/backup/editor/style.css b/backup/editor/style.css new file mode 100644 index 0000000..49a023e --- /dev/null +++ b/backup/editor/style.css @@ -0,0 +1,682 @@ +:root { + --bg: #111; + --bg-bar: #1a1a1a; + --bg-raised: #161616; + --bg-hover: #1b1b1b; + --bg-hover2: #222; + --bg-sunken: #141414; + --bg-accent: #141e14; + --bg-active: #1e2e1e; + --bg-deep: #0f0f0f; + --bg-input: #0a0a0a; + --bg-canvas: #000; + --bg-xhover: #1e1010; + --bg-overlay: rgba(0,0,0,.6); + --bd: #2a2a2a; + --bd-inner: #1e1e1e; + --bd-deep: #1c1c1c; + --bd-card: #222; + --bd-btn: #2d2d2d; + --bd-type: #252525; + --bd-active: #2b3d2b; + --bd-hover: #444; + --bd-strong: #333; + --tx: #ccc; + --tx-hover: #bbb; + --tx-sub: #888; + --tx-label: #777; + --tx-meta: #666; + --tx-dim: #555; + --tx-faint: #444; + --tx-ghost: #333; + --tx-file: #3a3a3a; + --tx-empty: #2e2e2e; + --ac: #33ff66; + --dirty: #886622 +} + +@media (prefers-color-scheme: light) { + :root:not([data-theme="dark"]) { + --bg: #f5f5f5; + --bg-bar: #e8e8e8; + --bg-raised: #efefef; + --bg-hover: #e5e5e5; + --bg-hover2: #e0e0e0; + --bg-sunken: #ebebeb; + --bg-accent: #e8f5e8; + --bg-active: #dff0df; + --bg-deep: #f0f0f0; + --bg-input: #fff; + --bg-canvas: #fff; + --bg-xhover: #ffe8e8; + --bg-overlay: rgba(0,0,0,.4); + --bd: #d0d0d0; + --bd-inner: #ddd; + --bd-deep: #e0e0e0; + --bd-card: #ddd; + --bd-btn: #ccc; + --bd-type: #d8d8d8; + --bd-active: #7dc87d; + --bd-hover: #bbb; + --bd-strong: #ccc; + --tx: #222; + --tx-hover: #333; + --tx-sub: #555; + --tx-label: #666; + --tx-meta: #777; + --tx-dim: #888; + --tx-faint: #999; + --tx-ghost: #aaa; + --tx-file: #aaa; + --tx-empty: #bbb; + --ac: #1a9940; + --dirty: #b37a00 + } +} + +[data-theme="light"] { + --bg: #f5f5f5; + --bg-bar: #e8e8e8; + --bg-raised: #efefef; + --bg-hover: #e5e5e5; + --bg-hover2: #e0e0e0; + --bg-sunken: #ebebeb; + --bg-accent: #e8f5e8; + --bg-active: #dff0df; + --bg-deep: #f0f0f0; + --bg-input: #fff; + --bg-canvas: #fff; + --bg-xhover: #ffe8e8; + --bg-overlay: rgba(0,0,0,.4); + --bd: #d0d0d0; + --bd-inner: #ddd; + --bd-deep: #e0e0e0; + --bd-card: #ddd; + --bd-btn: #ccc; + --bd-type: #d8d8d8; + --bd-active: #7dc87d; + --bd-hover: #bbb; + --bd-strong: #ccc; + --tx: #222; + --tx-hover: #333; + --tx-sub: #555; + --tx-label: #666; + --tx-meta: #777; + --tx-dim: #888; + --tx-faint: #999; + --tx-ghost: #aaa; + --tx-file: #aaa; + --tx-empty: #bbb; + --ac: #1a9940; + --dirty: #b37a00 +} + +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0 +} + +body { + background: var(--bg); + color: var(--tx); + font-family: monospace; + height: 100vh; + display: flex; + flex-direction: column; + overflow: hidden; + font-size: 12px +} + +#topbar { + height: 36px; + background: var(--bg-bar); + border-bottom: 1px solid var(--bd); + display: flex; + align-items: center; + gap: 8px; + padding: 0 12px; + flex-shrink: 0 +} + +#topbar .app-name { + color: var(--tx-dim); + font-size: 11px; + letter-spacing: .12em; + text-transform: uppercase; + margin-right: 4px +} + +.tb-btn { + background: var(--bg-raised); + color: var(--tx-sub); + border: 1px solid var(--bd-btn); + border-radius: 3px; + padding: 3px 10px; + font-family: monospace; + font-size: 11px; + cursor: pointer; + display: flex; + align-items: center; + gap: 5px; + white-space: nowrap +} + +.tb-btn:hover { + background: var(--bg-hover2); + color: var(--tx-hover); + border-color: var(--bd-hover) +} + +.tb-btn svg { + width: 13px; + height: 13px; + stroke: currentColor; + fill: none; + stroke-width: 1.8; + stroke-linecap: round; + stroke-linejoin: round; + flex-shrink: 0 +} + +#file-input { + display: none +} + +#filename { + color: var(--tx-file); + font-size: 11px; + margin-left: 2px; + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap +} + +.tb-sep { + width: 1px; + height: 18px; + background: var(--bd) +} + +.dirty { + color: var(--dirty) !important +} + +#main { + flex: 1; + display: flex; + min-height: 0 +} + +#left { + width: 48%; + border-right: 1px solid var(--bd-inner); + display: flex; + flex-direction: column; + padding: 14px; + gap: 10px; + overflow-y: auto; + flex-shrink: 0 +} + +#display-box { + width: 100%; + aspect-ratio: 2/1; + background: var(--bg-canvas); + border: 1px solid var(--bd); + border-radius: 3px; + overflow: hidden +} + +#canvas_root { + width: 100%; + height: 100%; + display: block; + image-rendering: pixelated; + image-rendering: crisp-edges +} + +.pv-label { + font-size: 10px; + color: var(--tx-ghost); + letter-spacing: .1em; + text-transform: uppercase +} + +#demo-btns { + display: flex; + flex-wrap: wrap; + gap: 5px; + margin-top: 6px +} +#demo-btns > div { + display: contents; +} + +canvas.pixel-editor { + display: block; + cursor: crosshair; + image-rendering: pixelated; + max-width: 100%; + border: 1px solid var(--bd-inner); +} +.anim-editor { + display: flex; + flex-direction: column; + gap: 4px; +} +.anim-frame-tabs { + display: flex; + flex-wrap: wrap; + gap: 3px; + align-items: center; +} +.anim-frame-tab { + display: flex; + align-items: center; + gap: 2px; + padding: 2px 6px; + border: 1px solid var(--bd-inner); + border-radius: 3px; + cursor: pointer; + font-size: 11px; + user-select: none; +} +.anim-frame-tab.active { + border-color: var(--accent, #EC0); + color: var(--accent, #EC0); +} +.x-btn-sm { + background: none; + border: none; + color: inherit; + cursor: pointer; + padding: 0 2px; + font-size: 13px; + line-height: 1; + opacity: 0.6; +} +.x-btn-sm:hover { opacity: 1; } + +#sec-meta { + background: var(--bg-sunken); + border: 1px solid var(--bd-inner); + border-radius: 3px; + padding: 8px 10px; + display: flex; + flex-direction: column; + gap: 6px +} + +.meta-row { + display: flex; + align-items: center; + gap: 8px; + font-size: 11px +} + +.meta-row label { + color: var(--tx-faint); + min-width: 90px +} + +.meta-val { + color: var(--tx-meta) +} + +.green { + color: var(--ac) +} + +select.meta-val { + background: var(--bg-input); + color: var(--ac); + border: 1px solid var(--bd-inner); + border-radius: 3px; + padding: 1px 4px; +} + +.flag-check { + display: flex; + align-items: center; + gap: 5px; + font-size: 11px; + color: var(--tx-dim); + cursor: pointer +} + +.flag-check input { + accent-color: var(--ac); + cursor: pointer +} + +#right { + flex: 1; + display: flex; + flex-direction: column; + min-height: 0; + overflow: hidden +} + +#right-hdr { + height: 36px; + background: var(--bg-sunken); + border-bottom: 1px solid var(--bd-inner); + display: flex; + align-items: center; + padding: 0 10px; + gap: 6px; + flex-shrink: 0 +} + +#right-hdr .rh-title { + flex: 1; + color: var(--tx-faint); + font-size: 11px; + letter-spacing: .08em; + text-transform: uppercase +} + +#sections-wrap { + flex: 1; + overflow-y: auto; + padding: 8px +} + +.empty-state { + color: var(--tx-empty); + font-size: 11px; + padding: 28px 16px; + text-align: center; + line-height: 2 +} + +/* section card */ +.sec-card { + border: 1px solid var(--bd-card); + border-radius: 3px; + margin-bottom: 5px; + overflow: hidden +} + +.sec-card.active { + border-color: var(--bd-active) +} + +.sec-hdr { + display: flex; + align-items: center; + gap: 7px; + padding: 6px 8px; + cursor: pointer; + user-select: none; + background: var(--bg-raised); + position: relative +} + +.sec-hdr:hover { + background: var(--bg-hover) +} + +.sec-card.active .sec-hdr, +.sec-hdr.active { + background: var(--bg-active) +} + +.sec-arrow { + color: var(--tx-ghost); + font-size: 13px; + line-height: 1; + transition: transform .12s; + flex-shrink: 0 +} + +.sec-card.open .sec-arrow { + transform: rotate(90deg) +} + +.sec-label { + flex: 1; + color: var(--tx-label); + font-size: 11px +} + +.sec-badge { + font-size: 10px; + color: var(--ac); + border: 1px solid var(--bd-active); + background: var(--bg-accent); + border-radius: 2px; + padding: 1px 6px; + flex-shrink: 0 +} + +/* red × - section & element */ +.x-btn { + background: none; + border: none; + color: var(--bd); + cursor: pointer; + font-size: 15px; + line-height: 1; + padding: 2px 5px; + border-radius: 2px; + flex-shrink: 0; + transition: color .1s, background .1s +} + +.x-btn:hover { + color: #ff4444; + background: var(--bg-xhover) +} + +/* element */ +.el-list { + background: var(--bg-deep); + border-top: 1px solid var(--bd-deep); + padding: 6px +} + +.el-item { + border: 1px solid var(--bd-inner); + border-radius: 3px; + background: var(--bg-sunken); + margin-bottom: 4px; + overflow: hidden +} + +.el-item.active { + border-color: var(--ac) +} + +.el-item-hdr { + display: flex; + align-items: center; + gap: 6px; + padding: 5px 8px; + cursor: pointer +} + +.el-item-hdr:hover { + background: var(--bg-hover) +} + +.el-type { + font-size: 10px; + color: var(--tx-meta); + border: 1px solid var(--bd-type); + border-radius: 2px; + padding: 1px 6px; + flex-shrink: 0 +} + +.el-item.active .el-type { + color: var(--ac); + border-color: var(--bd-active) +} + +.el-name { + flex: 1; + color: var(--tx-dim); + font-size: 11px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 120px +} + +.el-fields { + padding: 6px 8px 8px; + border-top: 1px solid var(--bg-bar); + display: flex; + flex-direction: column; + gap: 5px +} + +.fields-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 4px 10px +} + +.field { + display: flex; + flex-direction: column; + gap: 2px +} + +.field.full { + grid-column: 1/-1 +} + +.field>label { + font-size: 10px; + color: var(--tx-faint) +} + +.field input[type=text], +.field input[type=number], +.field input[type=datetime-local], +.field select, +.field textarea { + background: var(--bg-input); + color: var(--tx-sub); + border: 1px solid var(--bd-card); + border-radius: 2px; + padding: 3px 6px; + font-family: monospace; + font-size: 11px; + width: 100%; + outline: none +} + +.field input:focus, +.field select:focus, +.field textarea:focus { + border-color: var(--ac); + color: var(--tx) +} + +.field textarea { + resize: vertical; + min-height: 44px; + line-height: 1.4 +} + +.field select option { + background: var(--bg-bar); + color: var(--tx-ghost) +} + +.flags-row { + display: flex; + flex-wrap: wrap; + gap: 10px; + padding: 2px 0 +} + +.add-el { + background: transparent; + color: var(--tx-ghost); + border: 1px dashed var(--bd-card); + border-radius: 3px; + padding: 5px 8px; + font-family: monospace; + font-size: 11px; + cursor: pointer; + width: 100%; + text-align: center; + margin-top: 2px +} + +.add-el:hover { + color: var(--tx-sub); + border-color: var(--bd-hover) +} + +/* confirm dialog */ +#confirm-overlay { + display: none; + position: fixed; + inset: 0; + background: var(--bg-overlay); + z-index: 100; + align-items: center; + justify-content: center +} + +#confirm-overlay.show { + display: flex +} + +#confirm-box { + background: var(--bg-bar); + border: 1px solid var(--bd-strong); + border-radius: 4px; + padding: 20px 24px; + max-width: 340px; + width: 90%; + display: flex; + flex-direction: column; + gap: 14px +} + +#confirm-msg { + color: var(--tx-ghost); + font-size: 12px; + line-height: 1.6 +} + +#confirm-btns { + display: flex; + gap: 8px; + justify-content: flex-end +} + +.cb { + background: var(--bg-raised); + color: var(--tx-sub); + border: 1px solid var(--bd-btn); + border-radius: 3px; + padding: 4px 14px; + font-family: monospace; + font-size: 11px; + cursor: pointer +} + +.cb:hover { + background: var(--bg-hover2); + color: var(--tx-hover) +} + +.cb.primary { + border-color: var(--bd-active); + color: var(--ac) +} + +.cb.primary:hover { + background: var(--bg-active) +} diff --git a/backup/favicon.ico b/backup/favicon.ico new file mode 100644 index 0000000..7cfea89 Binary files /dev/null and b/backup/favicon.ico differ diff --git a/backup/gpn/endscreen.bin b/backup/gpn/endscreen.bin new file mode 100644 index 0000000..57117a0 Binary files /dev/null and b/backup/gpn/endscreen.bin differ diff --git a/backup/gpn/monoformat_bithelpers.php b/backup/gpn/monoformat_bithelpers.php new file mode 100644 index 0000000..dbddb64 --- /dev/null +++ b/backup/gpn/monoformat_bithelpers.php @@ -0,0 +1,43 @@ += (PHP_INT_SIZE * 8)) { + return false; + } + $mask = 1 << $bit; + return ($value & $mask) == $mask; +} + +function setBit(int &$value, int $bit) { + if ($bit < 0 || $bit >= (PHP_INT_SIZE * 8)) { + return; + } + $mask = 1 << $bit; + $value |= $mask; +} + +function unsetBit(int &$value, int $bit) { + if ($bit < 0 || $bit >= (PHP_INT_SIZE * 8)) { + return; + } + $mask = 1 << $bit; + $value &= ~$mask; +} + +function maybeSetBit(int &$value, int $bit, int $set) { + if ($bit < 0 || $bit >= (PHP_INT_SIZE * 8)) { + return; + } + $mask = 1 << $bit; + if ($set) { + $value |= $mask; + } else { + $value &= ~$mask; + } +} + +} // namespace monoformat + +?> diff --git a/backup/gpn/monoformat_schema.php b/backup/gpn/monoformat_schema.php new file mode 100644 index 0000000..a26b3a4 --- /dev/null +++ b/backup/gpn/monoformat_schema.php @@ -0,0 +1,213 @@ +getProperties(\ReflectionProperty::IS_PUBLIC); + $result = new $myClass(); + foreach ($properties as $property) { + $name = $property->getName(); + if (array_key_exists($name, $json)) { + $result->$name = (bool) $json[$name]; + } + } + return $result; + } + + public function toJSON(): array { + $result = []; + $myClass = static::class; + $reflect = new \ReflectionClass($myClass); + $properties = $reflect->getProperties(\ReflectionProperty::IS_PUBLIC); + foreach ($properties as $property) { + $name = $property->getName(); + $result[$name] = $this->$name; + } + return $result; + } +} + +enum SectionType : int { + case AlwaysDrawn = 1; + case TimeBasedDrawn = 2; + case MultiTimeBasedDrawn = 3; + case ExpiryDate = 31; + case CustomFont = 32; + + public static function fromName(string $name): SectionType { + foreach (self::cases() as $case) { + if ($case->name === $name) { + return $case; + } + } + throw new \ValueError("$name is not a valid backing value for enum " . self::class); + } +} + +enum ElementType : int { + case Image = 1; + case Animation = 2; + case HScrollImage = 3; + case VScrollImage = 4; + case Line = 5; + case Box = 6; + case ClippedText = 16; + case HScrollText = 17; + //case VScrollText = 18; + case CurrentTime = 32; + + public static function fromName(string $name): SectionType { + foreach (self::cases() as $case) { + if ($case->name === $name) { + return $case; + } + } + throw new \ValueError("$name is not a valid backing value for enum " . self::class); + } +} + +class ScrollFlags { + use FlagStruct; + + public bool $endless = false; + public bool $invertDirection = false; + public bool $padBefore = false; + public bool $padAfter = false; + + public static function fromBitField(int $bitfield): ScrollFlags { + $result = new ScrollFlags(); + $result->endless = isBitSet($bitfield, 0); + $result->invertDirection = isBitSet($bitfield, 1); + $result->padBefore = isBitSet($bitfield, 2); + $result->padAfter = isBitSet($bitfield, 3); + return $result; + } + + public function toBitField(): int { + $result = 0; + maybeSetBit($result, 0, $this->endless); + maybeSetBit($result, 1, $this->invertDirection); + maybeSetBit($result, 2, $this->padBefore); + maybeSetBit($result, 3, $this->padAfter); + return $result; + } +} + +enum LineStyle : int { + case Solid = 0; + + public static function fromName(string $name): SectionType { + foreach (self::cases() as $case) { + if ($case->name === $name) { + return $case; + } + } + throw new \ValueError("$name is not a valid backing value for enum " . self::class); + } +} + +class LineFlags { + use FlagStruct; + + public bool $dark = false; + + public static function fromBitField(int $bitfield): LineFlags { + $result = new LineFlags(); + $result->dark = isBitSet($bitfield, 0); + return $result; + } + + public function toBitField(): int { + $result = 0; + maybeSetBit($result, 0, $this->dark); + return $result; + } +} + +enum FillPatternStyle : int { + case Solid = 0; + + public static function fromName(string $name): SectionType { + foreach (self::cases() as $case) { + if ($case->name === $name) { + return $case; + } + } + throw new \ValueError("$name is not a valid backing value for enum " . self::class); + } +} + +class FillFlags { + use FlagStruct; + + public bool $dark = false; + + public static function fromBitField(int $bitfield): FillFlags { + $result = new FillFlags(); + $result->dark = isBitSet($bitfield, 0); + return $result; + } + + public function toBitField(): int { + $result = 0; + maybeSetBit($result, 0, $this->dark); + return $result; + } +} + +class TextFlags { + use FlagStruct; + + public bool $dark = false; + public bool $autoWordWrap = false; + + public static function fromBitField(int $bitfield): TextFlags { + $result = new TextFlags(); + $result->dark = isBitSet($bitfield, 0); + $result->autoWordWrap = isBitSet($bitfield, 1); + return $result; + } + + public function toBitField(): int { + $result = 0; + maybeSetBit($result, 0, $this->dark); + maybeSetBit($result, 1, $this->autoWordWrap); + return $result; + } +} + +class TimeDisplayFlags { + use FlagStruct; + + public bool $use12h = false; + public bool $showHours = false; + public bool $showMinutes = false; + public bool $showSeconds = false; + + public static function fromBitField(int $bitfield): TimeDisplayFlags { + $result = new TimeDisplayFlags(); + $result->use12h = isBitSet($bitfield, 0); + $result->showHours = isBitSet($bitfield, 1); + $result->showMinutes = isBitSet($bitfield, 2); + $result->showSeconds = isBitSet($bitfield, 3); + return $result; + } + + public function toBitField(): int { + $result = 0; + maybeSetBit($result, 0, $this->use12h); + maybeSetBit($result, 1, $this->showHours); + maybeSetBit($result, 2, $this->showMinutes); + maybeSetBit($result, 3, $this->showSeconds); + return $result; + } +} + +} // namespace monoformat + +?> diff --git a/backup/gpn/monoformat_structured.php b/backup/gpn/monoformat_structured.php new file mode 100644 index 0000000..cef9b83 --- /dev/null +++ b/backup/gpn/monoformat_structured.php @@ -0,0 +1,1925 @@ += $n) { + break; + } + maybeSetBit($byte, $j, $pixels[$k]); + } + $result .= chr($byte); + } + return $result; +} + +function unserializePixels(int $n, string $serialized): array { + $nBytes = intdiv($n + 7, 8); + if (strlen($serialized) != $nBytes) { + $actual = strlen($serializd); + throw new \ValueError("Could not unserialize a pixel bitmap: the size $actual does not equal the expected $nBytes"); + } + $result = array_fill(0, $n, false); + for ($i = 0; $i < $nBytes; ++$i) { + $byte = ord($serialized[$i]); + for ($j = 0; $j < 8; ++$j) { + $k = $i * 8 + $j; + if ($k >= $n) { + break; + } + $result[$k] = isBitSet($byte, $j); + } + } + return $result; +} + +function packU24LE(int $value) { + return substr(pack("V", $value), 0, 3); +} + +function unpackU24LE(string $parts) { + return array_values(unpack("V", $parts . "\x00"))[0]; +} + +function alignSerialized(string $data, int $alignment) { + $n = strlen($data); + if ($n % $alignment) { + $target = intdiv($n + $alignment - 1, $alignment) * $alignment; + $remaining = $target - $n; + $data .= str_repeat("\x00", $remaining); + } + return $data; +} + +abstract class Section { + abstract public function sectionType(): SectionType; + abstract public function minimumFormatVersion(): int; + abstract public function serializeBinary(int $formatVersion): string; + abstract public function serializeJSON(int $formatVersion): array; +} + +abstract class Element { + abstract public function elementType(): ElementType; + abstract public function minimumFormatVersion(): int; + abstract public function serializeBinary(int $formatVersion): string; + abstract public function serializeJSON(int $formatVersion): array; +} + +class ImageElement extends Element { + private int $m_x = 0; + private int $m_y = 0; + private int $m_width = 0; + private int $m_height = 0; + private array $m_image = []; + + public function x(): int { + return $this->m_x; + } + + public function y(): int { + return $this->m_y; + } + + public function width(): int { + return $this->m_width; + } + + public function height(): int { + return $this->m_height; + } + + public function image(): array { + return $this->m_image; + } + + public function setImage(array $image) { + $n = $this->m_width * $this->m_height; + if (count($image) != $n) { + $actual = count($image); + throw new \ValueError("Cannot update the image of an ImageElement: the number of pixels supplied, $actual, is not the expected number, $n"); + } + $this->m_image = $image; + } + + public function __construct(int $x, int $y, int $width, int $height) { + $this->m_x = $x; + $this->m_y = $y; + $this->m_width = $width; + $this->m_height = $height; + $n = $width * $height; + $this->m_image = array_fill(0, $n, false); + } + + public function elementType(): ElementType { + return ElementType::Image; + } + + public function minimumFormatVersion(): int { + return 1; + } + + public function serializeBinary(int $formatVersion): string { + $result = pack("vvvvvv", + ElementType::Image->value, + $this->m_x, + $this->m_y, + $this->m_width, + $this->m_height, + 0); + $result .= serializePixels($this->m_image); + return alignSerialized($result, 4); + } + + public function serializeJSON(int $formatVersion): array { + $result = []; + $result["type"] = ElementType::Image->name; + $result["x"] = $this->m_x; + $result["y"] = $this->m_y; + $result["width"] = $this->m_width; + $result["height"] = $this->m_height; + $result["image"] = $this->m_image; + } + + public static function parseBinary(string &$data, int $formatVersion): ImageElement { + [$type, $x, $y, $width, $height, $reserved] = array_values(unpack("v6", substr($data, 0, 12))); + $type = ElementType::from($type); + if ($type != ElementType::Image) { + throw new \ValueError("Could not unserialize an image element: type mismatch"); + } + $n = $width * $height; + $nBytes = intdiv($n + 7, 8); + $nAligned = intdiv($nBytes + 3, 4) * 4; + $pixels = unserializePixels($width * $height, substr($data, 12, $nBytes)); + $result = new ImageElement($x, $y, $width, $height); + $result->setImage($pixels); + $data = substr($data, 12 + $nAligned); + return $result; + } + + public static function parseJSON(array $element, int $formatVersion): ImageElement { + $type = ElementType::fromString($element["type"]); + if ($type != ElementType::Image) { + throw new \ValueError("Could not unserialize an image element: type mismatch"); + } + $result = new ImageElement($element["x"], $element["y"], $element["width"], $element["height"]); + $result->setImage($element["image"]); + return $result; + } +} + +class AnimationElement extends Element { + private int $m_x = 0; + private int $m_y = 0; + private int $m_width = 0; + private int $m_height = 0; + private int $m_updateInterval = 0; + private array $m_frames = []; + + public function x(): int { + return $this->m_x; + } + + public function y(): int { + return $this->m_y; + } + + public function width(): int { + return $this->m_width; + } + + public function height(): int { + return $this->m_height; + } + + public function numberOfFrames(): int { + return count($this->m_frames); + } + + public function updateInterval(): int { + return $this->m_updateInterval; + } + + public function image(int $n): array { + if ($n >= count($this->m_frames)) { + $max = count($this->m_frames) - 1; + throw new \ValueError("Cannot retrieve a frame of an AnimationElement: the frame index $n is not in the range [0..$max]"); + } + return $this->m_frames[$n]; + } + + public function setImage(int $n, array $image) { + if ($n >= count($this->m_frames)) { + $max = count($this->m_frames) - 1; + throw new \ValueError("Cannot retrieve a frame of an AnimationElement: the frame index $n is not in the range [0..$max]"); + } + $nPixels = $this->m_width * $this->m_height; + if (count($image) != $nPixels) { + $actual = count($image); + throw new \ValueError("Cannot update a frame of an AnimationElement: the number of pixels supplied, $actual, is not the expected number, $nPixels"); + } + $this->m_frames[$n] = $image; + } + + public function __construct(int $x, int $y, int $width, int $height, int $numberOfFrames, int $updateInterval) { + $this->m_x = $x; + $this->m_y = $y; + $this->m_width = $width; + $this->m_height = $height; + $this->m_updateInterval = $updateInterval; + $n = $width * $height; + for ($i = 0; $i < $numberOfFrames; ++$i) { + array_push($this->m_frames, array_fill(0, $n, false)); + } + } + + public function elementType(): ElementType { + return ElementType::Animation; + } + + public function minimumFormatVersion(): int { + return 1; + } + + public function serializeBinary(int $formatVersion): string { + $result = pack("vvvvvvvv", + ElementType::Animation->value, + $this->m_x, + $this->m_y, + $this->m_width, + $this->m_height, + count($this->m_frames), + $this->m_updateInterval, + 0); + foreach ($this->m_frames as $frame) { + $result .= serializePixels($frame); + } + return alignSerialized($result, 4); + } + + public function serializeJSON(int $formatVersion): array { + $result = []; + $result["type"] = ElementType::Animation->name; + $result["x"] = $this->m_x; + $result["y"] = $this->m_y; + $result["width"] = $this->m_width; + $result["height"] = $this->m_height; + $result["updateInterval"] = $this->m_updateInterval; + $result["frames"] = $this->m_frames; + } + + public static function parseBinary(string &$data, int $formatVersion): AnimationElement { + [$type, $x, $y, $width, $height, $nFrames, $updateInterval, $reserved] = array_values(unpack("v8", substr($data, 0, 16))); + $type = ElementType::from($type); + if ($type != ElementType::Animation) { + throw new \ValueError("Could not unserialize an animation element: type mismatch"); + } + $n = $width * $height; + $nBytes = intdiv($n + 7, 8); + $nAligned = intdiv($nBytes * $nFrames + 3, 4) * 4; + $frames = []; + $result = new AnimationElement($x, $y, $width, $height, $nFrames, $updateInterval); + for ($i = 0; $i < $nFrames; ++$i) { + $pixels = unserializePixels($width * $height, substr($data, 16 + $i * $nBytes, $nBytes)); + $result->setImage($i, $pixels); + } + $data = substr($data, 16 + $nAligned); + return $result; + } + + public static function parseJSON(array $element, int $formatVersion): AnimationElement { + $type = ElementType::fromString($element["type"]); + if ($type != ElementType::Animation) { + throw new \ValueError("Could not unserialize an animation element: type mismatch"); + } + $result = new AnimationElement($element["x"], $element["y"], $element["width"], $element["height"], count($element["frames"]), $element["updateInterval"]); + for ($i = 0; $i < count($element["frames"]); ++$i) { + $result->setImage($i, $element["frames"][$i]); + } + return $result; + } +} + +class HScrollImageElement extends Element { + private int $m_x = 0; + private int $m_y = 0; + private int $m_width = 0; + private int $m_height = 0; + private int $m_contentWidth = 0; + private ScrollFlags $m_flags; + private int $m_scrollSpeed = 0; + private array $m_image = []; + + public function x(): int { + return $this->m_x; + } + + public function y(): int { + return $this->m_y; + } + + public function width(): int { + return $this->m_width; + } + + public function height(): int { + return $this->m_height; + } + + public function contentWidth(): int { + return $this->m_contentWidth; + } + + public function flags(): ScrollFlags { + return $this->m_flags; + } + + public function scrollSpeed(): int { + return $this->m_scrollSpeed; + } + + public function image(): array { + return $this->m_image; + } + + public function setImage(array $image) { + $n = $this->m_contentWidth * $this->m_height; + if (count($image) != $n) { + $actual = count($image); + throw new \ValueError("Cannot update the image of an HScrollImageElement: the number of pixels supplied, $actual, is not the expected number, $n"); + } + $this->m_image = $image; + } + + public function __construct(int $x, int $y, int $width, int $height, int $contentWidth, ScrollFlags $flags, int $scrollSpeed) { + $this->m_x = $x; + $this->m_y = $y; + $this->m_width = $width; + $this->m_height = $height; + $this->m_contentWidth = $contentWidth; + $this->m_flags = $flags; + $this->m_scrollSpeed = $scrollSpeed; + $n = $contentWidth * $height; + $this->m_image = array_fill(0, $n, false); + } + + public function elementType(): ElementType { + return ElementType::HScrollImage; + } + + public function minimumFormatVersion(): int { + return 1; + } + + public function serializeBinary(int $formatVersion): string { + $result = pack("vvvvvvCCv", + ElementType::HScrollImage->value, + $this->m_x, + $this->m_y, + $this->m_width, + $this->m_height, + $this->m_contentWidth, + $this->m_flags->toBitField(), + $this->m_scrollSpeed, + 0); + $result .= serializePixels($this->m_image); + return alignSerialized($result, 4); + } + + public function serializeJSON(int $formatVersion): array { + $result = []; + $result["type"] = ElementType::HScrollImage->name; + $result["x"] = $this->m_x; + $result["y"] = $this->m_y; + $result["width"] = $this->m_width; + $result["height"] = $this->m_height; + $result["contentWidth"] = $this->m_contentWidth; + $result["flags"] = $this->m_flags->toJSON(); + $result["scrollSpeed"] = $this->m_scrollSpeed; + $result["image"] = $this->m_image; + } + + public static function parseBinary(string &$data, int $formatVersion): HScrollImageElement { + [$type, $x, $y, $width, $height, $contentWidth] = array_values(unpack("v6", substr($data, 0, 12))); + $type = ElementType::from($type); + if ($type != ElementType::HScrollImage) { + throw new \ValueError("Could not unserialize a horizontally scrolling image element: type mismatch"); + } + [$flags, $scrollSpeed] = array_values(unpack("C2", substr($data, 12, 2))); + $flags = TextFlags::fromBitField($flags); + $n = $contentWidth * $height; + $nBytes = intdiv($n + 7, 8); + $nAligned = intdiv($nBytes + 3, 4) * 4; + $pixels = unserializePixels($n, substr($data, 16, $nBytes)); + $result = new HScrollImageElement($x, $y, $width, $height, $contentWidth, $flags, $scrollSpeed); + $result->setImage($pixels); + $data = substr($data, 16 + $nAligned); + return $result; + } + + public static function parseJSON(array $element, int $formatVersion): HScrollImageElement { + $type = ElementType::fromString($element["type"]); + if ($type != ElementType::HScrollImage) { + throw new \ValueError("Could not unserialize a horizontally scrolling image element: type mismatch"); + } + $result = new HScrollImageElement($element["x"], $element["y"], $element["width"], $element["height"], $element["contentWidth"], + ScrollFlags::fromJSON($element["flags"]), $element["scrollSpeed"]); + $result->setImage($element["image"]); + return $result; + } +} + +class VScrollImageElement extends Element { + private int $m_x = 0; + private int $m_y = 0; + private int $m_width = 0; + private int $m_height = 0; + private int $m_contentHeight = 0; + private ScrollFlags $m_flags; + private int $m_scrollSpeed = 0; + private array $m_image = []; + + public function x(): int { + return $this->m_x; + } + + public function y(): int { + return $this->m_y; + } + + public function width(): int { + return $this->m_width; + } + + public function height(): int { + return $this->m_height; + } + + public function contentHeight(): int { + return $this->m_contentHeight; + } + + public function flags(): ScrollFlags { + return $this->m_flags; + } + + public function scrollSpeed(): int { + return $this->m_scrollSpeed; + } + + public function image(): array { + return $this->m_image; + } + + public function setImage(array $image) { + $n = $this->m_width * $this->m_contentHeight; + if (count($image) != $n) { + $actual = count($image); + throw new \ValueError("Cannot update the image of a VScrollImageElement: the number of pixels supplied, $actual, is not the expected number, $n"); + } + $this->m_image = $image; + } + + public function __construct(int $x, int $y, int $width, int $height, int $contentHeight, ScrollFlags $flags, int $scrollSpeed) { + $this->m_x = $x; + $this->m_y = $y; + $this->m_width = $width; + $this->m_height = $height; + $this->m_contentHeight = $contentHeight; + $this->m_flags = $flags; + $this->m_scrollSpeed = $scrollSpeed; + $n = $width * $contentHeight; + $this->m_image = array_fill(0, $n, false); + } + + public function elementType(): ElementType { + return ElementType::VScrollImage; + } + + public function minimumFormatVersion(): int { + return 1; + } + + public function serializeBinary(int $formatVersion): string { + $result = pack("vvvvvvCCv", + ElementType::VScrollImage->value, + $this->m_x, + $this->m_y, + $this->m_width, + $this->m_height, + $this->m_contentHeight, + $this->m_flags->toBitField(), + $this->m_scrollSpeed, + 0); + $result .= serializePixels($this->m_image); + return alignSerialized($result, 4); + } + + public function serializeJSON(int $formatVersion): array { + $result = []; + $result["type"] = ElementType::HScrollImage->name; + $result["x"] = $this->m_x; + $result["y"] = $this->m_y; + $result["width"] = $this->m_width; + $result["height"] = $this->m_height; + $result["contentHeight"] = $this->m_contentHeight; + $result["flags"] = $this->m_flags->toJSON(); + $result["scrollSpeed"] = $this->m_scrollSpeed; + $result["image"] = $this->m_image; + } + + public static function parseBinary(string &$data, int $formatVersion): VScrollImageElement { + [$type, $x, $y, $width, $height, $contentHeight] = array_values(unpack("v6", substr($data, 0, 12))); + $type = ElementType::from($type); + if ($type != ElementType::VScrollImage) { + throw new \ValueError("Could not unserialize a vertically scrolling image element: type mismatch"); + } + [$flags, $scrollSpeed] = array_values(unpack("C2", substr($data, 12, 2))); + $flags = TextFlags::fromBitField($flags); + $n = $width * $contentHeight; + $nBytes = intdiv($n + 7, 8); + $nAligned = intdiv($nBytes + 3, 4) * 4; + $pixels = unserializePixels($n, substr($data, 16, $nBytes)); + $result = new VScrollImageElement($x, $y, $width, $height, $contentHeight, $flags, $scrollSpeed); + $result->setImage($pixels); + $data = substr($data, 16 + $nAligned); + return $result; + } + + public static function parseJSON(array $element, int $formatVersion): VScrollImageElement { + $type = ElementType::fromString($element["type"]); + if ($type != ElementType::VScrollImage) { + throw new \ValueError("Could not unserialize a vertically scrolling image element: type mismatch"); + } + $result = new VScrollImageElement($element["x"], $element["y"], $element["width"], $element["height"], $element["contentHeight"], + ScrollFlags::fromJSON($element["flags"]), $element["scrollSpeed"]); + $result->setImage($element["image"]); + return $result; + } +} + +class LineElement extends Element { + private int $m_originX = 0; + private int $m_orignY = 0; + private int $m_targetX = 0; + private int $m_targetY = 0; + private LineStyle $m_lineStyle = LineStyle::Solid; + private LineFlags $m_flags; + + public function originX(): int { + return $this->m_originX; + } + + public function originY(): int { + return $this->m_originY; + } + + public function targetX(): int { + return $this->m_targetX; + } + + public function targetY(): int { + return $this->m_targetY; + } + + public function lineStyle(): LineStyle { + return $this->m_lineStyle; + } + + public function flags(): LineFlags { + return $this->m_flags; + } + + public function __construct(int $originX, int $originY, int $targetX, int $targetY, LineStyle $lineStyle, LineFlags $flags) { + $this->m_originX = $originX; + $this->m_originY = $originY; + $this->m_targetX = $targetX; + $this->m_targetY = $targetY; + $this->m_lineStyle = $lineStyle; + $this->m_flags = $flags; + } + + public function elementType(): ElementType { + return ElementType::Line; + } + + public function minimumFormatVersion(): int { + if ($this->m_flags->toBitField() != 0) { + return 2; + } else { + return 1; + } + } + + public function serializeBinary(int $formatVersion): string { + $result = pack("vvvvvCC", + ElementType::Line->value, + $this->m_originX, + $this->m_originY, + $this->m_targetX, + $this->m_targetY, + $this->m_lineStyle->value, + $this->m_flags->toBitField()); + return alignSerialized($result, 4); + } + + public function serializeJSON(int $formatVersion): array { + $result = []; + $result["type"] = ElementType::Line->name; + $result["originX"] = $this->m_originX; + $result["originY"] = $this->m_originY; + $result["targetX"] = $this->m_targetX; + $result["targetY"] = $this->m_targetY; + $result["lineStyle"] = $this->m_lineStyle->toJSON(); + $result["flags"] = $this->m_flags->toJSON(); + return $result; + } + + public static function parseBinary(string &$data, int $formatVersion): LineElement { + [$type, $originX, $originY, $targetX, $targetY] = array_values(unpack("v5", substr($data, 0, 10))); + $data = substr($data, 10); + $pos = 10; + $type = ElementType::from($type); + if ($type != ElementType::Line) { + throw new \ValueError("Could not unserialize a line element: type mismatch"); + } + [$lineStyle, $flags] = array_values(unpack("C2", substr($data, 0, 2))); + $data = substr($data, 2); + $pos += 2; + $lineStyle = LineStyle::from($lineStyle); + $flags = LineFlags::fromBitField($flags); + $alignedPos = intdiv($pos + 3, 4) * 4; + $data = substr($data, $alignedPos - $pos); + + $result = new LineElement($originX, $originY, $targetX, $targetY, $lineStyle, $flags); + return $result; + } + + public static function parseJSON(array $element, int $formatVersion): LineElement { + $type = ElementType::fromString($element["type"]); + if ($type != ElementType::Line) { + throw new \ValueError("Could not unserialize a line element: type mismatch"); + } + $flags = new LineFlags(); + $lineStyle = LineStyle::fromName($element["lineStyle"]); + if ($formatVersion >= 2) { + $flags = LineFlags::fromJSON($element["flags"]); + } + $result = new LineElement($element["originX"], $element["originY"], $element["targetX"], $element["targetY"], $lineStyle, $flags); + return $result; + } +} + +class BoxElement extends Element { + private int $m_x = 0; + private int $m_y = 0; + private int $m_width = 0; + private int $m_height = 0; + private FillPattern $m_fillPattern = FillPattern::Solid; + private FillFlags $m_flags; + + public function x(): int { + return $this->m_x; + } + + public function y(): int { + return $this->m_y; + } + + public function width(): int { + return $this->m_width; + } + + public function height(): int { + return $this->m_height; + } + + public function fillPattern(): FillPattern { + return $this->m_fillPattern; + } + + public function flags(): FillFlags { + return $this->m_flags; + } + + public function __construct(int $x, int $y, int $width, int $height, FillPattern $fillPattern, FillFlags $flags) { + $this->m_x = $x; + $this->m_y = $y; + $this->m_width = $width; + $this->m_height = $height; + $this->m_fillPattern = $fillPattern; + $this->m_flags = $flags; + } + + public function elementType(): ElementType { + return ElementType::Box; + } + + public function minimumFormatVersion(): int { + return 2; + } + + public function serializeBinary(int $formatVersion): string { + $result = pack("vvvvvCC", + ElementType::Box->value, + $this->m_x, + $this->m_y, + $this->m_width, + $this->m_height, + $this->m_fillPattern->value, + $this->m_flags->toBitField()); + return alignSerialized($result, 4); + } + + public function serializeJSON(int $formatVersion): array { + $result = []; + $result["type"] = ElementType::Box->name; + $result["x"] = $this->m_x; + $result["y"] = $this->m_y; + $result["width"] = $this->m_width; + $result["height"] = $this->m_height; + $result["fillPattern"] = $this->m_fillPattern->toJSON(); + $result["flags"] = $this->m_flags->toJSON(); + return $result; + } + + public static function parseBinary(string &$data, int $formatVersion): BoxElement { + [$type, $x, $y, $width, $height] = array_values(unpack("v5", substr($data, 0, 10))); + $data = substr($data, 10); + $pos = 10; + $type = ElementType::from($type); + if ($type != ElementType::Box) { + throw new \ValueError("Could not unserialize a box element: type mismatch"); + } + [$fillPattern, $flags] = array_values(unpack("C2", substr($data, 0, 2))); + $data = substr($data, 2); + $pos += 2; + $fillPattern = FillPattern::from($fillPattern); + $flags = FillFlags::fromBitField($flags); + + $alignedPos = intdiv($pos + $len + 3, 4) * 4; + $data = substr($data, $alignedPos - $pos); + + $result = new BoxElement($x, $y, $width, $height, $fillPattern, $flags); + return $result; + } + + public static function parseJSON(array $element, int $formatVersion): BoxElement { + $type = ElementType::fromString($element["type"]); + if ($type != ElementType::Box) { + throw new \ValueError("Could not unserialize a box element: type mismatch"); + } + $fillPattern = FillPattern::fromName($element["fillPattern"]); + $flags = FillFlags::fromJSON($element["flags"]); + $result = new BoxElement($element["x"], $element["y"], $element["width"], $element["height"], $fillPattern, $flags); + return $result; + } +} + +class ClippedTextElement extends Element { + private int $m_x = 0; + private int $m_y = 0; + private int $m_width = 0; + private int $m_height = 0; + private TextFlags $m_textFlags; + private int $m_fontIndex = 0; + private string $m_text = ""; + + public function x(): int { + return $this->m_x; + } + + public function y(): int { + return $this->m_y; + } + + public function width(): int { + return $this->m_width; + } + + public function height(): int { + return $this->m_height; + } + + public function textFlags(): TextFlags { + return $this->m_textFlags; + } + + public function fontIndex(): int { + return $this->m_fontIndex; + } + + public function text(): string { + return $this->m_text; + } + + public function __construct(int $x, int $y, int $width, int $height, TextFlags $textFlags, int $fontIndex, string $text) { + $this->m_x = $x; + $this->m_y = $y; + $this->m_width = $width; + $this->m_height = $height; + $this->m_textFlags = $textFlags; + $this->m_fontIndex = $fontIndex; + $this->m_text = $text; + } + + public function elementType(): ElementType { + return ElementType::ClippedText; + } + + public function minimumFormatVersion(): int { + if ($this->m_textFlags->toBitField() != 0) { + return 2; + } else { + return 1; + } + } + + public function serializeBinary(int $formatVersion): string { + $result = pack("vvvvv", + ElementType::ClippedText->value, + $this->m_x, + $this->m_y, + $this->m_width, + $this->m_height); + if ($formatVersion >= 2) { + $result .= pack("CC", + $this->m_textFlags->toBitField(), + 0); + } + $result .= pack("vv", + $this->m_fontIndex, + strlen($this->m_text)); + $result .= $this->m_text; + return alignSerialized($result, 4); + } + + public function serializeJSON(int $formatVersion): array { + $result = []; + $result["type"] = ElementType::ClippedText->name; + $result["x"] = $this->m_x; + $result["y"] = $this->m_y; + $result["width"] = $this->m_width; + $result["height"] = $this->m_height; + if ($formatVersion >= 2) { + $result["textFlags"] = $this->m_textFlags->toJSON(); + } + $result["fontIndex"] = $this->m_fontIndex; + $result["text"] = $this->m_text; + return $result; + } + + public static function parseBinary(string &$data, int $formatVersion): ClippedTextElement { + [$type, $x, $y, $width, $height] = array_values(unpack("v5", substr($data, 0, 10))); + $data = substr($data, 10); + $pos = 10; + $type = ElementType::from($type); + if ($type != ElementType::ClippedText) { + throw new \ValueError("Could not unserialize a clipped text element: type mismatch"); + } + $textFlags = 0; + if ($formatVersion >= 2) { + [$textFlags, $reserved] = array_values(unpack("C2", substr($data, 0, 2))); + $data = substr($data, 2); + $pos += 2; + } + $textFlags = TextFlags::fromBitField($textFlags); + [$fontIndex, $len] = array_values(unpack("v2", substr($data, 0, 4))); + $data = substr($data, 4); + $text = substr($data, 0, $len); + $pos += 4; + $alignedPos = intdiv($pos + $len + 3, 4) * 4; + $data = substr($data, $alignedPos - $pos); + + $result = new ClippedTextElement($x, $y, $width, $height, $textFlags, $fontIndex, $text); + return $result; + } + + public static function parseJSON(array $element, int $formatVersion): ClippedTextElement { + $type = ElementType::fromString($element["type"]); + if ($type != ElementType::ClippedText) { + throw new \ValueError("Could not unserialize a clipped text element: type mismatch"); + } + $textFlags = new TextFlags(); + if ($formatVersion >= 2) { + $textFlags = TextFlags::fromJSON($element["textFlags"]); + } + $result = new ClippedTextElement($element["x"], $element["y"], $element["width"], $element["height"], $textFlags, $element["fontIndex"], $element["text"]); + return $result; + } +} + +class HScrollTextElement extends Element { + private int $m_x = 0; + private int $m_y = 0; + private int $m_width = 0; + private int $m_height = 0; + private TextFlags $m_textFlags; + private ScrollFlags $m_scrollFlags; + private int $m_scrollSpeed = 0; + private int $m_fontIndex = 0; + private string $m_text = ""; + + public function x(): int { + return $this->m_x; + } + + public function y(): int { + return $this->m_y; + } + + public function width(): int { + return $this->m_width; + } + + public function height(): int { + return $this->m_height; + } + + public function textFlags(): TextFlags { + return $this->m_textFlags; + } + + public function flags(): ScrollFlags { + return $this->m_flags; + } + + public function scrollSpeed(): int { + return $this->m_scrollSpeed; + } + + public function fontIndex(): int { + return $this->m_fontIndex; + } + + public function text(): string { + return $this->m_text; + } + + public function __construct(int $x, int $y, int $width, int $height, TextFlags $textFlags, ScrollFlags $flags, int $scrollSpeed, int $fontIndex, string $text) { + $this->m_x = $x; + $this->m_y = $y; + $this->m_width = $width; + $this->m_height = $height; + $this->m_textFlags = $textFlags; + $this->m_flags = $flags; + $this->m_scrollSpeed = $scrollSpeed; + $this->m_fontIndex = $fontIndex; + $this->m_text = $text; + } + + public function elementType(): ElementType { + return ElementType::HScrollText; + } + + public function minimumFormatVersion(): int { + if ($this->m_textFlags->toBitField() != 0) { + return 2; + } else { + return 1; + } + } + + public function serializeBinary(int $formatVersion): string { + $result = pack("vvvvv", + ElementType::HScrollText->value, + $this->m_x, + $this->m_y, + $this->m_width, + $this->m_height); + if ($formatVersion >= 2) { + $result .= pack("CC", + $this->m_textFlags->toBitField(), + 0); + } + $result .= pack("CC", + $this->m_flags->toBitField(), + $this->m_scrollSpeed); + if ($formatVersion >= 2) { + $result .= pack("v", 0); + } + $result .= pack("vv", + $this->m_fontIndex, + strlen($this->m_text)); + $result .= $this->m_text; + return alignSerialized($result, 4); + } + + public function serializeJSON(int $formatVersion): array { + $result = []; + $result["type"] = ElementType::HScrollText->name; + $result["x"] = $this->m_x; + $result["y"] = $this->m_y; + $result["width"] = $this->m_width; + $result["height"] = $this->m_height; + if ($formatVersion >= 2) { + $result["textFlags"] = $this->m_textFlags->toJSON(); + } + $result["flags"] = $this->m_flags; + $result["scrollSpeed"] = $this->m_scrollSpeed; + $result["fontIndex"] = $this->m_fontIndex; + $result["text"] = $this->m_text; + return $result; + } + + public static function parseBinary(string &$data, int $formatVersion): HScrollTextElement { + [$type, $x, $y, $width, $height] = array_values(unpack("v5", substr($data, 0, 10))); + $data = substr($data, 10); + $pos = 10; + $type = ElementType::from($type); + if ($type != ElementType::HScrollText) { + throw new \ValueError("Could not unserialize a clipped text element: type mismatch"); + } + $textFlags = 0; + if ($formatVersion >= 2) { + [$textFlags, $reserved] = array_values(unpack("C2", substr($data, 0, 2))); + $data = substr($data, 2); + $pos += 2; + } + $textFlags = TextFlags::fromBitField($textFlags); + [$flags, $scrollSpeed] = array_values(unpack("C2", substr($data, 0, 2))); + $flags = ScrollFlags::fromBitField($flags); + $data = substr($data, 2); + $pos += 2; + if ($formatVersion >= 2) { + $data = substr($data, 2); + $pos += 2; + } + [$fontIndex, $len] = array_values(unpack("v2", substr($data, 0, 4))); + $data = substr($data, 4); + $text = substr($data, 0, $len); + $pos += 4; + $alignedPos = intdiv($pos + $len + 3, 4) * 4; + $data = substr($data, $alignedPos - $pos); + + $result = new HScrollTextElement($x, $y, $width, $height, $textFlags, $flags, $scrollSpeed, $fontIndex, $text); + return $result; + } + + public static function parseJSON(array $element, int $formatVersion): HScrollTextElement { + $type = ElementType::fromString($element["type"]); + if ($type != ElementType::HScrollText) { + throw new \ValueError("Could not unserialize a horizontally scrolling text element: type mismatch"); + } + $textFlags = new TextFlags(); + if ($formatVersion >= 2) { + $textFlags = TextFlags::fromJSON($element["textFlags"]); + } + $flags = ScrollFlags::fromJSON($element["flags"]); + $result = new HScrollTextElement($element["x"], $element["y"], $element["width"], $element["height"], $textFlags, $flags, $element["scrollSpeed"], $element["fontIndex"], $element["text"]); + return $result; + } +} + +class CurrentTimeElement extends Element { + private int $m_x = 0; + private int $m_y = 0; + private int $m_width = 0; + private int $m_height = 0; + private TextFlags $m_textFlags; + private int $m_fontIndex = 0; + private int $m_utcOffset = 0; + private TimeDisplayFlags $m_flags; + + public function x(): int { + return $this->m_x; + } + + public function y(): int { + return $this->m_y; + } + + public function width(): int { + return $this->m_width; + } + + public function height(): int { + return $this->m_height; + } + + public function textFlags(): TextFlags { + return $this->m_textFlags; + } + + public function fontIndex(): int { + return $this->m_fontIndex; + } + + public function utcOffset(): int { + return $this->m_utcOffset; + } + + public function flags(): TimeDisplayFlags { + return $this->m_flags; + } + + public function __construct(int $x, int $y, int $width, int $height, TextFlags $textFlags, int $fontIndex, int $utcOffset, TimeDisplayFlags $flags) { + $this->m_x = $x; + $this->m_y = $y; + $this->m_width = $width; + $this->m_height = $height; + $this->m_textFlags = $textFlags; + $this->m_fontIndex = $fontIndex; + $this->m_utcOffset = $utcOffset; + $this->m_flags = $flags; + } + + public function elementType(): ElementType { + return ElementType::CurrentTime; + } + + public function minimumFormatVersion(): int { + if ($this->m_textFlags->toBitField() != 0) { + return 2; + } else { + return 1; + } + } + + public function serializeBinary(int $formatVersion): string { + $result = pack("vvvvv", + ElementType::CurrentTime->value, + $this->m_x, + $this->m_y, + $this->m_width, + $this->m_height); + if ($formatVersion >= 2) { + $result .= pack("CC", + $this->m_textFlags->toBitField(), + 0); + } + $result .= pack("vvv", + $this->m_fontIndex, + $this->m_utcOffset, + $this->m_flags->toBitField()); + if ($formatVersion >= 2) { + $result .= pack("v", 0); + } + return alignSerialized($result, 4); + } + + public function serializeJSON(int $formatVersion): array { + $result = []; + $result["type"] = ElementType::CurrentTime->name; + $result["x"] = $this->m_x; + $result["y"] = $this->m_y; + $result["width"] = $this->m_width; + $result["height"] = $this->m_height; + if ($formatVersion >= 2) { + $result["textFlags"] = $this->m_textFlags->toJSON(); + } + $result["fontIndex"] = $this->m_fontIndex; + $result["utcOffset"] = $this->m_utcOffset; + $result["flags"] = $this->m_flags->toJSON(); + return $result; + } + + public static function parseBinary(string &$data, int $formatVersion): CurrentTimeElement { + [$type, $x, $y, $width, $height] = array_values(unpack("v5", substr($data, 0, 10))); + $data = substr($data, 10); + $pos = 10; + $type = ElementType::from($type); + if ($type != ElementType::CurrentTime) { + throw new \ValueError("Could not unserialize a current time element: type mismatch"); + } + $textFlags = 0; + if ($formatVersion >= 2) { + [$textFlags, $reserved] = array_values(unpack("C2", substr($data, 0, 2))); + $data = substr($data, 2); + $pos += 2; + } + $textFlags = TextFlags::fromBitField($textFlags); + [$fontIndex, $utcOffset, $flags] = array_values(unpack("v3", substr($data, 0, 6))); + $flags = TimeDisplayFlags::fromBitField($flags); + $data = substr($data, 6); + $pos += 6; + if ($formatVersion >= 2) { + $data = substr($data, 2); + $pos += 2; + } + $alignedPos = intdiv($pos + 3, 4) * 4; + $data = substr($data, $alignedPos - $pos); + + $result = new CurrentTimeElement($x, $y, $width, $height, $textFlags, $fontIndex, $utcOffset, $flags); + return $result; + } + + public static function parseJSON(array $element, int $formatVersion): CurrentTime { + $type = ElementType::fromString($element["type"]); + if ($type != ElementType::CurrentTime) { + throw new \ValueError("Could not unserialize a current time element: type mismatch"); + } + $textFlags = new TextFlags(); + if ($formatVersion >= 2) { + $textFlags = TextFlags::fromJSON($element["textFlags"]); + } + $flags = TimeDisplayFlags::fromJSON($element["flags"]); + $result = new CurrentTimeElement($element["x"], $element["y"], $element["width"], $element["height"], $textFlags, $element["fontIndex"], $element["utcOffset"], $flags); + return $result; + } +} + +abstract class ElementContainingSection extends Section { + protected array $m_elements = []; + + public function elementCount(): int { + return count($this->m_elements); + } + + public function elementAt(int $i) { + if ($i >= count($this->m_elements)) { + $max = count($this->m_elements) - 1; + throw new \IndexError("Index $i out of range [0 ..$max]"); + } + return $this->m_elements[$i]; + } + + public function appendElement(Element $element) { + array_push($this->m_elements, $element); + } + + public function insertElement(int $index, Element $element) { + if ($index < count($this->m_elements)) { + array_splice($this->m_elements, $index, 0, $element); + } else { + array_push($this->m_elements, $element); + } + } + + public function replaceElement(int $index, Element $element): Element { + $result = null; + if ($index < count($this->m_elements)) { + $result = $this->m_elements[$index]; + $this->m_elements[$index] = $element; + } + return $result; + } + + public function eraseElement(int $index): Element { + $result = null; + if ($index < count($this->m_elements)) { + $result = $this->m_elements[$index]; + array_splice($this->m_elements, $index, 1); + } + return $result; + } + + protected static function parseElementsBinary(string &$elementData, int $nElements, int $formatVersion): array { + $elements = []; + for ($i = 0; $i < $nElements; ++$i) { + $type = ElementType::from(unpack("v", substr($elementData, 0, 2))[1]); + $element = null; + switch ($type) { + case ElementType::Image: $element = ImageElement::parseBinary($elementData, $formatVersion); break; + case ElementType::Animation: $element = AnimationElement::parseBinary($elementData, $formatVersion); break; + case ElementType::HScrollImage: $element = HScrollImageElement::parseBinary($elementData, $formatVersion); break; + case ElementType::VScrollImage: $element = VScrollImageElement::parseBinary($elementData, $formatVersion); break; + case ElementType::Line: $element = LineElement::parseBinary($elementData, $formatVersion); break; + case ElementType::Box: $element = BoxElement::parseBinary($elementData, $formatVersion); break; + case ElementType::ClippedText: $element = ClippedTextElement::parseBinary($elementData, $formatVersion); break; + case ElementType::HScrollText: $element = HScrollTextElement::parseBinary($elementData, $formatVersion); break; + case ElementType::CurrentTime: $element = CurrenttimeElement::parseBinary($elementData, $formatVersion); break; + default: throw new \ValueError("Unsupported element type " . $type->name . " encountered in file"); + } + array_push($elements, $element); + } + return $elements; + } + + protected static function parseElementsJSON(array $elementData, int $formatVersion): array { + $elements = []; + foreach ($elementData as $serializedElement) { + $type = ElementType::fromName($serializedElement["type"]); + $element = null; + switch ($type) { + case ElementType::Image: $element = ImageElement::parseJSON($serializedElement, $formatVersion); break; + case ElementType::Animation: $element = AnimationElement::parseJSON($serializedElement, $formatVersion); break; + case ElementType::HScrollImage: $element = HScrollImageElement::parseJSON($serializedElement, $formatVersion); break; + case ElementType::VScrollImage: $element = VScrollImageElement::parseJSON($serializedElement, $formatVersion); break; + case ElementType::Line: $element = LineElement::parseJSON($serializedElement, $formatVersion); break; + case ElementType::Box: $element = BoxElement::parseJSON($serializedElement, $formatVersion); break; + case ElementType::ClippedText: $element = ClippedTextElement::parseJSON($serializedElement, $formatVersion); break; + case ElementType::HScrollText: $element = HScrollTextElement::parseJSON($serializedElement, $formatVersion); break; + case ElementType::CurrentTime: $element = CurrenttimeElement::parseJSON($serializedElement, $formatVersion); break; + default: throw new \ValueError("Unsupported element type " . $type->name . " encountered in file"); + } + array_push($elements, $element); + } + return $elements; + } +} + +class AlwaysDrawnSection extends ElementContainingSection { + private bool $m_drawOnFront = true; + private bool $m_drawOnBack = true; + private bool $m_clearBeforeDrawing = true; + + public function doesDrawOnFront(): bool { + return $this->m_drawOnFront; + } + + public function doesDrawOnBack(): bool { + return $this->m_drawOnBack; + } + + public function doesClearBeforeDrawing(): bool { + return $this->m_clearBeforeDrawing; + } + + public function setDrawOnFront(bool $value) { + $this->m_drawOnFront = $value; + } + + public function setDrawOnBack(bool $value) { + $this->m_drawOnBack = $value; + } + + public function setClearBeforeDrawing(bool $value) { + $this->m_clearBeforeDrawing = $value; + } + + public function sectionType(): SectionType { + return SectionType::AlwaysDrawn; + } + + public function minimumFormatVersion(): int { + $result = 1; + foreach ($this->m_elements as $element) { + $result = max($result, $element->minimumFormatVersion()); + } + return $result; + } + + public function serializeBinary(int $formatVersion): string { + $result = pack("C", SectionType::AlwaysDrawn->value); + // Will be replaced later + $result .= packU24LE(0); + $flags = 0; + maybeSetBit($flags, 0, $this->m_drawOnFront); + maybeSetBit($flags, 1, $this->m_drawOnBack); + maybeSetBit($flags, 2, $this->m_clearBeforeDrawing); + $result .= pack("vv", $flags, count($this->m_elements)); + foreach ($this->m_elements as $element) { + $result .= $element->serializeBinary($formatVersion); + } + $result = alignSerialized($result, 4); + $len = strlen($result); + $len = packU24LE($len); + $result[1] = $len[0]; + $result[2] = $len[1]; + $result[3] = $len[2]; + return $result; + } + + public function serializeJSON(int $formatVersion): array { + $result = []; + $result["type"] = SectionType::AlwaysDrawn->name; + $result["flags"] = []; + $result["flags"]["drawOnFront"] = $this->m_drawOnFront; + $result["flags"]["drawOnBack"] = $this->m_drawOnBack; + $result["flags"]["clearBeforeDrawing"] = $this->m_clearBeforeDrawing; + $result["elements"] = []; + foreach ($this->m_elements as $element) { + array_push($result["elements"], $element->serializeJSON($formatVersion)); + } + return $result; + } + + public static function parseBinary(string $section, int $formatVersion): AlwaysDrawnSection { + $type = SectionType::from(unpack("C", substr($section, 0, 1))[1]); + if ($type != SectionType::AlwaysDrawn) { + throw new \ValueError("Invalid section type ". $type->name . " encountered while parsing a AlwaysDrawnSection"); + } + $len = unpackU24LE(substr($section, 1, 3)); + [$flags, $nElements] = array_values(unpack("v2", substr($section, 4, 4))); + $elementData = substr($section, 8); + $elements = static::parseElementsBinary($elementData, $nElements, $formatVersion); + $result = new AlwaysDrawnSection(); + $result->m_drawOnFront = isBitSet($flags, 0); + $result->m_drawOnBack = isBitSet($flags, 1); + $result->m_clearBeforeDrawing = isBitSet($flags, 2); + $result->m_elements = $elements; + return $result; + } + + public static function parseJSON(array $section, int $formatVersion): AlwaysDrawnSection { + $type = SectionType::fromString($section["type"]); + if ($type != SectionType::AlwaysDrawn) { + throw new \ValueError("Invalid section type ". $type->name . " encountered while parsing a AlwaysDrawnSection"); + } + $drawOnFront = $section["flags"]["drawOnFront"]; + $drawOnBack = $section["flags"]["drawOnBack"]; + $clearBeforeDrawing = $section["flags"]["clearBeforeDrawing"]; + $elements = static::parseElementsJSON($section["elements"], $formatVersion); + $result = new AlwaysDrawnSection(); + $result->m_drawOnFront = $drawOnFront; + $result->m_drawOnBack = $drawOnBack; + $result->m_clearBeforeDrawing = $clearBeforeDrawing; + $result->m_elements = $elements; + return $result; + } +} + +class TimeBasedDrawnSection extends ElementContainingSection { + private int $m_startTimestamp = 0; + private int $m_endTimestamp = 0; + private bool $m_drawOnFront = true; + private bool $m_drawOnBack = true; + private bool $m_clearBeforeDrawing = true; + + public function doesDrawOnFront(): bool { + return $this->m_drawOnFront; + } + + public function doesDrawOnBack(): bool { + return $this->m_drawOnBack; + } + + public function doesClearBeforeDrawing(): bool { + return $this->m_clearBeforeDrawing; + } + + public function setDrawOnFront(bool $value) { + $this->m_drawOnFront = $value; + } + + public function setDrawOnBack(bool $value) { + $this->m_drawOnBack = $value; + } + + public function setClearBeforeDrawing(bool $value) { + $this->m_clearBeforeDrawing = $value; + } + + public function startTimestamp(): int { + return $this->m_startTimestamp; + } + + public function endTimestamp(): int { + return $this->m_endTimestamp; + } + + public function setStartTimestamp(int $value) { + $this->m_startTimestamp = $value; + } + + public function setEndTimestamp(int $value) { + $this->m_endTimestamp = $value; + } + + public function sectionType(): SectionType { + return SectionType::TimeBasedDrawn; + } + + public function minimumFormatVersion(): int { + $result = 1; + foreach ($this->m_elements as $element) { + $result = max($result, $element->minimumFormatVersion()); + } + return $result; + } + + public function serializeBinary(int $formatVersion): string { + $result = pack("C", SectionType::TimeBasedDrawn->value); + // Will be replaced later + $result .= packU24LE(0); + $flags = 0; + maybeSetBit($flags, 0, $this->m_drawOnFront); + maybeSetBit($flags, 1, $this->m_drawOnBack); + maybeSetBit($flags, 2, $this->m_clearBeforeDrawing); + $result .= pack("vv", $flags, count($this->m_elements)); + $result .= pack("PP", $this->m_startTimestamp, $this->m_endTimestamp); + foreach ($this->m_elements as $element) { + $result .= $element->serializeBinary($formatVersion); + } + $result = alignSerialized($result, 4); + $len = strlen($result); + $len = packU24LE($len); + $result[1] = $len[0]; + $result[2] = $len[1]; + $result[3] = $len[2]; + return $result; + } + + public function serializeJSON(int $formatVersion): array { + $result = []; + $result["type"] = SectionType::TimeBasedDrawn->name; + $result["flags"] = []; + $result["flags"]["drawOnFront"] = $this->m_drawOnFront; + $result["flags"]["drawOnBack"] = $this->m_drawOnBack; + $result["flags"]["clearBeforeDrawing"] = $this->m_clearBeforeDrawing; + $result["startTimestamp"] = $this->m_startTimestamp; + $result["endTimestamp"] = $this->m_endTimestamp; + $result["elements"] = []; + foreach ($this->m_elements as $element) { + array_push($result["elements"], $element->serializeJSON($formatVersion)); + } + return $result; + } + + public static function parseBinary(string $section, int $formatVersion): TimeBasedDrawnSection { + $type = SectionType::from(unpack("C", substr($section, 0, 1))[1]); + if ($type != SectionType::TimeBasedDrawn) { + throw new \ValueError("Invalid section type ". $type->name . " encountered while parsing a TimeBasedDrawnSection"); + } + $len = unpackU24LE(substr($section, 1, 3)); + [$flags, $nElements] = array_values(unpack("v2", substr($section, 4, 4))); + [$startTimestamp, $endTimestamp] = array_values(unpack("P2", substr($section, 8, 16))); + $elementData = substr($section, 24); + $elements = static::parseElementsBinary($elementData, $nElements, $formatVersion); + $result = new TimeBasedDrawnSection(); + $result->m_drawOnFront = isBitSet($flags, 0); + $result->m_drawOnBack = isBitSet($flags, 1); + $result->m_clearBeforeDrawing = isBitSet($flags, 2); + $result->m_startTimestamp = $startTimestamp; + $result->m_endTimestamp = $endTimestamp; + $result->m_elements = $elements; + return $result; + } + + public static function parseJSON(array $section, int $formatVersion): TimeBasedDrawnSection { + $type = SectionType::fromString($section["type"]); + if ($type != SectionType::TimeBasedDrawn) { + throw new \ValueError("Invalid section type ". $type->name . " encountered while parsing a TimeBasedDrawnSection"); + } + $drawOnFront = $section["flags"]["drawOnFront"]; + $drawOnBack = $section["flags"]["drawOnBack"]; + $clearBeforeDrawing = $section["flags"]["clearBeforeDrawing"]; + $startTimestamp = $section["startTimestamp"]; + $endTimestamp = $section["endTimestamp"]; + $elements = static::parseElementsJSON($section["elements"], $formatVersion); + $result = new TimeBasedDrawnSection(); + $result->m_drawOnFront = $drawOnFront; + $result->m_drawOnBack = $drawOnBack; + $result->m_clearBeforeDrawing = $clearBeforeDrawing; + $result->m_startTimestamp = $startTimestamp; + $result->m_endTimestamp = $endTimestamp; + $result->m_elements = $elements; + return $result; + } +} + +class MultiTimeBasedDrawnSection extends ElementContainingSection { + private array $m_timeRanges = []; + private bool $m_drawOnFront = true; + private bool $m_drawOnBack = true; + private bool $m_clearBeforeDrawing = true; + + public function doesDrawOnFront(): bool { + return $this->m_drawOnFront; + } + + public function doesDrawOnBack(): bool { + return $this->m_drawOnBack; + } + + public function doesClearBeforeDrawing(): bool { + return $this->m_clearBeforeDrawing; + } + + public function setDrawOnFront(bool $value) { + $this->m_drawOnFront = $value; + } + + public function setDrawOnBack(bool $value) { + $this->m_drawOnBack = $value; + } + + public function setClearBeforeDrawing(bool $value) { + $this->m_clearBeforeDrawing = $value; + } + + public function timeRanges(): array { + return $this->m_timeRanges; + } + + public function setTimeRanges(array $timeRanges) { + foreach ($timeRanges as $timeRange) { + if (!is_array($timeRange) || count($timeRange) != 2 || !isset($timeRange[0]) || !isset($timeRange[1])) { + throw new \ValueError("Invalid time range list supplied"); + } + } + $this->m_timeRanges = $timeRanges; + } + + public function sectionType(): SectionType { + return SectionType::MultiTimeBasedDrawn; + } + + public function minimumFormatVersion(): int { + $result = 2; + foreach ($this->m_elements as $element) { + $result = max($result, $element->minimumFormatVersion()); + } + return $result; + } + + public function serializeBinary(int $formatVersion): string { + $result = pack("C", SectionType::MultiTimeBasedDrawn->value); + // Will be replaced later + $result .= packU24LE(0); + $flags = 0; + maybeSetBit($flags, 0, $this->m_drawOnFront); + maybeSetBit($flags, 1, $this->m_drawOnBack); + maybeSetBit($flags, 2, $this->m_clearBeforeDrawing); + $result .= pack("vvv", $flags, count($this->m_elements), count($this->m_timeRanges), 0); + foreach ($this->m_timeRanges as $timeRange) { + $result .= pack("PP", $timeRange[0], $timeRange[1]); + } + foreach ($this->m_elements as $element) { + $result .= $element->serializeBinary($formatVersion); + } + $result = alignSerialized($result, 4); + $len = strlen($result); + $len = packU24LE($len); + $result[1] = $len[0]; + $result[2] = $len[1]; + $result[3] = $len[2]; + return $result; + } + + public function serializeJSON(int $formatVersion): array { + $result = []; + $result["type"] = SectionType::TimeBasedDrawn->name; + $result["flags"] = []; + $result["flags"]["drawOnFront"] = $this->m_drawOnFront; + $result["flags"]["drawOnBack"] = $this->m_drawOnBack; + $result["flags"]["clearBeforeDrawing"] = $this->m_clearBeforeDrawing; + $result["timeRanges"] = $this->m_timeRanges; + $result["elements"] = []; + foreach ($this->m_elements as $element) { + array_push($result["elements"], $element->serializeJSON($formatVersion)); + } + return $result; + } + + public static function parseBinary(string $section, int $formatVersion): MultiTimeBasedDrawnSection { + $type = SectionType::from(unpack("C", substr($section, 0, 1))[1]); + if ($type != SectionType::MultiTimeBasedDrawn) { + throw new \ValueError("Invalid section type ". $type->name . " encountered while parsing a MultiTimeBasedDrawnSection"); + } + $len = unpackU24LE(substr($section, 1, 3)); + [$flags, $nElements, $nRanges, $reserved] = array_values(unpack("v4", substr($section, 4, 8))); + $section = substr($section, 12); + $timeRanges = []; + for ($i = 0; $i < $nRanges; ++$i) { + [$startTimestamp, $endTimestamp] = array_values(unpack("P2", substr($section, 0, 16))); + $section = substr($section, 16); + array_push($timeRanges, [$startTimestamp, $endTimestamp]); + } + + $elementData = $section; + $elements = static::parseElementsBinary($elementData, $nElements, $formatVersion); + $result = new MultiTimeBasedDrawnSection(); + $result->m_drawOnFront = isBitSet($flags, 0); + $result->m_drawOnBack = isBitSet($flags, 1); + $result->m_clearBeforeDrawing = isBitSet($flags, 2); + $result->m_timeRanges = $timeRanges; + return $result; + } + + public static function parseJSON(array $section, int $formatVersion): MultiTimeBasedDrawnSection { + $type = SectionType::fromString($section["type"]); + if ($type != SectionType::MultiTimeBasedDrawn) { + throw new \ValueError("Invalid section type ". $type->name . " encountered while parsing a MultiTimeBasedDrawnSection"); + } + $drawOnFront = $section["flags"]["drawOnFront"]; + $drawOnBack = $section["flags"]["drawOnBack"]; + $clearBeforeDrawing = $section["flags"]["clearBeforeDrawing"]; + $timeRanges = $section["timeRanges"]; + $elements = static::parseElementsJSON($section["elements"], $formatVersion); + $result = new MultiTimeBasedDrawnSection(); + $result->m_drawOnFront = $drawOnFront; + $result->m_drawOnBack = $drawOnBack; + $result->m_clearBeforeDrawing = $clearBeforeDrawing; + // Use setter here to validate the structure + $result->setTimeRanges($timeRanges); + $result->m_elements = $elements; + return $result; + } +} + +class ExpiryDateSection extends Section { + private int $m_expiryDate = 0; + + public function expiryDate(): int { + return $this->m_expiryDate; + } + + public function setExpiryDate(int $value) { + $this->m_expiryDate = $value; + } + + public function sectionType(): SectionType { + return SectionType::ExpiryDate; + } + + public function minimumFormatVersion(): int { + return 2; + } + + public function serializeBinary(int $formatVersion): string { + $result = pack("C", SectionType::ExpiryDate->value); + // Will be replaced later + $result .= packU24LE(0); + $result .= pack("P", $this->m_expiryDate); + $result = alignSerialized($result, 4); + $len = strlen($result); + $len = packU24LE($len); + $result[1] = $len[0]; + $result[2] = $len[1]; + $result[3] = $len[2]; + return $result; + } + + public function serializeJSON(int $formatVersion): array { + $result = []; + $result["type"] = SectionType::ExpiryDate->name; + $result["expiryDate"] = $this->m_expiryDate; + return $result; + } + + public static function parseBinary(string $section, int $formatVersion): ExpiryDateSection { + $type = SectionType::from(unpack("C", substr($section, 0, 1))[1]); + if ($type != SectionType::ExpiryDate) { + throw new \ValueError("Invalid section type ". $type->name . " encountered while parsing a ExpiryDateSection"); + } + $len = unpackU24LE(substr($section, 1, 3)); + [$expiryDate] = array_values(unpack("P", substr($section, 4, 8))); + $result = new ExpiryDateSection(); + $result->m_expiryDate = $expiryDate; + return $result; + } + + public static function parseJSON(array $section, int $formatVersion): ExpiryDateSection { + $type = SectionType::fromString($section["type"]); + if ($type != SectionType::ExpiryDate) { + throw new \ValueError("Invalid section type ". $type->name . " encountered while parsing a ExpiryDateSection"); + } + $expiryDate = $section["expiryDate"]; + $result = new ExpiryDateSection(); + $result->m_expiryDate = $expiryDate; + return $result; + } +} + +class CustomFontSection extends Section { + private string $m_fontData = ""; + + public function fontData(): string { + return $this->m_fontData; + } + + public function setFontData(string $value) { + $this->m_fontData = $value; + } + + public function sectionType(): SectionType { + return SectionType::CustomFont; + } + + public function minimumFormatVersion(): int { + return 1; + } + + public function serializeBinary(int $formatVersion): string { + $result = pack("C", SectionType::CustomFont->value); + // Will be replaced later + $result .= packU24LE(0); + $result .= packU24LE(strlen($this->m_fontData)); + $result .= pack("C", 0); + $result .= $this->m_fontData; + $result = alignSerialized($result, 4); + $len = strlen($result); + $len = packU24LE($len); + $result[1] = $len[0]; + $result[2] = $len[1]; + $result[3] = $len[2]; + return $result; + } + + public function serializeJSON(int $formatVersion): array { + $result = []; + $result["type"] = SectionType::CustomFont->name; + $result["data"] = base64_encode($this->m_fontData); + return $result; + } + + public static function parseBinary(string $section, int $formatVersion): CustomFontSection { + $type = SectionType::from(unpack("C", substr($section, 0, 1))[1]); + if ($type != SectionType::CustomFont) { + throw new \ValueError("Invalid section type ". $type->name . " encountered while parsing a CustomFontSection"); + } + $len = unpackU24LE(substr($section, 1, 3)); + $actualLen = unpackU24LE(substr($section, 4, 3)); + $fontData = substr($section, 8, $actualLen); + $result = new CustomFontSection(); + $result->m_fontData = $fontData; + return $result; + } + + public static function parseJSON(array $section, int $formatVersion): CustomFontSection { + $type = SectionType::fromString($section["type"]); + if ($type != SectionType::CustomFont) { + throw new \ValueError("Invalid section type ". $type->name . " encountered while parsing a CustomFontSection"); + } + $fontData = base64_decode($section["data"]); + $result = new CustomFontSection(); + $result->m_fontData = $fontData; + return $result; + } +} + +class File { + public array $sections = []; +} + +function serializeFile(File $file): string { + $result = "\xAF\x7E\x2B\x63"; + $formatVersion = 1; + foreach ($file->sections as $section) { + $formatVersion = max($formatVersion, $section->minimumFormatVersion()); + } + $result .= pack("Vvv", $formatVersion, count($file->sections), 0); + $result = alignSerialized($result, 4); + foreach ($file->sections as $section) { + $result .= $section->serializeBinary($formatVersion); + } + return $result; +} + +function serializeFileJSON(File $file): array { + $result = []; + $formatVersion = 1; + foreach ($file->sections as $section) { + $formatVersion = max($formatVersion, $section->minimumFormatVersion()); + } + $result["version"] = $formatVersion; + $result["sections"] = []; + foreach ($file->sections as $section) { + array_push($result["sections"], $section->serializeJSON($formatVersion)); + } + return $result; +} + +function parseFile(string $data): File { + if (substr($data, 0, 4) != "\xAF\x7E\x2B\x63") { + throw new \ValueError("The file contains an invalid magic sequence"); + } + $version = unpack("V", substr($data, 4, 4))[1]; + [$nSections, $reserved] = array_values(unpack("v2", substr($data, 8, 4))); + $data = substr($data, 12); + $result = new File(); + for ($i = 0; $i < $nSections; ++$i) { + $type = SectionType::from(unpack("C", substr($data, 0, 1))[1]); + $size = unpackU24LE(substr($data, 1, 3)); + $sectionData = substr($data, 0, $size); + $data = substr($data, $size); + $section = null; + switch ($type) { + case SectionType::AlwaysDrawn: $section = AlwaysDrawnSection::parseBinary($sectionData, $version); break; + case SectionType::TimeBasedDrawn: $section = TimeBasedDrawnSection::parseBinary($sectionData, $version); break; + case SectionType::MultiTimeBasedDrawn: $section = MultiTimeBasedDrawnSection::parseBinary($sectionData, $version); break; + case SectionType::ExpiryDate: $section = ExpiryDateSection::parseBinary($sectionData, $version); break; + case SectionType::CustomFont: $section = CustomFontSection::parseBinary($sectionData, $version); break; + default: throw new \ValueError("Unsupported section type " . $type->name . " encountered in file"); + } + array_push($result->sections, $section); + } + return $result; +} + +function parseFileJSON(array $data): File { + $version = $data["version"]; + if ($version < 1 || $version > 2) { + throw new \ValueError("Unsupported file format version $version encountered while parsing a file"); + } + $result = new File(); + foreach ($data["sections"] as $serializedSection) { + $type = SectionType::fromString($serializedSection["type"]); + $section = null; + switch ($type) { + case SectionType::AlwaysDrawn: $section = AlwaysDrawnSection::parseJSON($serializedSection, $version); break; + case SectionType::TimeBasedDrawn: $section = TimeBasedDrawnSection::parseJSON($serializedSection, $version); break; + case SectionType::MultiTimeBasedDrawn: $section = MultiTimeBasedDrawnSection::parseJSON($serializedSection, $version); break; + case SectionType::ExpiryDate: $section = ExpiryDateSection::parseJSON($serializedSection, $version); break; + case SectionType::CustomFont: $section = CustomFontSection::parseJSON($serializedSection, $version); break; + default: throw new \ValueError("Unsupported section type " . $type->name . " encountered in file"); + } + array_push($result->sections, $section); + } + return $result; +} + +} // namespace monoformat + +?> diff --git a/backup/gpn/noroom.bin b/backup/gpn/noroom.bin new file mode 100644 index 0000000..2dd369f Binary files /dev/null and b/backup/gpn/noroom.bin differ diff --git a/backup/gpn/schedule.php b/backup/gpn/schedule.php new file mode 100644 index 0000000..1fceb56 --- /dev/null +++ b/backup/gpn/schedule.php @@ -0,0 +1,181 @@ += count($talks)) { + return ""; + } + $talk = $talks[$id]; + if (array_key_exists($what, $talk)) { + return $talk[$what]; + } else { + return ""; + } + } else { + return ""; + } + }, $str); +} + +$roomMapping = [ + "1020ba76eb04" => "a873f07f-4ab2-57ea-9183-99b187e8b0cf", // Medientheater + "001122334466" => "0273ef15-d23a-5413-9c74-3edc092b6ee8", // Kubus + "001122334477" => "80d66dbe-d978-57b5-86e2-b3d480136b9a", // Vortragssaal +]; + +if (!isset($_GET["mac"]) or !array_key_exists($_GET["mac"], $roomMapping)) { + /* We didn't find this device in the mapping, so let's just + * send a default file to the user. + */ + Header("Content-Type: application/octet-stream"); + readfile(dirname(__FILE__) . "/noroom.bin"); + exit(0); +} + +$roomGUID = strtolower($roomMapping[$_GET["mac"]]); + +$pretalx = json_decode(file_get_contents("https://cfp.gulas.ch/gpn24/schedule/export/schedule.json"), true); + +$roomName = null; +foreach ($pretalx["schedule"]["conference"]["rooms"] as $room) { + $thisRoomGUID = strtolower($room["guid"]); + if ($thisRoomGUID === $roomGUID) { + $roomName = $room["name"]; + break; + } +} +if ($roomName === null) { + die("Room not found!"); +} + +$talks = []; +$now = new DateTimeImmutable("now"); + +foreach ($pretalx["schedule"]["conference"]["days"] as $day) { + if (!array_key_exists($roomName, $day["rooms"])) { + continue; + } + $roomTalks = $day["rooms"][$roomName]; + foreach ($roomTalks as $t) { + [$h, $m] = explode(":", $t["duration"]); + $duration = $h * 3600 + $m * 60; + $duration = DateInterval::createFromDateString("$duration sec"); + $speakers = []; + foreach ($t["persons"] as $person) { + array_push($speakers, $person["name"]); + } + $talkBegin = new DateTimeImmutable($t["date"]); + $talkEnd = $talkBegin->add($duration); + if ($talkEnd < $now) { + continue; + } + $talk = [ + "Time" => $talkBegin->format("H:i"), + "Duration" => $duration->format("H:i"), + "Persons" => implode(", ", $speakers), + "Title" => $t["title"], + "_start" => $talkBegin, + "_end" => $talkEnd, + ]; + array_push($talks, $talk); + } +} + +$template = file_get_contents(dirname(__FILE__) . "/template.bin"); +$template = monoformat\parseFile($template); + +if (count($template->sections) != 1 or $template->sections[0]->sectionType() != monoformat\SectionType::AlwaysDrawn) { + die("Invalid section in template file"); +} + +$elements = []; +for ($i = 0; $i < $template->sections[0]->elementCount(); ++$i) { + array_push($elements, $template->sections[0]->elementAt($i)); +} + +$end_template = file_get_contents(dirname(__FILE__) . "/endscreen.bin"); +$end_template = monoformat\parseFile($end_template); + +if (count($end_template->sections) != 1 or $end_template->sections[0]->sectionType() != monoformat\SectionType::AlwaysDrawn) { + die("Invalid section in end screen template file"); +} + +$end_elements = []; +for ($i = 0; $i < $end_template->sections[0]->elementCount(); ++$i) { + array_push($end_elements, $end_template->sections[0]->elementAt($i)); +} + +$n = 42; +$last = false; +if ($n > count($talks)) { + $n = count($talks); + $last = true; +} + +$lastEnd = 0; +$outputFile = new monoformat\File(); +if (!$last) { + $section = new monoformat\ExpiryDateSection(); + $section->setExpiryDate($talks[$n - 1]["_end"]->getTimestamp()); + array_push($outputFile->sections, $section); +} +for ($i = 0; $i < $n; ++$i) { + $subTalks = array_slice($talks, $i); + $end = $subTalks[0]["_end"]->getTimestamp(); + $section = new monoformat\TimeBasedDrawnSection(); + $section->setDrawOnFront(true); + $section->setDrawOnBack(true); + $section->setClearBeforeDrawing(true); + $section->setStartTimestamp($lastEnd); + $section->setEndTimestamp($end); + foreach ($elements as $element) { + if ($element->elementType() == monoformat\ElementType::ClippedText) { + $x = $element->x(); + $y = $element->y(); + $width = $element->width(); + $height = $element->height(); + $textFlags = $element->textFlags(); + $fontIndex = $element->fontIndex(); + $text = replaceTalkInfo($element->text(), $subTalks); + $element = new monoformat\ClippedTextElement($x, $y, $width, $height, $textFlags, $fontIndex, $text); + } else if ($element->elementType() == monoformat\ElementType::HScrollText) { + $x = $element->x(); + $y = $element->y(); + $width = $element->width(); + $height = $element->height(); + $textFlags = $element->textFlags(); + $flags = $element->flags(); + $scrollSpeed = $element->scrollSpeed(); + $fontIndex = $element->fontIndex(); + $text = replaceTalkInfo($element->text(), $subTalks) . " "; + $element = new monoformat\HScrollTextElement($x, $y, $width, $height, $textFlags, $flags, $scrollSpeed, $fontIndex, $text); + } + $section->appendElement($element); + } + array_push($outputFile->sections, $section); + $lastEnd = $end; +} +if ($last) { + $section = new monoformat\TimeBasedDrawnSection(); + $section->setDrawOnFront(true); + $section->setDrawOnBack(true); + $section->setClearBeforeDrawing(true); + $section->setStartTimestamp($lastEnd); + // Set this far enough in the future that it doesn't matter + $section->setEndTimestamp($lastEnd + 86400 * 300); + foreach ($end_elements as $element) { + $section->appendElement($element); + } + array_push($outputFile->sections, $section); +} + +Header("Content-Type: application/octet-stream"); +print(monoformat\serializeFile($outputFile)); + +?> diff --git a/backup/gpn/template.bin b/backup/gpn/template.bin new file mode 100644 index 0000000..5efefd2 Binary files /dev/null and b/backup/gpn/template.bin differ diff --git a/backup/index.html b/backup/index.html new file mode 100644 index 0000000..6f89789 --- /dev/null +++ b/backup/index.html @@ -0,0 +1,83 @@ + + + + + + + Busanzeiger Paint + + + + + + +

Busanzeiger Paint

+ +
+ + + + +
+ +
Drag and drop image(s) here or click to upload
+ +
+ + + + + + + + + +
+ + + +
+ + + + +
+ +
+ +
+ + +
+ Filename: +
+ Passwort: + +
+ + + + + + diff --git a/backup/live.php b/backup/live.php new file mode 100644 index 0000000..ea0e800 --- /dev/null +++ b/backup/live.php @@ -0,0 +1,215 @@ + + + +Login + + + +
+ + + + Wrong password. +
+ + + + +Select File + + + +

Live file selector

+

Active: none' ?>

+ + + +

No .bin files found in avj2305/

+ + + serve active .bin file +// GET /live.php?login -> login page +// POST /live.php?login -> process login +// GET /live.php?edit -> file picker [auth required] +// POST /live.php?edit -> set active file [auth required] +// GET /live.php?img=x.bin -> serve x.png preview [auth required] +// +// ============================================================================= + +$method = $_SERVER['REQUEST_METHOD']; +$route = array_key_first($_GET) ?? ''; + +match (true) { + $method === 'POST' && $route === 'login' => action_login(), + $method === 'POST' && $route === 'edit' => action_set_file(), + $method === 'GET' && $route === 'login' => render_login(), + $method === 'GET' && $route === 'edit' => render_edit(), + $method === 'GET' && $route === 'img' => serve_png(), + default => serve_active_file(), +}; diff --git a/backup/out/cttue.disabled b/backup/out/cttue.disabled new file mode 100644 index 0000000..29b794c Binary files /dev/null and b/backup/out/cttue.disabled differ diff --git a/backup/out/franzwerk.bin b/backup/out/franzwerk.bin new file mode 100644 index 0000000..f7d4a9b Binary files /dev/null and b/backup/out/franzwerk.bin differ diff --git a/backup/output.bin b/backup/output.bin new file mode 100644 index 0000000..5b7df78 Binary files /dev/null and b/backup/output.bin differ diff --git a/backup/schedule_bool.php b/backup/schedule_bool.php new file mode 100644 index 0000000..68f9edf --- /dev/null +++ b/backup/schedule_bool.php @@ -0,0 +1,203 @@ +text); + $result = pack("vvvvvCCvv", + 17, + $this->x, + $this->y, + $this->width, + $this->height, + $this->flags, + $this->scrollSpeed, + $this->fontIndex, + $len); + $result .= (string) $this->text; + if ($len % 4 != 0) { + $n = 4 - ($len % 4); + for ($i = 0; $i < $n; ++$i) { + $result .= chr(0); + } + } + return $result; + } +} + +class CurrentTimeElement extends Element { + public $x = 0; + public $y = 0; + public $width = 0; + public $height = 0; + public $fontIndex = 0; + public $utcOffset = 0; + public $flags = 0; + + public function serialize() { + $result = pack("vvvvvvvv", + 32, + $this->x, + $this->y, + $this->width, + $this->height, + $this->fontIndex, + $this->utcOffset, + $this->flags); + return $result; + } +} + +class Section { + public function serialize() { + return ""; + } +} + +class AlwaysDrawnSection extends Section { + public $drawOnFront = false; + public $drawOnBack = false; + public $clearBeforeDrawing = false; + public $elements = []; + + public function serialize() { + $flags = 0; + if ($this->drawOnFront) { + $flags |= 0x01; + } + if ($this->drawOnBack) { + $flags |= 0x02; + } + if ($this->clearBeforeDrawing) { + $flags |= 0x04; + } + $inner = pack("vv", $flags, count($this->elements)); + foreach ($this->elements as $element) { + $inner .= $element->serialize(); + } + $len = strlen($inner) + 4; + $len = substr(pack("V", $len), 0, 3); + return pack("C", 1) . $len . $inner; + } +} + +class File { + public $sections = []; + + public function serialize() { + $nSections = count($this->sections); + $result = "\xAF\x7E\x2B\x63"; + $result .= pack("Vvv", 1, $nSections, 0); + foreach ($this->sections as $section) { + $result .= $section->serialize(); + } + return $result; + } +} + +} // namespace monoformat + +namespace { + +$roomName= "BOOL"; + +$data = json_decode(file_get_contents("https://cfp.cttue.de/tdf5/schedule/export/schedule.json"), true); +$talks = []; +$now = new DateTimeImmutable("now"); +foreach ($data["schedule"]["conference"]["days"] as $day) { + foreach ($day["rooms"][$roomName] as $t) { + [$h, $m] = explode(":", $t["duration"]); + $duration = $h * 3600 + $m * 60; + $duration = DateInterval::createFromDateString("$duration sec"); + $talk = [ + "date" => new DateTimeImmutable($t["date"]), + "duration" => $duration, + "title" => $t["title"], + ]; + $talkEnd = $talk["date"]->add($talk["duration"]); + if ($talkEnd < $now) { + continue; + } + if ($talk["date"] > $now && count($talks) > 2) { + break; + } + array_push($talks, $talk); + } +} + +$elements = []; + +function putText($x, $y, $width, $height, $text) { + global $elements; + + $e = new monoformat\HScrollTextElement(); + $e->x = $x; + $e->y = $y; + $e->width = $width; + $e->height = $height; + $e->flags = 0; + $e->scrollSpeed = 15; + $e->fontIndex = 0; + $e->text = $text; + array_push($elements, $e); +} + +$i = 0; +foreach ($talks as $talk) { + putText(10, $i * 20, 25, 20, $talk["date"]->format("H:i")); + putText(35, $i * 20, 85, 20, $talk["title"]); + ++$i; +} + +$section = new monoformat\AlwaysDrawnSection(); +$section->drawOnFront = true; +$section->drawOnBack = true; +$section->clearBeforeDrawing = true; +$section->elements = $elements; + +$file = new monoformat\File(); +array_push($file->sections, $section); + +Header("Content-Type: application/octet-stream"); + +print($file->serialize()); +} // global + +?> diff --git a/backup/schedule_enum.php b/backup/schedule_enum.php new file mode 100644 index 0000000..8638248 --- /dev/null +++ b/backup/schedule_enum.php @@ -0,0 +1,203 @@ +text); + $result = pack("vvvvvCCvv", + 17, + $this->x, + $this->y, + $this->width, + $this->height, + $this->flags, + $this->scrollSpeed, + $this->fontIndex, + $len); + $result .= (string) $this->text; + if ($len % 4 != 0) { + $n = 4 - ($len % 4); + for ($i = 0; $i < $n; ++$i) { + $result .= chr(0); + } + } + return $result; + } +} + +class CurrentTimeElement extends Element { + public $x = 0; + public $y = 0; + public $width = 0; + public $height = 0; + public $fontIndex = 0; + public $utcOffset = 0; + public $flags = 0; + + public function serialize() { + $result = pack("vvvvvvvv", + 32, + $this->x, + $this->y, + $this->width, + $this->height, + $this->fontIndex, + $this->utcOffset, + $this->flags); + return $result; + } +} + +class Section { + public function serialize() { + return ""; + } +} + +class AlwaysDrawnSection extends Section { + public $drawOnFront = false; + public $drawOnBack = false; + public $clearBeforeDrawing = false; + public $elements = []; + + public function serialize() { + $flags = 0; + if ($this->drawOnFront) { + $flags |= 0x01; + } + if ($this->drawOnBack) { + $flags |= 0x02; + } + if ($this->clearBeforeDrawing) { + $flags |= 0x04; + } + $inner = pack("vv", $flags, count($this->elements)); + foreach ($this->elements as $element) { + $inner .= $element->serialize(); + } + $len = strlen($inner) + 4; + $len = substr(pack("V", $len), 0, 3); + return pack("C", 1) . $len . $inner; + } +} + +class File { + public $sections = []; + + public function serialize() { + $nSections = count($this->sections); + $result = "\xAF\x7E\x2B\x63"; + $result .= pack("Vvv", 1, $nSections, 0); + foreach ($this->sections as $section) { + $result .= $section->serialize(); + } + return $result; + } +} + +} // namespace monoformat + +namespace { + +$roomName= "ENUM"; + +$data = json_decode(file_get_contents("https://cfp.cttue.de/tdf5/schedule/export/schedule.json"), true); +$talks = []; +$now = new DateTimeImmutable("now"); +foreach ($data["schedule"]["conference"]["days"] as $day) { + foreach ($day["rooms"][$roomName] as $t) { + [$h, $m] = explode(":", $t["duration"]); + $duration = $h * 3600 + $m * 60; + $duration = DateInterval::createFromDateString("$duration sec"); + $talk = [ + "date" => new DateTimeImmutable($t["date"]), + "duration" => $duration, + "title" => $t["title"], + ]; + $talkEnd = $talk["date"]->add($talk["duration"]); + if ($talkEnd < $now) { + continue; + } + if ($talk["date"] > $now && count($talks) > 2) { + break; + } + array_push($talks, $talk); + } +} + +$elements = []; + +function putText($x, $y, $width, $height, $text) { + global $elements; + + $e = new monoformat\HScrollTextElement(); + $e->x = $x; + $e->y = $y; + $e->width = $width; + $e->height = $height; + $e->flags = 0; + $e->scrollSpeed = 15; + $e->fontIndex = 0; + $e->text = $text; + array_push($elements, $e); +} + +$i = 0; +foreach ($talks as $talk) { + putText(10, $i * 20, 25, 20, $talk["date"]->format("H:i")); + putText(35, $i * 20, 85, 20, $talk["title"]); + ++$i; +} + +$section = new monoformat\AlwaysDrawnSection(); +$section->drawOnFront = true; +$section->drawOnBack = true; +$section->clearBeforeDrawing = true; +$section->elements = $elements; + +$file = new monoformat\File(); +array_push($file->sections, $section); + +Header("Content-Type: application/octet-stream"); + +print($file->serialize()); +} // global + +?> diff --git a/backup/show.php b/backup/show.php new file mode 100644 index 0000000..d791389 --- /dev/null +++ b/backup/show.php @@ -0,0 +1,53 @@ + diff --git a/backup/styles.css b/backup/styles.css new file mode 100644 index 0000000..51b1b53 --- /dev/null +++ b/backup/styles.css @@ -0,0 +1,84 @@ +body { + font-family: sans-serif; + background: #111; + color: white; + display: flex; + flex-direction: column; + align-items: center; +} + +canvas { + image-rendering: pixelated; + margin-top: 20px; +} + +#matrix { + display: grid; + grid-template-columns: repeat(var(--col-count, 10), 1fr); + grid-gap: 1px; + margin-top: 20px; +} + +.dot { + width: 10px; + height: 10px; + background: #222; + border-radius: 50%; +} + +.dot.on { + background: orange; +} + +#dropzone { + border: 2px dashed #555; + padding: 20px; + margin-top: 20px; + text-align: center; + width: 300px; + cursor: pointer; +} + +#controls { + margin-top: 10px; + display: flex; + gap: 10px; + flex-wrap: wrap; + justify-content: center; +} + +#textInputArea { + display: none; + margin-top: 10px; +} + +input, textarea, select { + background: #222; + color: white; + border: 1px solid #555; + padding: 5px; + border-radius: 4px; +} + +select option { + background: #222; + color: white; +} + +button { + background: #333; + color: white; + border: 1px solid #555; + padding: 5px 10px; + border-radius: 4px; + cursor: pointer; +} + +button:hover { + background: #444; +} + +button.active { + background: #f60; + border-color: #f60; +} diff --git a/backup/upload.php b/backup/upload.php new file mode 100644 index 0000000..16615db --- /dev/null +++ b/backup/upload.php @@ -0,0 +1,105 @@ +/TM?C(V@S"; + +// --- Helpers --- +function setPixel(int $x, int $y, int $width, &$bitmap) { + if ($x < 0 || $y < 0) return; + $index = $y * $width + $x; + $byteIdx = intdiv($index, 8); + $bitPos = $index % 8; + $bitmap[$byteIdx] |= (1 << $bitPos); +} + +function createImageBitmap(array $pixelsOn, int $width, int $height): array { + $totalBits = $width * $height; + $bitmapSize = intdiv($totalBits + 7, 8); + $bitmap = array_fill(0, $bitmapSize, 0); + foreach ($pixelsOn as [$x, $y]) { + setPixel($x, $y, $width, $bitmap); + } + return $bitmap; +} + +function packBitmap(array $bitmap): string { + return implode('', array_map(fn($b) => pack('C', $b), $bitmap)); +} + +function createImageObject(int $width, int $height, array $bitmap, int $xOffset = 0, int $yOffset = 0): string { + $payload = pack('C', $width); + $payload .= pack('C', $height); + $payload .= packBitmap($bitmap); + $header = pack('C*', 0x01, $xOffset, $yOffset); // Type = 1 + return $header . $payload; +} + +function createScrollObject(int $xOffset, int $yOffset, int $width, int $height, int $contentWidth, array $bitmap, int $speed = 0, bool $directionRight = false, bool $wrap = true): string { + $CW = pack('v', $contentWidth); + $UF = (($wrap ? 1 : 0) << 7) | (($directionRight ? 1 : 0) << 6) | ($speed & 0x3F); + $payload = pack('C', $width); + $payload .= pack('C', $height); + $payload .= $CW; + $payload .= pack('C', $UF); + $payload .= packBitmap($bitmap); + $header = pack('C*', 0x03, $xOffset, $yOffset); + return $header . $payload; +} + +function createBlob(array $objects, string $fileName) { + $blob = ''; + $blob .= pack('C*', 0x42, 0x4E, 0x17, 0xEE); + $blob .= pack('C', count($objects)); + foreach ($objects as $obj) { + $blob .= $obj; + } + file_put_contents($fileName, $blob); + echo "Blob written to {$fileName} (" . strlen($blob) . " bytes)\n"; +} + +// --- Validation --- +if (!isset($_POST['password'])) die("password missing."); +if (htmlspecialchars($_POST['password']) !== htmlspecialchars($PASSWORD)) die("wrong password."); + +// --- Determine mode --- +$mode = $_POST['mode'] ?? 'image'; +$objects = []; +$fileName = 'out/' . $_POST['fileName'] . '.bin' ?? 'out/' .'output.bin'; + +mkdir('out/'); +// --- Handle Image Mode --- +if ($mode === 'image') { + $pixelsOn = json_decode($_POST['array_output'] ?? '[]', true); + if (!is_array($pixelsOn)) die("invalid or missing array_output for image mode."); + $bitmap = createImageBitmap($pixelsOn, $width, $height); + $objects[] = createImageObject($width, $height, $bitmap, 0, 0); +} + +// --- Handle Text Mode (scrolling text) --- +elseif ($mode === 'text') { + $scrolls = json_decode($_POST['scrolls'] ?? '[]', true); + if (!is_array($scrolls) || empty($scrolls)) die("invalid or missing scrolls for text mode."); + + foreach ($scrolls as $scroll) { + $pixelsOn = $scroll['pixelsOn'] ?? []; + $bitmap = createImageBitmap($pixelsOn, $scroll['contentWidth'], $scroll['height']); + $objects[] = createScrollObject( + $scroll['x'] ?? 0, + $scroll['y'] ?? 0, + $scroll['width'] ?? $width, + $scroll['height'] ?? 8, + $scroll['contentWidth'] ?? 60, + $bitmap, + $scroll['speed'] ?? 0, + $scroll['directionRight'] ?? false, + $scroll['wrap'] ?? true + ); + } +} + +else { + die("unknown mode: {$mode}"); +} + +createBlob($objects, $fileName);