Signed-off-by: 吴文峰 <kevin@lmve.net>

This commit is contained in:
2026-03-06 16:14:00 +08:00
parent 47d5814325
commit 1ad2934389
1451 changed files with 242614 additions and 0 deletions
@@ -0,0 +1,108 @@
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#include "./LatchingBacklight.h"
#include "assert.h"
#include "sleep.h"
using namespace NicheGraphics::Drivers;
// Private constructor
// Called by getInstance
LatchingBacklight::LatchingBacklight()
{
// Attach the deep sleep callback
deepSleepObserver.observe(&notifyDeepSleep);
}
// Get access to (or create) the singleton instance of this class
LatchingBacklight *LatchingBacklight::getInstance()
{
// Instantiate the class the first time this method is called
static LatchingBacklight *const singletonInstance = new LatchingBacklight;
return singletonInstance;
}
// Which pin controls the backlight?
// Is the light active HIGH (default) or active LOW?
void LatchingBacklight::setPin(uint8_t pin, bool activeWhen)
{
this->pin = pin;
this->logicActive = activeWhen;
pinMode(pin, OUTPUT);
off(); // Explicit off seem required by T-Echo?
}
// Called when device is shutting down
// Ensures the backlight is off
int LatchingBacklight::beforeDeepSleep(void *unused)
{
// Contingency only
// - pin wasn't set
if (pin != (uint8_t)-1) {
off();
pinMode(pin, INPUT); // High impedance - unnecessary?
} else
LOG_WARN("LatchingBacklight instantiated, but pin not set");
return 0; // Continue with deep sleep
}
// Turn the backlight on *temporarily*
// This should be used for momentary illumination, such as while a button is held
// The effect on the backlight is the same; peek and latch are separated to simplify short vs long press button handling
void LatchingBacklight::peek()
{
assert(pin != (uint8_t)-1);
digitalWrite(pin, logicActive); // On
on = true;
latched = false;
}
// Turn the backlight on, and keep it on
// This should be used when the backlight should remain active, even after user input ends
// e.g. when enabled via the menu
// The effect on the backlight is the same; peek and latch are separated to simplify short vs long press button handling
void LatchingBacklight::latch()
{
assert(pin != (uint8_t)-1);
// Blink if moving from peek to latch
// Indicates to user that the transition has taken place
if (on && !latched) {
digitalWrite(pin, !logicActive); // Off
delay(25);
digitalWrite(pin, logicActive); // On
delay(25);
digitalWrite(pin, !logicActive); // Off
delay(25);
}
digitalWrite(pin, logicActive); // On
on = true;
latched = true;
}
// Turn the backlight off
// Suitable for ending both peek and latch
void LatchingBacklight::off()
{
assert(pin != (uint8_t)-1);
digitalWrite(pin, !logicActive); // Off
on = false;
latched = false;
}
bool LatchingBacklight::isOn()
{
return on;
}
bool LatchingBacklight::isLatched()
{
return latched;
}
#endif
@@ -0,0 +1,50 @@
/*
Singleton class
On-demand control of a display's backlight, connected to a GPIO
Initial use case is control of T-Echo's frontlight, via the capacitive touch button
- momentary on
- latched on
*/
#pragma once
#include "configuration.h"
#include "Observer.h"
namespace NicheGraphics::Drivers
{
class LatchingBacklight
{
public:
static LatchingBacklight *getInstance(); // Create or get the singleton instance
void setPin(uint8_t pin, bool activeWhen = HIGH);
int beforeDeepSleep(void *unused); // Callback for auto-shutoff
void peek(); // Backlight on temporarily, e.g. while button held
void latch(); // Backlight on permanently, e.g. toggled via menu
void off(); // Backlight off. Suitable for both peek and latch
bool isOn(); // Either peek or latch
bool isLatched();
private:
LatchingBacklight(); // Constructor made private: force use of getInstance
// Get notified when the system is shutting down
CallbackObserver<LatchingBacklight, void *> deepSleepObserver =
CallbackObserver<LatchingBacklight, void *>(this, &LatchingBacklight::beforeDeepSleep);
uint8_t pin = (uint8_t)-1;
bool logicActive = HIGH; // Is light active HIGH or active LOW
bool on = false; // Is light on (either peek or latched)
bool latched = false; // Is light latched on
};
} // namespace NicheGraphics::Drivers
@@ -0,0 +1,132 @@
#include "./DEPG0213BNS800.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
using namespace NicheGraphics::Drivers;
// Describes the operation performed when a "fast refresh" is performed
// Source: Modified from GxEPD2 (GxEPD2_213_BN)
static const uint8_t LUT_FAST[] = {
// 1 2 3
0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // B2B (Existing black pixels)
0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // B2W (New white pixels)
0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // W2B (New black pixels)
0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // W2W (Existing white pixels)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // VCOM
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // 1. Any pixels changing W2B or B2W. Two medium taps.
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 2. All pixels. One short tap.
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 3. Cooldown
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, //
};
// How strongly the pixels are pulled and pushed
void DEPG0213BNS800::configVoltages()
{
switch (updateType) {
case FAST:
// Reference: display datasheet, GxEPD1
sendCommand(0x03); // Gate voltage
sendData(0x17); // VGH: 20V
// Reference: display datasheet, GxEPD1
sendCommand(0x04); // Source voltage
sendData(0x41); // VSH1: 15V
sendData(0x00); // VSH2: NA
sendData(0x32); // VSL: -15V
// GxEPD1 sets this at -1.2V, but that seems to be drive the pixels very hard
sendCommand(0x2C); // VCOM voltage
sendData(0x08); // VCOM: -0.2V
break;
case FULL:
default:
// From OTP memory
break;
}
}
// Load settings about how the pixels are moved from old state to new state during a refresh
// - manually specified,
// - or with stored values from displays OTP memory
void DEPG0213BNS800::configWaveform()
{
switch (updateType) {
case FAST:
sendCommand(0x3C); // Border waveform:
sendData(0x80); // VSS
sendCommand(0x32); // Write LUT register from MCU:
sendData(LUT_FAST, sizeof(LUT_FAST)); // (describes operation for a FAST refresh)
break;
case FULL:
default:
// From OTP memory
break;
}
}
// Describes the sequence of events performed by the displays controller IC during a refresh
// Includes "power up", "load settings from memory", "update the pixels", etc
void DEPG0213BNS800::configUpdateSequence()
{
switch (updateType) {
case FAST:
sendCommand(0x22); // Set "update sequence"
sendData(0xCF); // Differential, use manually loaded waveform
break;
case FULL:
default:
sendCommand(0x22); // Set "update sequence"
sendData(0xF7); // Non-differential, load waveform from OTP
break;
}
}
// Once the refresh operation has been started,
// begin periodically polling the display to check for completion, using the normal Meshtastic threading code
// Only used when refresh is "async"
void DEPG0213BNS800::detachFromUpdate()
{
switch (updateType) {
case FAST:
return beginPolling(50, 500); // At least 500ms, then poll every 50ms
case FULL:
default:
return beginPolling(100, 3500); // At least 3500ms, then poll every 100ms
}
}
// For this display, we do not need to re-write the new image.
// We're overriding SSD16XX::finalizeUpdate to make this small optimization.
// The display does also work just fine with the generic SSD16XX method, though.
void DEPG0213BNS800::finalizeUpdate()
{
// Put a copy of the image into the "old memory".
// Used with differential refreshes (e.g. FAST update), to determine which px need to move, and which can remain in place
// We need to keep the "old memory" up to date, because don't know whether next refresh will be FULL or FAST etc.
if (updateType != FULL) {
// writeNewImage(); // Not required for this display
writeOldImage();
sendCommand(0x7F); // Terminate image write without update
wait();
}
// Enter deep-sleep to save a few µA
// Waking from this requires that display's reset pin is broken out
if (pin_rst != 0xFF)
deepSleep();
}
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -0,0 +1,44 @@
/*
E-Ink display driver
- DEPG0213BNS800
- Manufacturer: DKE
- Size: 2.13 inch
- Resolution: 122px x 250px
- Flex connector marking (not a unique identifier): FPC-7528B
Note: this is from an older generation of DKE panels, which still used Solomon Systech controller ICs.
DKE's website suggests that the latest DEPG0213BN displays may use Fitipower controllers instead.
*/
#pragma once
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#include "configuration.h"
#include "./SSD16XX.h"
namespace NicheGraphics::Drivers
{
class DEPG0213BNS800 : public SSD16XX
{
// Display properties
private:
static constexpr uint32_t width = 122;
static constexpr uint32_t height = 250;
static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST);
public:
DEPG0213BNS800() : SSD16XX(width, height, supported, 1) {} // Note: left edge of this display is offset by 1 byte
protected:
void configVoltages() override;
void configWaveform() override;
void configUpdateSequence() override;
void detachFromUpdate() override;
void finalizeUpdate() override; // Only overriden for a slight optimization
};
} // namespace NicheGraphics::Drivers
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -0,0 +1,125 @@
#include "./DEPG0290BNS800.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
using namespace NicheGraphics::Drivers;
// Describes the operation performed when a "fast refresh" is performed
// Source: custom, with DEPG0150BNS810 as a reference
static const uint8_t LUT_FAST[] = {
// 1 2 3 4
0x40, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // B2B (Existing black pixels)
0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // B2W (New white pixels)
0x00, 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // W2B (New black pixels)
0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // W2W (Existing white pixels)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // VCOM
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 1. Tap existing black pixels back into place
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 2. Move new pixels
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 3. New pixels, and also existing black pixels
0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // 4. All pixels, then cooldown
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00,
};
// How strongly the pixels are pulled and pushed
void DEPG0290BNS800::configVoltages()
{
switch (updateType) {
case FAST:
// Listed as "typical" in datasheet
sendCommand(0x04);
sendData(0x41); // VSH1 15V
sendData(0x00); // VSH2 NA
sendData(0x32); // VSL -15V
break;
case FULL:
default:
// From OTP memory
break;
}
}
// Load settings about how the pixels are moved from old state to new state during a refresh
// - manually specified,
// - or with stored values from displays OTP memory
void DEPG0290BNS800::configWaveform()
{
switch (updateType) {
case FAST:
sendCommand(0x3C); // Border waveform:
sendData(0x60); // Actively hold screen border during update
sendCommand(0x32); // Write LUT register from MCU:
sendData(LUT_FAST, sizeof(LUT_FAST)); // (describes operation for a FAST refresh)
break;
case FULL:
default:
// From OTP memory
break;
}
}
// Describes the sequence of events performed by the displays controller IC during a refresh
// Includes "power up", "load settings from memory", "update the pixels", etc
void DEPG0290BNS800::configUpdateSequence()
{
switch (updateType) {
case FAST:
sendCommand(0x22); // Set "update sequence"
sendData(0xCF); // Differential, use manually loaded waveform
break;
case FULL:
default:
sendCommand(0x22); // Set "update sequence"
sendData(0xF7); // Non-differential, load waveform from OTP
break;
}
}
// Once the refresh operation has been started,
// begin periodically polling the display to check for completion, using the normal Meshtastic threading code
// Only used when refresh is "async"
void DEPG0290BNS800::detachFromUpdate()
{
switch (updateType) {
case FAST:
return beginPolling(50, 450); // At least 450ms for fast refresh
case FULL:
default:
return beginPolling(100, 3000); // At least 3 seconds for full refresh
}
}
// For this display, we do not need to re-write the new image.
// We're overriding SSD16XX::finalizeUpdate to make this small optimization.
// The display does also work just fine with the generic SSD16XX method, though.
void DEPG0290BNS800::finalizeUpdate()
{
// Put a copy of the image into the "old memory".
// Used with differential refreshes (e.g. FAST update), to determine which px need to move, and which can remain in place
// We need to keep the "old memory" up to date, because don't know whether next refresh will be FULL or FAST etc.
if (updateType != FULL) {
// writeNewImage(); // Not required for this display
writeOldImage();
sendCommand(0x7F); // Terminate image write without update
wait();
}
// Enter deep-sleep to save a few µA
// Waking from this requires that display's reset pin is broken out
if (pin_rst != 0xFF)
deepSleep();
}
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -0,0 +1,42 @@
/*
E-Ink display driver
- DEPG0290BNS800
- Manufacturer: DKE
- Size: 2.9 inch
- Resolution: 128px x 296px
- Flex connector marking (not a unique identifier): FPC-7519 rev.b
*/
#pragma once
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#include "configuration.h"
#include "./SSD16XX.h"
namespace NicheGraphics::Drivers
{
class DEPG0290BNS800 : public SSD16XX
{
// Display properties
private:
static constexpr uint32_t width = 128;
static constexpr uint32_t height = 296;
static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST);
public:
DEPG0290BNS800() : SSD16XX(width, height, supported, 1) {} // Note: left edge of this display is offset by 1 byte
protected:
void configVoltages() override;
void configWaveform() override;
void configUpdateSequence() override;
void detachFromUpdate() override;
void finalizeUpdate() override; // Only overriden for a slight optimization
};
} // namespace NicheGraphics::Drivers
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -0,0 +1,84 @@
#include "./E0213A367.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
using namespace NicheGraphics::Drivers;
// Map the display controller IC's output to the connected panel
void E0213A367::configScanning()
{
// "Driver output control"
// Scan gates from 0 to 249 (vertical resolution 250px)
sendCommand(0x01);
sendData(0xF9);
sendData(0x00);
}
// Specify which information is used to control the sequence of voltages applied to move the pixels
void E0213A367::configWaveform()
{
// This command (0x37) is poorly documented
// As of July 2025, the datasheet for this display's controller IC is unavailable
// The values are supplied by Heltec, who presumably have privileged access to information from the display manufacturer
// Datasheet for the similar SSD1680 IC hints at the function of this command:
// "Spare VCOM OTP selection":
// Unclear why 0x40 is set. Sane values for related SSD1680 seem to be 0x80 or 0x00.
// Maybe value is redundant? No noticeable impact when set to 0x00.
// We'll leave it set to 0x40, following Heltec's lead, just in case.
// "Display Mode"
// Seems to specify whether a waveform stored in OTP should use display mode 1 or 2 (full refresh or differential refresh)
// Unusual that waveforms are programmed to OTP, but this meta information is not ..?
sendCommand(0x37); // "Write Register for Display Option" ?
sendData(0x40); // "Spare VCOM OTP selection" ?
sendData(0x80); // "Display Mode for WS[7:0]" ?
sendData(0x03); // "Display Mode for WS[15:8]" ?
sendData(0x0E); // "Display Mode [23:16]" ?
switch (updateType) {
case FAST:
sendCommand(0x3C); // Border waveform:
sendData(0x81); // As specified by Heltec. Actually VCOM (0x80)?. Bit 0 seems redundant here.
break;
case FULL:
default:
sendCommand(0x3C); // Border waveform:
sendData(0x01); // Follow LUT 1 (blink same as white pixels)
break;
}
}
// Tell controller IC which operations to run
void E0213A367::configUpdateSequence()
{
switch (updateType) {
case FAST:
sendCommand(0x22); // Set "update sequence"
sendData(0xFF); // Will load LUT from OTP memory, Display mode 2 "differential refresh"
break;
case FULL:
default:
sendCommand(0x22); // Set "update sequence"
sendData(0xF7); // Will load LUT from OTP memory, Display mode 1 "full refresh"
break;
}
}
// Once the refresh operation has been started,
// begin periodically polling the display to check for completion, using the normal Meshtastic threading code
// Only used when refresh is "async"
void E0213A367::detachFromUpdate()
{
switch (updateType) {
case FAST:
return beginPolling(50, 500); // At least 500ms for fast refresh
case FULL:
default:
return beginPolling(100, 1500); // At least 1.5 seconds for full refresh
}
}
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -0,0 +1,41 @@
/*
E-Ink display driver
- SSD1682
- Manufacturer: SEEKINK
- Size: 2.13 inch
- Resolution: 122px x 255px
- Flex connector marking: HINK-E0213A162-A1 (hidden, printed on reverse)
*/
#pragma once
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#include "configuration.h"
#include "./SSD1682.h"
namespace NicheGraphics::Drivers
{
class E0213A367 : public SSD1682
{
// Display properties
private:
static constexpr uint32_t width = 122;
static constexpr uint32_t height = 250;
static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST);
public:
E0213A367() : SSD1682(width, height, supported, 0) {}
protected:
void configScanning() override;
void configWaveform() override;
void configUpdateSequence() override;
void detachFromUpdate() override;
};
} // namespace NicheGraphics::Drivers
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -0,0 +1,86 @@
#include "./EInk.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
using namespace NicheGraphics::Drivers;
// Separate from EInk::begin method, as derived class constructors can probably supply these parameters as constants
EInk::EInk(uint16_t width, uint16_t height, UpdateTypes supported)
: concurrency::OSThread("EInkDriver"), width(width), height(height), supportedUpdateTypes(supported)
{
OSThread::disable();
}
// Used by NicheGraphics implementations to check if a display supports a specific refresh operation.
// Whether or not the update type is supported is specified in the constructor
bool EInk::supports(UpdateTypes type)
{
// The EInkUpdateTypes enum assigns each type a unique bit. We are checking if that bit is set.
if (supportedUpdateTypes & type)
return true;
else
return false;
}
// Begins using the OSThread to detect when a display update is complete
// This allows the refresh operation to run "asynchronously".
// Rather than blocking execution waiting for the update to complete, we are periodically checking the hardware's BUSY pin
// The expectedDuration argument allows us to delay the start of this checking, if we know "roughly" how long an update takes.
// Potentially, a display without hardware BUSY could rely entirely on "expectedDuration",
// provided its isUpdateDone() override always returns true.
void EInk::beginPolling(uint32_t interval, uint32_t expectedDuration)
{
updateRunning = true;
pollingInterval = interval;
pollingBegunAt = millis();
// To minimize load, we can choose to delay polling for a few seconds, if we know roughly how long the update will take
// By default, expectedDuration is 0, and we'll start polling immediately
OSThread::setIntervalFromNow(expectedDuration);
OSThread::enabled = true;
}
// Meshtastic's pseudo-threading layer
// We're using this as a timer, to periodically check if an update is complete
// This is what allows us to update the display asynchronously
int32_t EInk::runOnce()
{
// Check for polling timeout
// Manually set at 10 seconds, in case some big task holds up the firmware's cooperative multitasking
if (millis() - pollingBegunAt > 10000)
failed = true;
// Handle failure
// - polling timeout
// - other error (derived classes)
if (failed) {
LOG_WARN("Display update failed. Check wiring & power supply.");
updateRunning = false;
failed = false;
return disable();
}
// If update not yet done
if (!isUpdateDone())
return pollingInterval; // Poll again in a few ms
// If update done
finalizeUpdate(); // Any post-update code: power down panel hardware, hibernate, etc
updateRunning = false; // Change what we report via EInk::busy()
return disable(); // Stop polling
}
// Wait for an in progress update to complete before continuing
// Run a normal (async) update first, *then* call await
void EInk::await()
{
// Stop our concurrency thread
OSThread::disable();
// Sit and block until the update is complete
while (updateRunning) {
runOnce();
yield();
}
}
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -0,0 +1,57 @@
/*
Base class for E-Ink display drivers
*/
#pragma once
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#include "configuration.h"
#include "concurrency/OSThread.h"
#include <SPI.h>
namespace NicheGraphics::Drivers
{
class EInk : private concurrency::OSThread
{
public:
// Different possible operations used to update an E-Ink display
// Some displays will not support all operations
// Each value needs a unique bit. In some cases, we might set more than one bit (e.g. EInk::supportedUpdateType)
enum UpdateTypes : uint8_t {
UNSPECIFIED = 0,
FULL = 1 << 0,
FAST = 1 << 1, // "Partial Refresh"
};
EInk(uint16_t width, uint16_t height, UpdateTypes supported);
virtual void begin(SPIClass *spi, uint8_t pin_dc, uint8_t pin_cs, uint8_t pin_busy, uint8_t pin_rst = -1) = 0;
virtual void update(uint8_t *imageData, UpdateTypes type) = 0; // Change the display image
void await(); // Wait for an in-progress update to complete before proceeding
bool supports(UpdateTypes type); // Can display perform a certain update type
bool busy() { return updateRunning; } // Display able to update right now?
const uint16_t width; // Public so that NicheGraphics implementations can access. Safe because const.
const uint16_t height;
protected:
void beginPolling(uint32_t interval, uint32_t expectedDuration); // Begin checking repeatedly if update finished
virtual bool isUpdateDone() = 0; // Check once if update finished
virtual void finalizeUpdate() {} // Run any post-update code
bool failed = false; // If an error occurred during update
private:
int32_t runOnce() override; // Repeated checking if update finished
const UpdateTypes supportedUpdateTypes; // Capabilities of a derived display class
bool updateRunning = false; // see EInk::busy()
uint32_t pollingInterval = 0; // How often to check if update complete (ms)
uint32_t pollingBegunAt = 0; // To timeout during polling
};
} // namespace NicheGraphics::Drivers
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -0,0 +1,58 @@
#include "./GDEY0154D67.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
using namespace NicheGraphics::Drivers;
// Map the display controller IC's output to the connected panel
void GDEY0154D67::configScanning()
{
// "Driver output control"
sendCommand(0x01);
sendData(0xC7); // Scan until gate 199 (200px vertical res.)
sendData(0x00);
sendData(0x00);
}
// Specify which information is used to control the sequence of voltages applied to move the pixels
// - For this display, configUpdateSequence() specifies that a suitable LUT will be loaded from
// the controller IC's OTP memory, when the update procedure begins.
void GDEY0154D67::configWaveform()
{
sendCommand(0x3C); // Border waveform:
sendData(0x05); // Screen border should follow LUT1 waveform (actively drive pixels white)
sendCommand(0x18); // Temperature sensor:
sendData(0x80); // Use internal temperature sensor to select an appropriate refresh waveform
}
void GDEY0154D67::configUpdateSequence()
{
switch (updateType) {
case FAST:
sendCommand(0x22); // Set "update sequence"
sendData(0xFF); // Will load LUT from OTP memory, Display mode 2 "differential refresh"
break;
case FULL:
default:
sendCommand(0x22); // Set "update sequence"
sendData(0xF7); // Will load LUT from OTP memory
break;
}
}
// Once the refresh operation has been started,
// begin periodically polling the display to check for completion, using the normal Meshtastic threading code
// Only used when refresh is "async"
void GDEY0154D67::detachFromUpdate()
{
switch (updateType) {
case FAST:
return beginPolling(50, 300); // At least 300ms for fast refresh
case FULL:
default:
return beginPolling(100, 1500); // At least 1.5 seconds for full refresh
}
}
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -0,0 +1,42 @@
/*
E-Ink display driver
- GDEY0154D67
- Manufacturer: Goodisplay
- Size: 1.54 inch
- Resolution: 200px x 200px
- Flex connector marking (not a unique identifier): FPC-B001
*/
#pragma once
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#include "configuration.h"
#include "./SSD16XX.h"
namespace NicheGraphics::Drivers
{
class GDEY0154D67 : public SSD16XX
{
// Display properties
private:
static constexpr uint32_t width = 200;
static constexpr uint32_t height = 200;
static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST);
public:
GDEY0154D67() : SSD16XX(width, height, supported) {}
protected:
void configScanning() override;
void configWaveform() override;
void configUpdateSequence() override;
void detachFromUpdate() override;
};
} // namespace NicheGraphics::Drivers
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -0,0 +1,58 @@
#include "./GDEY0213B74.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
using namespace NicheGraphics::Drivers;
// Map the display controller IC's output to the connected panel
void GDEY0213B74::configScanning()
{
// "Driver output control"
sendCommand(0x01);
sendData(0xF9);
sendData(0x00);
sendData(0x00);
}
// Specify which information is used to control the sequence of voltages applied to move the pixels
// - For this display, configUpdateSequence() specifies that a suitable LUT will be loaded from
// the controller IC's OTP memory, when the update procedure begins.
void GDEY0213B74::configWaveform()
{
sendCommand(0x3C); // Border waveform:
sendData(0x05); // Screen border should follow LUT1 waveform (actively drive pixels white)
sendCommand(0x18); // Temperature sensor:
sendData(0x80); // Use internal temperature sensor to select an appropriate refresh waveform
}
void GDEY0213B74::configUpdateSequence()
{
switch (updateType) {
case FAST:
sendCommand(0x22); // Set "update sequence"
sendData(0xFF); // Will load LUT from OTP memory, Display mode 2 "differential refresh"
break;
case FULL:
default:
sendCommand(0x22); // Set "update sequence"
sendData(0xF7); // Will load LUT from OTP memory
break;
}
}
// Once the refresh operation has been started,
// begin periodically polling the display to check for completion, using the normal Meshtastic threading code
// Only used when refresh is "async"
void GDEY0213B74::detachFromUpdate()
{
switch (updateType) {
case FAST:
return beginPolling(50, 500); // At least 500ms for fast refresh
case FULL:
default:
return beginPolling(100, 2000); // At least 2 seconds for full refresh
}
}
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -0,0 +1,44 @@
/*
E-Ink display driver
- GDEY0213B74
- Manufacturer: Goodisplay
- Size: 2.13 inch
- Resolution: 250px x 122px
- Flex connector marking (not a unique identifier):
- FPC-A002
- FPC-A005 20.06.15 TRX
*/
#pragma once
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#include "configuration.h"
#include "./SSD16XX.h"
namespace NicheGraphics::Drivers
{
class GDEY0213B74 : public SSD16XX
{
// Display properties
private:
static constexpr uint32_t width = 122;
static constexpr uint32_t height = 250;
static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST);
public:
GDEY0213B74() : SSD16XX(width, height, supported) {}
protected:
virtual void configScanning() override;
virtual void configWaveform() override;
virtual void configUpdateSequence() override;
void detachFromUpdate() override;
};
} // namespace NicheGraphics::Drivers
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -0,0 +1,61 @@
#include "./HINK_E0213A289.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
using namespace NicheGraphics::Drivers;
// Map the display controller IC's output to the connected panel
void HINK_E0213A289::configScanning()
{
// "Driver output control"
// Scan gates from 0 to 249 (vertical resolution 250px)
sendCommand(0x01);
sendData(0xF9); // Maximum gate # (249, bits 0-7)
sendData(0x00); // Maximum gate # (bit 8)
sendData(0x00); // (Do not invert scanning order)
}
// Specify which information is used to control the sequence of voltages applied to move the pixels
// - For this display, configUpdateSequence() specifies that a suitable LUT will be loaded from
// the controller IC's OTP memory, when the update procedure begins.
void HINK_E0213A289::configWaveform()
{
sendCommand(0x3C); // Border waveform:
sendData(0x05); // Screen border should follow LUT1 waveform (actively drive pixels white)
sendCommand(0x18); // Temperature sensor:
sendData(0x80); // Use internal temperature sensor to select an appropriate refresh waveform
}
// Describes the sequence of events performed by the displays controller IC during a refresh
// Includes "power up", "load settings from memory", "update the pixels", etc
void HINK_E0213A289::configUpdateSequence()
{
switch (updateType) {
case FAST:
sendCommand(0x22); // Set "update sequence"
sendData(0xFF); // Will load LUT from OTP memory, Display mode 2 "differential refresh"
break;
case FULL:
default:
sendCommand(0x22); // Set "update sequence"
sendData(0xF7); // Will load LUT from OTP memory
break;
}
}
// Once the refresh operation has been started,
// begin periodically polling the display to check for completion, using the normal Meshtastic threading code
// Only used when refresh is "async"
void HINK_E0213A289::detachFromUpdate()
{
switch (updateType) {
case FAST:
return beginPolling(50, 500); // At least 500ms for fast refresh
case FULL:
default:
return beginPolling(100, 1000); // At least 1 second for full refresh (quick; display only blinks pixels once)
}
}
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -0,0 +1,44 @@
/*
E-Ink display driver
- HINK_E0213A289
- Manufacturer: Holitech
- Size: 2.13 inch
- Resolution: 122px x 250px
- Flex connector label (not a unique identifier): FPC-7528B
Note: as of Feb. 2025, these panels are used for "WeActStudio 2.13in B&W" display modules
*/
#pragma once
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#include "configuration.h"
#include "./SSD16XX.h"
namespace NicheGraphics::Drivers
{
class HINK_E0213A289 : public SSD16XX
{
// Display properties
private:
static constexpr uint32_t width = 122;
static constexpr uint32_t height = 250;
static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST);
public:
HINK_E0213A289() : SSD16XX(width, height, supported, 1) {}
protected:
void configScanning() override;
void configWaveform() override;
void configUpdateSequence() override;
void detachFromUpdate() override;
};
} // namespace NicheGraphics::Drivers
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -0,0 +1,58 @@
#include "./HINK_E042A87.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
using namespace NicheGraphics::Drivers;
// Load settings about how the pixels are moved from old state to new state during a refresh
// - manually specified,
// - or with stored values from displays OTP memory
void HINK_E042A87::configWaveform()
{
sendCommand(0x3C); // Border waveform:
sendData(0x01); // Follow LUT for VSH1
sendCommand(0x18); // Temperature sensor:
sendData(0x80); // Use internal temperature sensor to select an appropriate refresh waveform
}
// Describes the sequence of events performed by the displays controller IC during a refresh
// Includes "power up", "load settings from memory", "update the pixels", etc
void HINK_E042A87::configUpdateSequence()
{
switch (updateType) {
case FAST:
sendCommand(0x21); // Use both "old" and "new" image memory (differential)
sendData(0x00);
sendData(0x00);
sendCommand(0x22); // Set "update sequence"
sendData(0xFF); // Differential, load waveform from OTP
break;
case FULL:
default:
sendCommand(0x21); // Bypass "old" image memory (non-differential)
sendData(0x40);
sendData(0x00);
sendCommand(0x22); // Set "update sequence":
sendData(0xF7); // Non-differential, load waveform from OTP
break;
}
}
// Once the refresh operation has been started,
// begin periodically polling the display to check for completion, using the normal Meshtastic threading code
// Only used when refresh is "async"
void HINK_E042A87::detachFromUpdate()
{
switch (updateType) {
case FAST:
return beginPolling(50, 1000); // At least 1 second, then check every 50ms
case FULL:
default:
return beginPolling(100, 3500); // At least 3.5 seconds, then check every 100ms
}
}
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -0,0 +1,43 @@
/*
E-Ink display driver
- HINK-E042A87
- Manufacturer: Holitech
- Size: 4.2 inch
- Resolution: 400px x 300px
- Flex connector marking (not a unique identifier): HINK-E042A07-FPC-A1
- Silver sticker with QR code, marked: HE042A87
Note: as of Feb. 2025, these panels are used for "WeActStudio 4.2in B&W" display modules
*/
#pragma once
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#include "configuration.h"
#include "./SSD16XX.h"
namespace NicheGraphics::Drivers
{
class HINK_E042A87 : public SSD16XX
{
// Display properties
private:
static constexpr uint32_t width = 400;
static constexpr uint32_t height = 300;
static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST);
public:
HINK_E042A87() : SSD16XX(width, height, supported) {}
protected:
void configWaveform() override;
void configUpdateSequence() override;
void detachFromUpdate() override;
};
} // namespace NicheGraphics::Drivers
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -0,0 +1,68 @@
#include "./LCMEN2R13ECC1.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
using namespace NicheGraphics::Drivers;
// Map the display controller IC's output to the connected panel
void LCMEN2R13ECC1::configScanning()
{
// "Driver output control"
sendCommand(0x01);
sendData(0xF9);
sendData(0x00);
sendData(0x00);
// To-do: delete this method?
// Values set here might be redundant: F9, 00, 00 seems to be default
}
// Specify which information is used to control the sequence of voltages applied to move the pixels
// - For this display, configUpdateSequence() specifies that a suitable LUT will be loaded from
// the controller IC's OTP memory, when the update procedure begins.
void LCMEN2R13ECC1::configWaveform()
{
switch (updateType) {
case FAST:
sendCommand(0x3C); // Border waveform:
sendData(0x85);
break;
case FULL:
default:
// From OTP memory
break;
}
}
void LCMEN2R13ECC1::configUpdateSequence()
{
switch (updateType) {
case FAST:
sendCommand(0x22); // Set "update sequence"
sendData(0xFF); // Will load LUT from OTP memory, Display mode 2 "differential refresh"
break;
case FULL:
default:
sendCommand(0x22); // Set "update sequence"
sendData(0xF7); // Will load LUT from OTP memory
break;
}
}
// Once the refresh operation has been started,
// begin periodically polling the display to check for completion, using the normal Meshtastic threading code
// Only used when refresh is "async"
void LCMEN2R13ECC1::detachFromUpdate()
{
switch (updateType) {
case FAST:
return beginPolling(50, 800); // At least 500ms for fast refresh
case FULL:
default:
return beginPolling(100, 2500); // At least 2 seconds for full refresh
}
}
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -0,0 +1,40 @@
/*
E-Ink display driver
- SSD1680
- Manufacturer: WISEVAST
- Size: 2.13 inch
- Resolution: 122px x 255px
*/
#pragma once
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#include "configuration.h"
#include "./SSD16XX.h"
namespace NicheGraphics::Drivers
{
class LCMEN2R13ECC1 : public SSD16XX
{
// Display properties
private:
static constexpr uint32_t width = 122;
static constexpr uint32_t height = 250;
static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST);
public:
LCMEN2R13ECC1() : SSD16XX(width, height, supported, 1) {} // Note: left edge of this display is offset by 1 byte
protected:
virtual void configScanning() override;
virtual void configWaveform() override;
virtual void configUpdateSequence() override;
void detachFromUpdate() override;
};
} // namespace NicheGraphics::Drivers
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -0,0 +1,306 @@
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#include "./LCMEN2R13EFC1.h"
#include <assert.h>
#include "SPILock.h"
using namespace NicheGraphics::Drivers;
// Look up table: fast refresh, common electrode
static const uint8_t LUT_FAST_VCOMDC[] = {
0x01, 0x06, 0x03, 0x02, 0x01, 0x01, 0x01, //
0x01, 0x06, 0x02, 0x01, 0x01, 0x01, 0x01, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
};
// Look up table: fast refresh, pixels which remain white
static const uint8_t LUT_FAST_WW[] = {
0x01, 0x06, 0x03, 0x02, 0x81, 0x01, 0x01, //
0x01, 0x06, 0x02, 0x01, 0x01, 0x01, 0x01, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
};
// Look up table: fast refresh, pixel which change from black to white
static const uint8_t LUT_FAST_BW[] = {
0x01, 0x86, 0x83, 0x82, 0x81, 0x01, 0x01, //
0x01, 0x86, 0x82, 0x01, 0x01, 0x01, 0x01, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
};
// Look up table: fast refresh, pixels which change from white to black
static const uint8_t LUT_FAST_WB[] = {
0x01, 0x46, 0x43, 0x02, 0x01, 0x01, 0x01, //
0x01, 0x46, 0x42, 0x01, 0x01, 0x01, 0x01, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
};
// Look up table: fast refresh, pixels which remain black
static const uint8_t LUT_FAST_BB[] = {
0x01, 0x06, 0x03, 0x42, 0x41, 0x01, 0x01, //
0x01, 0x06, 0x02, 0x01, 0x01, 0x01, 0x01, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
};
LCMEN213EFC1::LCMEN213EFC1() : EInk(width, height, supported)
{
// Pre-calculate size of the image buffer, for convenience
// Determine the X dimension of the image buffer, in bytes.
// Along rows, pixels are stored 8 per byte.
// Not all display widths are divisible by 8. Need to make sure bytecount accommodates padding for these.
bufferRowSize = ((width - 1) / 8) + 1;
// Total size of image buffer, in bytes.
bufferSize = bufferRowSize * height;
}
void LCMEN213EFC1::begin(SPIClass *spi, uint8_t pin_dc, uint8_t pin_cs, uint8_t pin_busy, uint8_t pin_rst)
{
this->spi = spi;
this->pin_dc = pin_dc;
this->pin_cs = pin_cs;
this->pin_busy = pin_busy;
this->pin_rst = pin_rst;
pinMode(pin_dc, OUTPUT);
pinMode(pin_cs, OUTPUT);
pinMode(pin_busy, INPUT);
// Reset is active low, hold high
pinMode(pin_rst, INPUT_PULLUP);
reset();
}
// Display an image on the display
void LCMEN213EFC1::update(uint8_t *imageData, UpdateTypes type)
{
this->updateType = type;
this->buffer = imageData;
reset();
// Config
if (updateType == FULL)
configFull();
else
configFast();
// Transfer image data
if (updateType == FULL) {
writeNewImage();
writeOldImage();
} else {
writeNewImage();
}
sendCommand(0x04); // Power on the panel voltage
wait();
sendCommand(0x12); // Begin executing the update
// Let the update run async, on display hardware. Base class will poll completion, then finalize.
// For a blocking update, call await after update
detachFromUpdate();
}
void LCMEN213EFC1::wait()
{
// Busy when LOW
while (digitalRead(pin_busy) == LOW)
yield();
}
void LCMEN213EFC1::reset()
{
pinMode(pin_rst, OUTPUT);
digitalWrite(pin_rst, LOW);
delay(10);
pinMode(pin_rst, INPUT_PULLUP);
wait();
sendCommand(0x12);
wait();
}
void LCMEN213EFC1::sendCommand(const uint8_t command)
{
// Take firmware's SPI lock
spiLock->lock();
spi->beginTransaction(spiSettings);
digitalWrite(pin_dc, LOW); // DC pin low indicates command
digitalWrite(pin_cs, LOW);
spi->transfer(command);
digitalWrite(pin_cs, HIGH);
digitalWrite(pin_dc, HIGH);
spi->endTransaction();
spiLock->unlock();
}
void LCMEN213EFC1::sendData(uint8_t data)
{
sendData(&data, 1);
}
void LCMEN213EFC1::sendData(const uint8_t *data, uint32_t size)
{
// Take firmware's SPI lock
spiLock->lock();
spi->beginTransaction(spiSettings);
digitalWrite(pin_dc, HIGH); // DC pin HIGH indicates data, instead of command
digitalWrite(pin_cs, LOW);
// Platform-specific SPI command
// Mothballing. This display model is only used by Heltec Wireless Paper (ESP32)
#if defined(ARCH_ESP32)
spi->transferBytes(data, NULL, size); // NULL for a "write only" transfer
#elif defined(ARCH_NRF52)
spi->transfer(data, NULL, size); // NULL for a "write only" transfer
#else
#error Not implemented yet? Feel free to add other platforms here.
#endif
digitalWrite(pin_cs, HIGH);
digitalWrite(pin_dc, HIGH);
spi->endTransaction();
spiLock->unlock();
}
void LCMEN213EFC1::configFull()
{
sendCommand(0x00); // Panel setting register
sendData(0b11 << 6 // Display resolution
| 1 << 4 // B&W only
| 1 << 3 // Vertical scan direction
| 1 << 2 // Horizontal scan direction
| 1 << 1 // Shutdown: no
| 1 << 0 // Reset: no
);
sendCommand(0x50); // VCOM and data interval setting register
sendData(0b10 << 6 // Border driven white
| 0b11 << 4 // Invert image colors: no
| 0b0111 << 0 // Interval between VCOM on and image data (default)
);
}
void LCMEN213EFC1::configFast()
{
sendCommand(0x00); // Panel setting register
sendData(0b11 << 6 // Display resolution
| 1 << 5 // LUT from registers (set below)
| 1 << 4 // B&W only
| 1 << 3 // Vertical scan direction
| 1 << 2 // Horizontal scan direction
| 1 << 1 // Shutdown: no
| 1 << 0 // Reset: no
);
sendCommand(0x50); // VCOM and data interval setting register
sendData(0b11 << 6 // Border floating
| 0b01 << 4 // Invert image colors: no
| 0b0111 << 0 // Interval between VCOM on and image data (default)
);
// Load the various LUTs
sendCommand(0x20); // VCOM
sendData(LUT_FAST_VCOMDC, sizeof(LUT_FAST_VCOMDC));
sendCommand(0x21); // White -> White
sendData(LUT_FAST_WW, sizeof(LUT_FAST_WW));
sendCommand(0x22); // Black -> White
sendData(LUT_FAST_BW, sizeof(LUT_FAST_BW));
sendCommand(0x23); // White -> Black
sendData(LUT_FAST_WB, sizeof(LUT_FAST_WB));
sendCommand(0x24); // Black -> Black
sendData(LUT_FAST_BB, sizeof(LUT_FAST_BB));
}
void LCMEN213EFC1::writeNewImage()
{
sendCommand(0x13);
sendData(buffer, bufferSize);
}
void LCMEN213EFC1::writeOldImage()
{
sendCommand(0x10);
sendData(buffer, bufferSize);
}
void LCMEN213EFC1::detachFromUpdate()
{
// To save power / cycles, displays can choose to specify an "expected duration" for various refresh types
// If we know a full-refresh takes at least 4 seconds, we can delay polling until 3 seconds have passed
// If not implemented, we'll just poll right from the get-go
switch (updateType) {
case FULL:
EInk::beginPolling(10, 3650);
break;
case FAST:
EInk::beginPolling(10, 720);
break;
default:
assert(false);
}
}
bool LCMEN213EFC1::isUpdateDone()
{
// Busy when LOW
if (digitalRead(pin_busy) == LOW)
return false;
else
return true;
}
void LCMEN213EFC1::finalizeUpdate()
{
// Power off the panel voltages
sendCommand(0x02);
wait();
// Put a copy of the image into the "old memory".
// Used with differential refreshes (e.g. FAST update), to determine which px need to move, and which can remain in place
// We need to keep the "old memory" up to date, because don't know whether next refresh will be FULL or FAST etc.
if (updateType != FULL) {
writeOldImage();
wait();
}
}
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -0,0 +1,71 @@
/*
E-Ink display driver
- LCMEN213EFC1
- Manufacturer: Wisevast
- Size: 2.13 inch
- Resolution: 122px x 250px
- Flex connector marking (not a unique identifier): HINK-E0213A162-FPC-A0 (Hidden, printed on back-side)
Note: this display uses an uncommon controller IC, Fitipower JD79656.
It is implemented as a "one-off", directly inheriting the EInk base class, unlike SSD16XX displays.
*/
#pragma once
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#include "configuration.h"
#include "./EInk.h"
namespace NicheGraphics::Drivers
{
class LCMEN213EFC1 : public EInk
{
// Display properties
private:
static constexpr uint32_t width = 122;
static constexpr uint32_t height = 250;
static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST);
public:
LCMEN213EFC1();
void begin(SPIClass *spi, uint8_t pin_dc, uint8_t pin_cs, uint8_t pin_busy, uint8_t pin_rst);
void update(uint8_t *imageData, UpdateTypes type) override;
protected:
void wait();
void reset();
void sendCommand(const uint8_t command);
void sendData(const uint8_t data);
void sendData(const uint8_t *data, uint32_t size);
void configFull(); // Configure display for FULL refresh
void configFast(); // Configure display for FAST refresh
void writeNewImage();
void writeOldImage(); // Used for "differential update", aka FAST refresh
void detachFromUpdate();
bool isUpdateDone();
void finalizeUpdate();
protected:
uint8_t bufferOffsetX = 0; // In bytes. Panel x=0 does not always align with controller x=0. Quirky internal wiring?
uint8_t bufferRowSize = 0; // In bytes. Rows store 8 pixels per byte. Rounded up to fit (e.g. 122px would require 16 bytes)
uint32_t bufferSize = 0; // In bytes. Rows * Columns
uint8_t *buffer = nullptr;
UpdateTypes updateType = UpdateTypes::UNSPECIFIED;
uint8_t pin_dc = -1;
uint8_t pin_cs = -1;
uint8_t pin_busy = -1;
uint8_t pin_rst = -1;
SPIClass *spi = nullptr;
SPISettings spiSettings = SPISettings(6000000, MSBFIRST, SPI_MODE0);
};
} // namespace NicheGraphics::Drivers
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -0,0 +1,132 @@
# NicheGraphics - E-Ink Driver
A driver for E-Ink SPI displays. Suitable for re-use by various NicheGraphics UIs.
Your UI should use the class `NicheGraphics::Drivers::EInk` .
When you set up a hardware variant, you will use one of the specific display model classes, which extend the EInk class.
An example setup might look like this:
```cpp
void setupNicheGraphics()
{
using namespace NicheGraphics;
// An imaginary UI
YourCustomUI *yourUI = new YourCustomUI();
// Setup SPI
SPIClass *hspi = new SPIClass(HSPI);
hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS);
// Setup Enk driver
Drivers::EInk *driver = new Drivers::DEPG0290BNS800;
driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY);
// Pass the driver to your UI
YourUI::driver = driver;
}
```
- [Methods](#methods)
- [`update(uint8_t *imageData, UpdateTypes type)`](#updateuint8_t-imagedata-updatetypes-type)
- [`await()`](#await)
- [`supports(UpdateTypes type)`](#supportsupdatetypes-type)
- [`busy()`](#busy)
- [`width()`](#width)
- [`height()`](#height)
- [Supporting New Displays](#supporting-new-displays)
- [Controller IC](#controller-ic)
- [Finding Information](#finding-information)
## Methods
### `update(uint8_t *imageData, UpdateTypes type)`
Update the image on the display
- _`imageData`_ to draw to the display.
- _`type`_ which type of update to perform.
- `FULL`
- `FAST` (partial refresh)
- (Other custom types may be possible)
The imageData is a 1-bit image. X-Pixels are 8-per byte, with the MSB being the leftmost pixel. This was not an InkHUD design decision; it is the raw format accepted by the E-Ink display controllers ICs.
_To-do: add a helper method to `InkHUD::Drivers::EInk` to do this arithmetic for you._
```cpp
uint16_t w = driver::width();
uint16_t h = driver::height();
uint8_t image[ (w/8) * h ]; // X pixels are 8-per-byte
image[0] |= (1 << 7); // Set pixel x=0, y=0
image[0] |= (1 << 0); // Set pixel x=7, y=0
image[1] |= (1 << 7); // Set pixel x=8, y=0
uint8_t x = 12;
uint8_t y = 2;
uint8_t yBytes = y * (w/8);
uint8_t xBytes = x / 8;
uint8_t xBits = (7-x) % 8;
image[yByte + xByte] |= (1 << xBits); // Set pixel x=12, y=2
```
### `await()`
Wait for an in-progress update to complete before continuing
### `supports(UpdateTypes type)`
Check if display supports a specific update type. `true` if supported.
- _`type`_ type to check
### `busy()`
Check if display is already performing an `update()`. `true` if already updating.
### `width()`
Width of the display, in pixels. Note: most displays are portrait. Your UI will need to implement rotation in software.
### `height()`
Height of the display, in pixels. Note: most displays are portrait. Your UI will need to implement rotation in software.
## Supporting New Displays
_This topic is not covered in depth, but these notes may be helpful._
The `InkHUD::Drivers::EInk` class contains only the mechanism for implementing an E-Ink driver on-top of Meshtastic's `OSThread`. A driver for a specific display needs to extend this class.
### Controller IC
If your display uses a controller IC from Solomon Systech, you can probably extend the existing `Drivers::SSD16XX` class, making only minor modifications.
At this stage, displays using controller ICS from other manufacturers (UltraChip, Fitipower, etc) need to manually implemented. See `Drivers::LCMEN2R13EFC1` for an example.
Generic base classes for manufacturers other than Solomon Systech might be added here in future.
### Finding Information
#### Flex-Connector Labels
The orange flex-connector attached to E-Ink displays is often printed with an identifying label. This is not a _totally_ unique identifier, but does give a very strong clue as to the true model of the display, which can be used to search out further information.
#### Datasheets
The manufacturer of a DIY display module may publish a datasheet. These are often incomplete, but might reveal the true model of the display, or the controller IC.
If you can determine the true model name of the display, you can likely find a more complete datasheet on the display manufacturer's website. This will often provide a "typical operating sequence"; a general overview of the code used to drive the display
#### Example Code
The manufacturer of a DIY module may publish example code. You may have more luck finding example code published by the display manufacturer themselves, if you can determine the true model of the panel. These examples are a very valuable reference.
#### Other E-Ink drivers
Libraries like ZinggJM's GxEPD2 can be valuable sources of information, although your panel may not be _specifically_ supported, and only _compatible_ with a driver there, so some caution is advised.
The display selection file in GxEPD2's Hello World example is also a useful resource for matching "flex connector labels" with display models, but the flex connector label is _not_ a unique identifier, so this is only another clue.
@@ -0,0 +1,41 @@
#include "./SSD1682.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
using namespace NicheGraphics::Drivers;
SSD1682::SSD1682(uint16_t width, uint16_t height, EInk::UpdateTypes supported, uint8_t bufferOffsetX)
: SSD16XX(width, height, supported, bufferOffsetX)
{
}
// SSD1682 only accepts single-byte x and y values
// This causes an incompatibility with the default SSD16XX::configFullscreen
void SSD1682::configFullscreen()
{
// Define the boundaries of the "fullscreen" region, for the controller IC
static const uint8_t sx = bufferOffsetX; // Notice the offset
static const uint8_t sy = 0;
static const uint8_t ex = bufferRowSize + bufferOffsetX - 1; // End is "max index", not "count". Minus 1 handles this
static const uint8_t ey = height;
// Data entry mode - Left to Right, Top to Bottom
sendCommand(0x11);
sendData(0x03);
// Select controller IC memory region to display a fullscreen image
sendCommand(0x44); // Memory X start - end
sendData(sx);
sendData(ex);
sendCommand(0x45); // Memory Y start - end
sendData(sy);
sendData(ey);
// Place the cursor at the start of this memory region, ready to send image data x=0 y=0
sendCommand(0x4E); // Memory cursor X
sendData(sx);
sendCommand(0x4F); // Memory cursor y
sendData(sy);
}
#endif
@@ -0,0 +1,31 @@
/*
E-Ink base class for displays based on SSD1682
SSD1682 has a few quirks. We're implementing them here in a new base class,
to avoid re-implementing them every time we need to add a new SSD1682-based display.
*/
#pragma once
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#include "configuration.h"
#include "./SSD16XX.h"
namespace NicheGraphics::Drivers
{
class SSD1682 : public SSD16XX
{
public:
SSD1682(uint16_t width, uint16_t height, EInk::UpdateTypes supported, uint8_t bufferOffsetX = 0);
virtual void configFullscreen(); // Select memory region on controller IC
virtual void deepSleep() {} // Not usable (image memory not retained)
};
} // namespace NicheGraphics::Drivers
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -0,0 +1,272 @@
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#include "./SSD16XX.h"
#include "SPILock.h"
using namespace NicheGraphics::Drivers;
SSD16XX::SSD16XX(uint16_t width, uint16_t height, UpdateTypes supported, uint8_t bufferOffsetX)
: EInk(width, height, supported), bufferOffsetX(bufferOffsetX)
{
// Pre-calculate size of the image buffer, for convenience
// Determine the X dimension of the image buffer, in bytes.
// Along rows, pixels are stored 8 per byte.
// Not all display widths are divisible by 8. Need to make sure bytecount accommodates padding for these.
bufferRowSize = ((width - 1) / 8) + 1;
// Total size of image buffer, in bytes.
bufferSize = bufferRowSize * height;
}
void SSD16XX::begin(SPIClass *spi, uint8_t pin_dc, uint8_t pin_cs, uint8_t pin_busy, uint8_t pin_rst)
{
this->spi = spi;
this->pin_dc = pin_dc;
this->pin_cs = pin_cs;
this->pin_busy = pin_busy;
this->pin_rst = pin_rst;
pinMode(pin_dc, OUTPUT);
pinMode(pin_cs, OUTPUT);
pinMode(pin_busy, INPUT);
// If using a reset pin, hold high
// Reset is active low for Solomon Systech ICs
if (pin_rst != 0xFF)
pinMode(pin_rst, INPUT_PULLUP);
reset();
}
// Poll the displays busy pin until an operation is complete
// Timeout and set fail flag if something went wrong and the display got stuck
void SSD16XX::wait(uint32_t timeout)
{
// Don't bother waiting if part of the update sequence failed
// In that situation, we're now just failing-through the process, until we can try again with next update.
if (failed)
return;
uint32_t startMs = millis();
// Busy when HIGH
while (digitalRead(pin_busy) == HIGH) {
// Check for timeout
if (millis() - startMs > timeout) {
failed = true;
break;
}
yield();
}
}
void SSD16XX::reset()
{
// Check if reset pin is defined
if (pin_rst != 0xFF) {
pinMode(pin_rst, OUTPUT);
digitalWrite(pin_rst, LOW);
delay(10);
digitalWrite(pin_rst, HIGH);
delay(10);
wait();
}
sendCommand(0x12);
wait();
}
void SSD16XX::sendCommand(const uint8_t command)
{
// Abort if part of the update sequence failed
// This will unlock again once we have failed-through the entire process
if (failed)
return;
// Take firmware's SPI lock
spiLock->lock();
spi->beginTransaction(spiSettings);
digitalWrite(pin_dc, LOW); // DC pin low indicates command
digitalWrite(pin_cs, LOW);
spi->transfer(command);
digitalWrite(pin_cs, HIGH);
digitalWrite(pin_dc, HIGH);
spi->endTransaction();
spiLock->unlock();
}
void SSD16XX::sendData(uint8_t data)
{
sendData(&data, 1);
}
void SSD16XX::sendData(const uint8_t *data, uint32_t size)
{
// Abort if part of the update sequence failed
// This will unlock again once we have failed-through the entire process
if (failed)
return;
// Take firmware's SPI lock
spiLock->lock();
spi->beginTransaction(spiSettings);
digitalWrite(pin_dc, HIGH); // DC pin HIGH indicates data, instead of command
digitalWrite(pin_cs, LOW);
// Platform-specific SPI command
#if defined(ARCH_ESP32)
spi->transferBytes(data, NULL, size); // NULL for a "write only" transfer
#elif defined(ARCH_NRF52)
spi->transfer(data, NULL, size); // NULL for a "write only" transfer
#else
#error Not implemented yet? Feel free to add other platforms here.
#endif
digitalWrite(pin_cs, HIGH);
digitalWrite(pin_dc, HIGH);
spi->endTransaction();
spiLock->unlock();
}
void SSD16XX::configFullscreen()
{
// Placing this code in a separate method because it's probably pretty consistent between displays
// Should make it tidier to override SSD16XX::configure
// Define the boundaries of the "fullscreen" region, for the controller IC
static const uint16_t sx = bufferOffsetX; // Notice the offset
static const uint16_t sy = 0;
static const uint16_t ex = bufferRowSize + bufferOffsetX - 1; // End is "max index", not "count". Minus 1 handles this
static const uint16_t ey = height;
// Split into bytes
static const uint8_t sy1 = sy & 0xFF;
static const uint8_t sy2 = (sy >> 8) & 0xFF;
static const uint8_t ey1 = ey & 0xFF;
static const uint8_t ey2 = (ey >> 8) & 0xFF;
// Data entry mode - Left to Right, Top to Bottom
sendCommand(0x11);
sendData(0x03);
// Select controller IC memory region to display a fullscreen image
sendCommand(0x44); // Memory X start - end
sendData(sx);
sendData(ex);
sendCommand(0x45); // Memory Y start - end
sendData(sy1);
sendData(sy2);
sendData(ey1);
sendData(ey2);
// Place the cursor at the start of this memory region, ready to send image data x=0 y=0
sendCommand(0x4E); // Memory cursor X
sendData(sx);
sendCommand(0x4F); // Memory cursor y
sendData(sy1);
sendData(sy2);
}
void SSD16XX::update(uint8_t *imageData, UpdateTypes type)
{
this->updateType = type;
this->buffer = imageData;
reset();
configFullscreen();
configScanning(); // Virtual, unused by base class
configVoltages(); // Virtual, unused by base class
configWaveform(); // Virtual, unused by base class
wait();
if (updateType == FULL) {
writeNewImage();
writeOldImage();
} else {
writeNewImage();
}
configUpdateSequence();
sendCommand(0x20); // Begin executing the update
// Let the update run async, on display hardware. Base class will poll completion, then finalize.
// For a blocking update, call await after update
detachFromUpdate();
}
// Send SPI commands for controller IC to begin executing the refresh operation
void SSD16XX::configUpdateSequence()
{
switch (updateType) {
default:
sendCommand(0x22); // Set "update sequence"
sendData(0xF7); // Non-differential, load waveform from OTP
break;
}
}
void SSD16XX::writeNewImage()
{
sendCommand(0x24);
sendData(buffer, bufferSize);
}
void SSD16XX::writeOldImage()
{
sendCommand(0x26);
sendData(buffer, bufferSize);
}
void SSD16XX::detachFromUpdate()
{
// To save power / cycles, displays can choose to specify an "expected duration" for various refresh types
// If we know a full-refresh takes at least 4 seconds, we can delay polling until 3 seconds have passed
// If not implemented, we'll just poll right from the get-go
switch (updateType) {
default:
EInk::beginPolling(100, 0);
}
}
bool SSD16XX::isUpdateDone()
{
// Busy when HIGH
if (digitalRead(pin_busy) == HIGH)
return false;
else
return true;
}
void SSD16XX::finalizeUpdate()
{
// Put a copy of the image into the "old memory".
// Used with differential refreshes (e.g. FAST update), to determine which px need to move, and which can remain in place
// We need to keep the "old memory" up to date, because don't know whether next refresh will be FULL or FAST etc.
if (updateType != FULL) {
writeNewImage(); // Only required by some controller variants. Todo: Override just for GDEY0154D678?
writeOldImage();
sendCommand(0x7F); // Terminate image write without update
wait();
}
// Enter deep-sleep to save a few µA
// Waking from this requires that display's reset pin is broken out
if (pin_rst != 0xFF)
deepSleep();
}
// Enter a lower-power state
// May only save a few µA..
void SSD16XX::deepSleep()
{
sendCommand(0x10); // Enter deep sleep
sendData(0x01); // Mode 1: preserve image RAM
}
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -0,0 +1,66 @@
/*
E-Ink base class for displays based on SSD16XX
Most (but not all) SPI E-Ink displays use this family of controller IC.
Implementing new SSD16XX displays should be fairly painless.
See DEPG0154BNS800 and DEPG0290BNS800 for examples.
*/
#pragma once
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#include "configuration.h"
#include "./EInk.h"
namespace NicheGraphics::Drivers
{
class SSD16XX : public EInk
{
public:
SSD16XX(uint16_t width, uint16_t height, UpdateTypes supported, uint8_t bufferOffsetX = 0);
virtual void begin(SPIClass *spi, uint8_t pin_dc, uint8_t pin_cs, uint8_t pin_busy, uint8_t pin_rst = -1);
virtual void update(uint8_t *imageData, UpdateTypes type) override;
protected:
virtual void wait(uint32_t timeout = 1000);
virtual void reset();
virtual void sendCommand(const uint8_t command);
virtual void sendData(const uint8_t data);
virtual void sendData(const uint8_t *data, uint32_t size);
virtual void configFullscreen(); // Select memory region on controller IC
virtual void configScanning() {} // Optional. First & last gates, scan direction, etc
virtual void configVoltages() {} // Optional. Manual panel voltages, soft-start, etc
virtual void configWaveform() {} // Optional. LUT, panel border, temperature sensor, etc
virtual void configUpdateSequence(); // Tell controller IC which operations to run
virtual void writeNewImage();
virtual void writeOldImage(); // Image which can be used at *next* update for "differential refresh"
virtual void detachFromUpdate();
virtual bool isUpdateDone() override;
virtual void finalizeUpdate() override;
virtual void deepSleep();
protected:
uint8_t bufferOffsetX = 0; // In bytes. Panel x=0 does not always align with controller x=0. Quirky internal wiring?
uint8_t bufferRowSize = 0; // In bytes. Rows store 8 pixels per byte. Rounded up to fit (e.g. 122px would require 16 bytes)
uint32_t bufferSize = 0; // In bytes. Rows * Columns
uint8_t *buffer = nullptr;
UpdateTypes updateType = UpdateTypes::UNSPECIFIED;
uint8_t pin_dc = -1;
uint8_t pin_cs = -1;
uint8_t pin_busy = -1;
uint8_t pin_rst = -1;
SPIClass *spi = nullptr;
SPISettings spiSettings = SPISettings(4000000, MSBFIRST, SPI_MODE0);
};
} // namespace NicheGraphics::Drivers
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -0,0 +1,68 @@
#include "./ZJY122250_0213BAAMFGN.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
using namespace NicheGraphics::Drivers;
// Map the display controller IC's output to the connected panel
void ZJY122250_0213BAAMFGN::configScanning()
{
// "Driver output control"
// Scan gates from 0 to 249 (vertical resolution 250px)
sendCommand(0x01);
sendData(0xF9);
sendData(0x00);
sendData(0x00);
}
// Specify which information is used to control the sequence of voltages applied to move the pixels
// - For this display, configUpdateSequence() specifies that a suitable LUT will be loaded from
// the controller IC's OTP memory, when the update procedure begins.
void ZJY122250_0213BAAMFGN::configWaveform()
{
switch (updateType) {
case FAST:
sendCommand(0x3C); // Border waveform:
sendData(0x80); // VCOM
break;
case FULL:
default:
sendCommand(0x3C); // Border waveform:
sendData(0x01); // Follow LUT 1 (blink same as white pixels)
break;
}
sendCommand(0x18); // Temperature sensor:
sendData(0x80); // Use internal temperature sensor to select an appropriate refresh waveform
}
void ZJY122250_0213BAAMFGN::configUpdateSequence()
{
switch (updateType) {
case FAST:
sendCommand(0x22); // Set "update sequence"
sendData(0xFF); // Will load LUT from OTP memory, Display mode 2 "differential refresh"
break;
case FULL:
default:
sendCommand(0x22); // Set "update sequence"
sendData(0xF7); // Will load LUT from OTP memory
break;
}
}
// Once the refresh operation has been started,
// begin periodically polling the display to check for completion, using the normal Meshtastic threading code
// Only used when refresh is "async"
void ZJY122250_0213BAAMFGN::detachFromUpdate()
{
switch (updateType) {
case FAST:
return beginPolling(50, 500); // At least 500ms for fast refresh
case FULL:
default:
return beginPolling(100, 2000); // At least 2 seconds for full refresh
}
}
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -0,0 +1,42 @@
/*
E-Ink display driver
- ZJY122250_0213BAAMFGN
- Manufacturer: Zhongjingyuan
- Size: 2.13 inch
- Resolution: 250px x 122px
- Flex connector marking (not a unique identifier): FPC-A002
*/
#pragma once
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#include "configuration.h"
#include "./SSD16XX.h"
namespace NicheGraphics::Drivers
{
class ZJY122250_0213BAAMFGN : public SSD16XX
{
// Display properties
private:
static constexpr uint32_t width = 122;
static constexpr uint32_t height = 250;
static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST);
public:
ZJY122250_0213BAAMFGN() : SSD16XX(width, height, supported) {}
protected:
virtual void configScanning() override;
virtual void configWaveform() override;
virtual void configUpdateSequence() override;
void detachFromUpdate() override;
};
} // namespace NicheGraphics::Drivers
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -0,0 +1,59 @@
#include "./ZJY128296_029EAAMFGN.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
using namespace NicheGraphics::Drivers;
// Map the display controller IC's output to the connected panel
void ZJY128296_029EAAMFGN::configScanning()
{
// "Driver output control"
// Scan gates from 0 to 295 (vertical resolution 296px)
sendCommand(0x01);
sendData(0x27); // Number of gates (295, bits 0-7)
sendData(0x01); // Number of gates (295, bit 8)
sendData(0x00); // (Do not invert scanning order)
}
// Specify which information is used to control the sequence of voltages applied to move the pixels
// - For this display, configUpdateSequence() specifies that a suitable LUT will be loaded from
// the controller IC's OTP memory, when the update procedure begins.
void ZJY128296_029EAAMFGN::configWaveform()
{
sendCommand(0x3C); // Border waveform:
sendData(0x05); // Screen border should follow LUT1 waveform (actively drive pixels white)
sendCommand(0x18); // Temperature sensor:
sendData(0x80); // Use internal temperature sensor to select an appropriate refresh waveform
}
void ZJY128296_029EAAMFGN::configUpdateSequence()
{
switch (updateType) {
case FAST:
sendCommand(0x22); // Set "update sequence"
sendData(0xFF); // Will load LUT from OTP memory, Display mode 2 "differential refresh"
break;
case FULL:
default:
sendCommand(0x22); // Set "update sequence"
sendData(0xF7); // Will load LUT from OTP memory
break;
}
}
// Once the refresh operation has been started,
// begin periodically polling the display to check for completion, using the normal Meshtastic threading code
// Only used when refresh is "async"
void ZJY128296_029EAAMFGN::detachFromUpdate()
{
switch (updateType) {
case FAST:
return beginPolling(50, 300); // At least 300ms for fast refresh
case FULL:
default:
return beginPolling(100, 2000); // At least 2 seconds for full refresh
}
}
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -0,0 +1,44 @@
/*
E-Ink display driver
- ZJY128296-029EAAMFGN
- Manufacturer: Zhongjingyuan
- Size: 2.9 inch
- Resolution: 128px x 296px
- Flex connector label (not a unique identifier): FPC-A005 20.06.15 TRX
Note: as of Feb. 2025, these panels are used for "WeActStudio 2.9in B&W" display modules
*/
#pragma once
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#include "configuration.h"
#include "./SSD16XX.h"
namespace NicheGraphics::Drivers
{
class ZJY128296_029EAAMFGN : public SSD16XX
{
// Display properties
private:
static constexpr uint32_t width = 128;
static constexpr uint32_t height = 296;
static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST);
public:
ZJY128296_029EAAMFGN() : SSD16XX(width, height, supported) {}
protected:
void configScanning() override;
void configWaveform() override;
void configUpdateSequence() override;
void detachFromUpdate() override;
};
} // namespace NicheGraphics::Drivers
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -0,0 +1,32 @@
/*
E-Ink display driver
- ZJY200200-0154DAAMFGN
- Manufacturer: Zhongjingyuan
- Size: 1.54 inch
- Resolution: 200px x 200px
- Flex connector marking: FPC-B001
Note: as of Feb. 2025, these panels are used for "WeActStudio 1.54in B&W" display modules
This *is* a distinct panel, however the driver is currently identical to GDEY0154D67
We recognize it as separate now, to avoid breaking any custom builds if the drivers do need to diverge in future.
*/
#pragma once
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#include "configuration.h"
#include "./GDEY0154D67.h"
namespace NicheGraphics::Drivers
{
typedef GDEY0154D67 ZJY200200_0154DAAMFGN;
} // namespace NicheGraphics::Drivers
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -0,0 +1,3 @@
# NicheGraphics - Drivers
Common drivers which can be used by various NicheGraphics UIs
@@ -0,0 +1,527 @@
// trunk-ignore-all(clang-format)
#pragma once
/* PROPERTIES
FONT_NAME FreeSans12pt_Win1250
*/
const uint8_t FreeSans12pt_Win1250Bitmaps[] PROGMEM = {
/* 0x01 */ 0x00, 0x30, 0x00, 0x09, 0x00, 0x01, 0x20, 0x00, 0x24, 0x00, 0x04, 0x80, 0x01, 0x90, 0x00, 0x62, 0x00, 0x30, 0xFE, 0x04, 0x10, 0x5F, 0x02, 0x0B, 0x00, 0x7F, 0xE0, 0x0C, 0x1C, 0x02, 0x83, 0x81, 0x9F, 0xF0, 0x02, 0x1E, 0x00, 0x41, 0xC0, 0x0E, 0x7F, 0x81, 0x78, 0x18, 0x62, 0x00, 0xFF, 0xC0,
/* 0x02 */ 0x00, 0xFF, 0x80, 0x61, 0x13, 0xF0, 0x62, 0x60, 0x07, 0xFC, 0x00, 0x83, 0x80, 0x10, 0xF0, 0x33, 0xF6, 0x01, 0x41, 0xC0, 0x18, 0x38, 0x03, 0xFF, 0xE0, 0x47, 0x02, 0x08, 0x20, 0x61, 0xC4, 0x06, 0x17, 0x00, 0x22, 0x00, 0x02, 0x40, 0x00, 0x48, 0x00, 0x09, 0x00, 0x01, 0x20, 0x00, 0x3C, 0x00,
/* 0x03 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x04, 0x08, 0x48, 0x70, 0xE1, 0xC3, 0x87, 0x0E, 0x08, 0x10, 0x70, 0x00, 0x03, 0x80, 0x00, 0x14, 0x00, 0x00, 0xA1, 0x81, 0x8D, 0x87, 0xF0, 0x44, 0x00, 0x06, 0x30, 0x00, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x04 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x10, 0x02, 0x48, 0xE0, 0x61, 0xC1, 0xCC, 0x0E, 0x78, 0x1C, 0x70, 0x00, 0x03, 0x80, 0x00, 0x14, 0xFF, 0xFC, 0xA6, 0x00, 0xCD, 0x9F, 0xFE, 0x44, 0x71, 0xE6, 0x30, 0xFC, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x05 */ 0x00, 0x18, 0x00, 0x00, 0x40, 0x01, 0x90, 0x01, 0xF4, 0x08, 0x12, 0x23, 0xC1, 0x91, 0x2C, 0x1C, 0x8A, 0xC3, 0x64, 0x64, 0x13, 0x22, 0x41, 0x98, 0x26, 0x2C, 0xC4, 0x22, 0x60, 0x42, 0x13, 0x04, 0x30, 0x80, 0x61, 0xA4, 0x02, 0x18, 0x20, 0x03, 0x41, 0x00, 0x20, 0x08, 0x02, 0x00, 0x60, 0x40, 0x03, 0xF8,
/* 0x06 */ 0x00, 0x10, 0x00, 0x03, 0x00, 0x1C, 0x48, 0x00, 0xB4, 0x80, 0x09, 0xF9, 0xC0, 0xE0, 0xE4, 0x0C, 0x02, 0x8F, 0x80, 0x38, 0x88, 0x01, 0x0D, 0x00, 0x18, 0x30, 0x01, 0x60, 0x80, 0x13, 0x18, 0x03, 0xF2, 0xC0, 0x20, 0x26, 0x06, 0x07, 0xFF, 0xA0, 0x02, 0x39, 0x00, 0x14, 0x70, 0x01, 0xC3, 0x00, 0x18, 0x00,
/* 0x07 */
/* 0x08 */ 0x00, 0x1F, 0x80, 0x00, 0x60, 0x80, 0x01, 0x00, 0x80, 0x06, 0x00, 0x80, 0x3C, 0x01, 0x01, 0x8C, 0x02, 0x02, 0x08, 0x04, 0x04, 0x08, 0x0C, 0x38, 0x00, 0x04, 0x80, 0x00, 0x06, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x2E, 0xC0, 0x01, 0x83, 0x7E, 0x0C, 0x10, 0x37, 0xE2, 0x61, 0x00, 0x0C, 0xC6, 0x10, 0x98, 0x0C, 0x63, 0x00, 0x00, 0xC6, 0x00,
/* 0x09 */ 0x00, 0x1F, 0x80, 0x00, 0x60, 0x80, 0x01, 0x00, 0x80, 0x06, 0x00, 0x80, 0x3C, 0x01, 0x01, 0x8C, 0x02, 0x02, 0x08, 0x04, 0x04, 0x08, 0x0C, 0x38, 0x00, 0x04, 0x80, 0x00, 0x06, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x2E, 0xC0, 0x01, 0x83, 0x7E, 0x0C, 0x00, 0x37, 0xE0,
/* 0x0A */
/* 0x0B */ 0x1F, 0x07, 0xC1, 0x86, 0x41, 0x10, 0x0C, 0x04, 0x80, 0x40, 0x18, 0x00, 0x00, 0xC0, 0x00, 0x06, 0x00, 0x00, 0x30, 0x00, 0x01, 0x40, 0x00, 0x0A, 0x00, 0x00, 0x88, 0x00, 0x04, 0x40, 0x00, 0x41, 0x00, 0x02, 0x04, 0x00, 0x20, 0x20, 0x02, 0x00, 0x80, 0x20, 0x02, 0x02, 0x00, 0x08, 0x20, 0x00, 0x22, 0x00, 0x00, 0xE0, 0x00,
/* 0x0C */ 0x01, 0x00, 0x00, 0x38, 0x00, 0x04, 0xC0, 0x01, 0x08, 0x00, 0x18, 0x80, 0x1C, 0x10, 0x02, 0x07, 0x80, 0x81, 0x10, 0x1F, 0xC2, 0x02, 0x00, 0x60, 0x80, 0x1A, 0x20, 0x1C, 0x42, 0x1C, 0x08, 0xFE, 0x03, 0xA0, 0x01, 0x8C, 0x01, 0xC1, 0x43, 0xD0, 0x27, 0x81, 0xF8,
/* 0x0D */
/* 0x0E */ 0x00, 0xE0, 0x00, 0x11, 0x00, 0x01, 0x10, 0x00, 0x0B, 0x00, 0x03, 0xF8, 0x00, 0x60, 0x60, 0x09, 0x02, 0x00, 0xA0, 0x10, 0x16, 0x01, 0x01, 0x40, 0x10, 0x10, 0x01, 0x01, 0x00, 0x08, 0x10, 0x00, 0x82, 0x1F, 0x08, 0x3F, 0x90, 0x44, 0x00, 0x06, 0xBF, 0xFF, 0xAF, 0xF0, 0xFF, 0xFF, 0x0F, 0xE3, 0xFB, 0xFC,
/* 0x0F */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x40, 0x12, 0x34, 0x00, 0x69, 0x40, 0x01, 0x49, 0xE0, 0xF1, 0xCD, 0x06, 0x8E, 0x28, 0x14, 0x71, 0x40, 0xA3, 0x8B, 0xFD, 0x14, 0x50, 0x68, 0xA2, 0x81, 0x4D, 0x97, 0xFA, 0x44, 0xBF, 0xD6, 0x31, 0x02, 0xE0, 0xC8, 0x16, 0x08, 0x61, 0x08, 0x21, 0xF0, 0x80, 0xF8, 0x78, 0x00,
/* 0x10 */ 0x00, 0xF0, 0x00, 0x3A, 0x00, 0x07, 0xC0, 0x00, 0xA8, 0x00, 0x1F, 0x00, 0x02, 0xB0, 0x00, 0x52, 0x00, 0x0A, 0x40, 0x02, 0x48, 0x00, 0x49, 0x00, 0x09, 0x30, 0x01, 0x22, 0x01, 0xC4, 0x70, 0xF0, 0x85, 0xE1, 0x10, 0x88, 0x37, 0x20, 0x03, 0x9C, 0x00, 0x37, 0x00, 0x06, 0x40, 0x01, 0x86, 0x00,
/* 0x11 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x60, 0x02, 0x36, 0x00, 0x09, 0x04, 0x0C, 0x48, 0x60, 0xC1, 0xC3, 0x0F, 0x0E, 0x00, 0x08, 0x70, 0x00, 0x23, 0x80, 0x63, 0x84, 0x01, 0x9F, 0x20, 0x0C, 0xFD, 0x80, 0x27, 0xE4, 0x03, 0x3F, 0x30, 0x33, 0xE0, 0xC0, 0x00, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x12 */ 0x00, 0xC2, 0x00, 0x1C, 0x24, 0x02, 0x18, 0x60, 0x64, 0x02, 0x02, 0x40, 0x20, 0x00, 0xF2, 0x03, 0x89, 0xE0, 0x7C, 0x80, 0x0E, 0x25, 0x80, 0xE1, 0x00, 0x1A, 0x08, 0x71, 0xB0, 0xC4, 0x39, 0x84, 0xC2, 0xCC, 0x40, 0x76, 0x7C, 0x05, 0xBB, 0x80, 0x4C, 0xE0, 0x0A, 0x78, 0x00, 0x9C, 0x00, 0x0F, 0x00, 0x00,
/* 0x13 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x00, 0x00, 0x48, 0x60, 0xC1, 0xC6, 0xC9, 0x0E, 0x00, 0x00, 0x70, 0x00, 0x03, 0x80, 0x00, 0x14, 0xFF, 0xF8, 0xA6, 0x00, 0xCD, 0x9F, 0xFE, 0x44, 0x71, 0xE6, 0x30, 0xFC, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x14 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x20, 0x22, 0x33, 0x01, 0x89, 0x20, 0x02, 0x48, 0x60, 0xE1, 0xC8, 0x80, 0x8E, 0x46, 0x46, 0x72, 0x32, 0x33, 0x9F, 0x9F, 0x94, 0x78, 0x78, 0xA0, 0x00, 0x0D, 0x80, 0x00, 0x44, 0x0E, 0x06, 0x30, 0x00, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x15 */ 0x03, 0xFC, 0x20, 0x38, 0x1C, 0x81, 0x80, 0x1D, 0x08, 0x00, 0x32, 0x60, 0x00, 0x89, 0x00, 0x02, 0x18, 0x00, 0x08, 0x61, 0xC3, 0x22, 0x8D, 0x93, 0x72, 0x00, 0x00, 0x48, 0x00, 0x01, 0x20, 0x00, 0x04, 0x9F, 0xFF, 0x92, 0x60, 0x0E, 0x44, 0xFF, 0xF2, 0x11, 0xC3, 0x88, 0x21, 0xF8, 0x40, 0x40, 0x02, 0x00, 0xC0, 0x30, 0x00, 0xFF, 0x00,
/* 0x16 */ 0x03, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x01, 0xE0, 0x03, 0xF0, 0x03, 0xF0, 0x27, 0xF0, 0x6F, 0x70, 0x6E, 0x60, 0xFC, 0x60, 0xFC, 0x7E, 0xFC, 0x7E, 0xFC, 0x3F, 0xF4, 0x1F, 0xF4, 0x1F, 0xF0, 0x0E, 0x70, 0x0E, 0x30, 0x1C, 0x38, 0x38, 0x0F, 0xF0,
/* 0x17 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x00, 0x00, 0x48, 0x00, 0x21, 0xC0, 0x02, 0x8E, 0x20, 0xF4, 0x70, 0x84, 0x11, 0x82, 0x40, 0x84, 0x01, 0x03, 0x20, 0x0F, 0x85, 0x80, 0x03, 0x04, 0x00, 0x04, 0x30, 0x78, 0x10, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x18 */ 0x00, 0xFC, 0x00, 0x02, 0x06, 0x00, 0x08, 0x24, 0x00, 0x21, 0xA4, 0x00, 0x4C, 0x48, 0x00, 0xA0, 0x50, 0x01, 0x92, 0x60, 0x03, 0x24, 0xC0, 0x06, 0x01, 0x81, 0x28, 0x03, 0x49, 0x6C, 0xC4, 0xAD, 0xD8, 0x16, 0xA4, 0xCC, 0xC4, 0x44, 0x86, 0x13, 0x05, 0x00, 0x28, 0x0A, 0x00, 0x50, 0x14, 0x00, 0x90, 0x48, 0x01, 0x20, 0x90, 0x02, 0x41, 0x20, 0x00, 0x00,
/* 0x19 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x00, 0x00, 0x49, 0xC3, 0x81, 0xC0, 0x00, 0x0E, 0x78, 0xF0, 0x77, 0xEF, 0xC3, 0xA7, 0x4E, 0x15, 0x0A, 0x10, 0xA7, 0x8F, 0x0D, 0x80, 0x00, 0x44, 0x00, 0x06, 0x33, 0xF0, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x1A */ 0xFF, 0xFF, 0x00, 0x06, 0x00, 0x0C, 0x3E, 0x18, 0x82, 0x32, 0x02, 0x64, 0x04, 0xC8, 0x09, 0x80, 0x23, 0x00, 0x86, 0x02, 0x0C, 0x08, 0x18, 0x10, 0x30, 0x00, 0x60, 0x00, 0xC0, 0x81, 0x80, 0x03, 0x00, 0x07, 0xFF, 0xF8,
/* 0x1B */ 0x00, 0xFE, 0x00, 0x03, 0x81, 0x80, 0x04, 0x00, 0x60, 0x08, 0x00, 0x30, 0x10, 0x00, 0x10, 0x30, 0x07, 0x88, 0x23, 0xC8, 0x08, 0x22, 0x00, 0x04, 0x60, 0x00, 0x44, 0x60, 0x00, 0x84, 0x63, 0x03, 0x04, 0x61, 0xFC, 0x04, 0x6B, 0x00, 0x9E, 0xA5, 0x01, 0x6A, 0xD5, 0x01, 0x43, 0xA8, 0x81, 0x05, 0xD0, 0x82, 0x0A, 0xA0, 0x82, 0x05, 0xC0, 0x82, 0x02, 0x61, 0xFF, 0x0C, 0x1E, 0x00, 0xF0,
/* 0x1C */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x30, 0x02, 0x32, 0x00, 0x09, 0x00, 0x00, 0x48, 0x20, 0x61, 0xC3, 0x84, 0x0E, 0x1C, 0x78, 0x70, 0x40, 0x03, 0x80, 0x00, 0x14, 0x00, 0x00, 0xA0, 0x03, 0x0D, 0x83, 0xF0, 0x44, 0x00, 0x06, 0x30, 0x00, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x1D */ 0x01, 0xFE, 0x00, 0x3A, 0x1C, 0x03, 0x00, 0x30, 0x23, 0x1E, 0xC3, 0x38, 0x03, 0x10, 0xC3, 0x09, 0x00, 0x18, 0x68, 0x00, 0xC1, 0x40, 0x00, 0x0A, 0x07, 0x80, 0x50, 0x46, 0x02, 0x80, 0x00, 0x1A, 0x1E, 0x00, 0xCB, 0x10, 0x0D, 0x03, 0x00, 0x48, 0x60, 0x06, 0x40, 0x00, 0x22, 0x0C, 0x02, 0x10, 0x60, 0x60, 0x43, 0xFC, 0x01, 0xE0, 0x00, 0x00,
/* 0x1E */ 0x01, 0xF0, 0x00, 0xEA, 0xC0, 0x31, 0x5F, 0x04, 0x5F, 0x88, 0x80, 0xA0, 0x48, 0x0E, 0x02, 0x8F, 0x40, 0x3C, 0x10, 0x21, 0x66, 0x87, 0x15, 0x98, 0x71, 0x41, 0x02, 0x14, 0x00, 0x01, 0x40, 0x00, 0x14, 0x00, 0x01, 0x21, 0xFE, 0x12, 0x00, 0x02, 0x10, 0x00, 0x60, 0x80, 0x0C, 0x06, 0x01, 0x80, 0x3F, 0xE0,
/* 0x1F */ 0x0E, 0x00, 0x13, 0x00, 0x23, 0x00, 0xF3, 0x01, 0x31, 0x01, 0x11, 0x03, 0xD3, 0x06, 0xF2, 0x30, 0x34, 0xC7, 0x25, 0x33, 0x2B, 0xC2, 0x57, 0x04, 0x3A, 0x08, 0x72, 0x30, 0xA3, 0xC3, 0x40, 0x04, 0x40, 0x18, 0x40, 0x60, 0x7F, 0x80,
/* ' ' 0x20 */
/* '!' 0x21 */ 0xFF, 0xFF, 0xFF, 0xF0, 0xF0,
/* '"' 0x22 */ 0xCF, 0x3C, 0xF3, 0x8A, 0x20,
/* '#' 0x23 */ 0x06, 0x30, 0x31, 0x03, 0x18, 0x18, 0xC7, 0xFF, 0xBF, 0xFC, 0x31, 0x01, 0x18, 0x18, 0xC7, 0xFF, 0xBF, 0xFC, 0x31, 0x01, 0x18, 0x18, 0xC0, 0xC6, 0x06, 0x30,
/* '$' 0x24 */ 0x04, 0x03, 0xE1, 0xFF, 0x72, 0x7C, 0x47, 0x88, 0xF1, 0x07, 0xA0, 0x7E, 0x03, 0xF0, 0x17, 0x02, 0x7C, 0x47, 0x88, 0xF1, 0x1B, 0x26, 0x7F, 0xC3, 0xE0, 0x10, 0x02, 0x00,
/* '%' 0x25 */ 0x00, 0x06, 0x03, 0xC0, 0x40, 0x7E, 0x0C, 0x0E, 0x70, 0x80, 0xC3, 0x18, 0x0C, 0x31, 0x00, 0xE7, 0x30, 0x07, 0xE6, 0x00, 0x3C, 0x40, 0x00, 0x0C, 0x7C, 0x00, 0x8F, 0xE0, 0x19, 0xC7, 0x01, 0x18, 0x30, 0x31, 0x83, 0x02, 0x1C, 0x70, 0x40, 0xFE, 0x04, 0x07, 0xC0,
/* '&' 0x26 */ 0x0F, 0x00, 0x7E, 0x03, 0x9C, 0x0C, 0x30, 0x30, 0xC0, 0xE7, 0x01, 0xF8, 0x03, 0x80, 0x3E, 0x01, 0xCC, 0x6E, 0x39, 0xB0, 0x7C, 0xC0, 0xF3, 0x03, 0xCE, 0x1F, 0x9F, 0xE6, 0x3E, 0x1C,
/* ''' 0x27 */ 0xFF, 0xA0,
/* '(' 0x28 */ 0x08, 0x8C, 0x46, 0x31, 0x98, 0xC6, 0x31, 0x8C, 0x63, 0x08, 0x63, 0x08, 0x61, 0x0C, 0x20,
/* ')' 0x29 */ 0x82, 0x18, 0xC3, 0x18, 0xC3, 0x18, 0xC6, 0x31, 0x8C, 0x62, 0x31, 0x88, 0xC4, 0x62, 0x00,
/* '*' 0x2A */ 0x10, 0x23, 0x5B, 0xE3, 0x8D, 0x91, 0x00,
/* '+' 0x2B */ 0x0C, 0x03, 0x00, 0xC0, 0x30, 0xFF, 0xFF, 0xF0, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0,
/* ',' 0x2C */ 0xF5, 0x60,
/* '-' 0x2D */ 0xFF, 0xF0,
/* '.' 0x2E */ 0xF0,
/* '/' 0x2F */ 0x02, 0x0C, 0x10, 0x20, 0xC1, 0x02, 0x0C, 0x10, 0x20, 0xC1, 0x02, 0x0C, 0x10, 0x20, 0xC1, 0x00,
/* '0' 0x30 */ 0x1F, 0x07, 0xF1, 0xC7, 0x30, 0x6C, 0x0F, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3E, 0x0E, 0xC1, 0x9C, 0x71, 0xFC, 0x1F, 0x00,
/* '1' 0x31 */ 0x08, 0xCF, 0xFF, 0x8C, 0x63, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x18,
/* '2' 0x32 */ 0x1F, 0x0F, 0xF9, 0x87, 0x60, 0x7C, 0x06, 0x00, 0xC0, 0x18, 0x07, 0x01, 0xC0, 0xF0, 0x78, 0x1C, 0x06, 0x00, 0xC0, 0x30, 0x07, 0xFF, 0xFF, 0xE0,
/* '3' 0x33 */ 0x3F, 0x0F, 0xF3, 0x87, 0x60, 0x6C, 0x0C, 0x01, 0x80, 0x60, 0x78, 0x0F, 0x80, 0x18, 0x01, 0x80, 0x3C, 0x07, 0x80, 0xD8, 0x73, 0xFC, 0x3F, 0x00,
/* '4' 0x34 */ 0x01, 0x80, 0x70, 0x0E, 0x03, 0xC0, 0xD8, 0x1B, 0x06, 0x61, 0x8C, 0x21, 0x8C, 0x33, 0x06, 0x7F, 0xFF, 0xFE, 0x03, 0x00, 0x60, 0x0C, 0x01, 0x80,
/* '5' 0x35 */ 0x3F, 0xCF, 0xF9, 0x80, 0x30, 0x06, 0x00, 0xDE, 0x1F, 0xE7, 0x0E, 0x00, 0xE0, 0x0C, 0x01, 0x80, 0x30, 0x07, 0x81, 0xB8, 0x73, 0xFC, 0x1F, 0x00,
/* '6' 0x36 */ 0x0F, 0x07, 0xF9, 0xC3, 0x30, 0x74, 0x01, 0x80, 0x33, 0xC7, 0xFE, 0xF1, 0xDC, 0x1F, 0x01, 0xE0, 0x3C, 0x06, 0xC1, 0xDC, 0x71, 0xFC, 0x1F, 0x00,
/* '7' 0x37 */ 0xFF, 0xFF, 0xFC, 0x01, 0x00, 0x60, 0x18, 0x02, 0x00, 0xC0, 0x30, 0x06, 0x01, 0x80, 0x30, 0x04, 0x01, 0x80, 0x30, 0x06, 0x01, 0x80, 0x30, 0x00,
/* '8' 0x38 */ 0x1F, 0x07, 0xF1, 0xC7, 0x30, 0x66, 0x0C, 0xC1, 0x8C, 0x61, 0xF8, 0x3F, 0x8E, 0x3B, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xD8, 0x31, 0xFC, 0x1F, 0x00,
/* '9' 0x39 */ 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x6C, 0x07, 0x80, 0xF0, 0x1E, 0x07, 0x61, 0xEF, 0xFC, 0x79, 0x80, 0x30, 0x05, 0xC1, 0x98, 0x73, 0xFC, 0x1E, 0x00,
/* ':' 0x3A */ 0xF0, 0x00, 0x03, 0xC0,
/* ';' 0x3B */ 0xF0, 0x00, 0x0F, 0x56,
/* '<' 0x3C */ 0x00, 0x70, 0x1E, 0x0F, 0x83, 0xC0, 0xF0, 0x0E, 0x00, 0x7C, 0x00, 0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x10,
/* '=' 0x3D */ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF,
/* '>' 0x3E */ 0xE0, 0x07, 0x80, 0x1F, 0x00, 0x7C, 0x00, 0xF0, 0x07, 0x01, 0xE0, 0xF0, 0x3C, 0x0F, 0x00, 0x80, 0x00,
/* '?' 0x3F */ 0x3F, 0x1F, 0xEE, 0x1F, 0x03, 0xC0, 0xC0, 0x30, 0x0C, 0x06, 0x03, 0x81, 0xC0, 0xE0, 0x30, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x0C, 0x03, 0x00,
/* '@' 0x40 */ 0x00, 0xFE, 0x00, 0x0F, 0xFE, 0x00, 0xF0, 0x3E, 0x07, 0x00, 0x3C, 0x38, 0x00, 0x38, 0xC1, 0xE0, 0x66, 0x0F, 0xD9, 0xD8, 0x61, 0xC3, 0xC3, 0x07, 0x0F, 0x1C, 0x1C, 0x3C, 0x60, 0x60, 0xF1, 0x81, 0x83, 0xC6, 0x06, 0x1B, 0x18, 0x38, 0xEE, 0x71, 0xE7, 0x18, 0xFD, 0xF8, 0x71, 0xE7, 0xC0, 0xE0, 0x00, 0x01, 0xE0, 0x00, 0x01, 0xFF, 0xC0, 0x01, 0xFC, 0x00,
/* 'A' 0x41 */ 0x07, 0x80, 0x1E, 0x00, 0x78, 0x03, 0xF0, 0x0C, 0xC0, 0x33, 0x01, 0xCE, 0x06, 0x18, 0x18, 0x60, 0xE1, 0xC3, 0x03, 0x0F, 0xFC, 0x7F, 0xF9, 0x80, 0x66, 0x01, 0xB8, 0x07, 0xC0, 0x0F, 0x00, 0x30,
/* 'B' 0x42 */ 0xFF, 0xC7, 0xFF, 0x30, 0x1D, 0x80, 0x6C, 0x03, 0x60, 0x1B, 0x00, 0xD8, 0x0C, 0xFF, 0xC7, 0xFF, 0x30, 0x0D, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x06, 0xFF, 0xF7, 0xFE, 0x00,
/* 'C' 0x43 */ 0x07, 0xE0, 0x3F, 0xF0, 0xE0, 0x73, 0x80, 0x76, 0x00, 0x6C, 0x00, 0x30, 0x00, 0x60, 0x00, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x06, 0x00, 0x0E, 0x00, 0x6C, 0x00, 0xDC, 0x03, 0x1E, 0x0E, 0x1F, 0xF8, 0x0F, 0xC0,
/* 'D' 0x44 */ 0xFF, 0xC3, 0xFF, 0x8C, 0x07, 0x30, 0x0E, 0xC0, 0x1B, 0x00, 0x7C, 0x00, 0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x3C, 0x00, 0xF0, 0x03, 0xC0, 0x1F, 0x00, 0x6C, 0x03, 0xB0, 0x1C, 0xFF, 0xE3, 0xFE, 0x00,
/* 'E' 0x45 */ 0xFF, 0xFF, 0xFF, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xEF, 0xFE, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xFF, 0xFF,
/* 'F' 0x46 */ 0xFF, 0xFF, 0xFF, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xFF, 0xDF, 0xFB, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x18, 0x00,
/* 'G' 0x47 */ 0x07, 0xF0, 0x1F, 0xFC, 0x3C, 0x1E, 0x70, 0x07, 0x60, 0x03, 0xE0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x7F, 0xC0, 0x7F, 0xC0, 0x03, 0xC0, 0x03, 0x60, 0x03, 0x60, 0x07, 0x30, 0x0F, 0x3C, 0x1F, 0x1F, 0xFB, 0x07, 0xE1,
/* 'H' 0x48 */ 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xFF, 0xFF, 0xFF, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xC0,
/* 'I' 0x49 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xF0,
/* 'J' 0x4A */ 0x01, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x80, 0xC0, 0x60, 0x3C, 0x1E, 0x0F, 0x07, 0xC7, 0x7F, 0x1F, 0x00,
/* 'K' 0x4B */ 0xC0, 0x3E, 0x03, 0xB0, 0x39, 0x83, 0x8C, 0x38, 0x63, 0x83, 0x38, 0x19, 0xC0, 0xDE, 0x07, 0xB8, 0x38, 0xE1, 0x83, 0x0C, 0x1C, 0x60, 0x73, 0x01, 0x98, 0x0E, 0xC0, 0x3E, 0x00, 0xC0,
/* 'L' 0x4C */ 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xFF, 0xFF, 0xF0,
/* 'M' 0x4D */ 0xE0, 0x07, 0xE0, 0x07, 0xF0, 0x0F, 0xF0, 0x0F, 0xD0, 0x0F, 0xD8, 0x1B, 0xD8, 0x1B, 0xD8, 0x1B, 0xCC, 0x33, 0xCC, 0x33, 0xCC, 0x33, 0xC6, 0x63, 0xC6, 0x63, 0xC6, 0x63, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC1, 0x83,
/* 'N' 0x4E */ 0xE0, 0x1F, 0x00, 0xFC, 0x07, 0xE0, 0x3D, 0x81, 0xEE, 0x0F, 0x30, 0x79, 0xC3, 0xC6, 0x1E, 0x18, 0xF0, 0xE7, 0x83, 0x3C, 0x1D, 0xE0, 0x6F, 0x01, 0xF8, 0x0F, 0xC0, 0x3E, 0x01, 0xC0,
/* 'O' 0x4F */ 0x07, 0xF0, 0x0F, 0xFE, 0x0F, 0x07, 0x86, 0x00, 0xC6, 0x00, 0x33, 0x00, 0x1B, 0x00, 0x07, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x36, 0x00, 0x33, 0x00, 0x18, 0xC0, 0x18, 0x78, 0x3C, 0x1F, 0xFC, 0x03, 0xF8, 0x00,
/* 'P' 0x50 */ 0xFF, 0x8F, 0xFE, 0xC0, 0x6C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x06, 0xFF, 0xEF, 0xFC, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00,
/* 'Q' 0x51 */ 0x07, 0xF0, 0x0F, 0xFE, 0x0F, 0x07, 0x86, 0x00, 0xC6, 0x00, 0x33, 0x00, 0x1B, 0x00, 0x07, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x36, 0x00, 0x33, 0x01, 0x98, 0xC0, 0xFC, 0x78, 0x3C, 0x1F, 0xFF, 0x03, 0xF9, 0x80, 0x00, 0x40,
/* 'R' 0x52 */ 0xFF, 0xE3, 0xFF, 0xCC, 0x03, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x6C, 0x01, 0xB0, 0x0C, 0xFF, 0xE3, 0xFF, 0xCC, 0x03, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x6C, 0x01, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x70,
/* 'S' 0x53 */ 0x0F, 0xE0, 0x7F, 0xC3, 0x83, 0x98, 0x07, 0x60, 0x0D, 0x80, 0x07, 0x00, 0x1E, 0x00, 0x3F, 0x80, 0x3F, 0xC0, 0x0F, 0x80, 0x07, 0xC0, 0x0F, 0x00, 0x3E, 0x00, 0xDE, 0x0E, 0x3F, 0xF0, 0x3F, 0x80,
/* 'T' 0x54 */ 0xFF, 0xFF, 0xFF, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60,
/* 'U' 0x55 */ 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x80, 0xEE, 0x0E, 0x3F, 0xE0, 0xFC, 0x00,
/* 'V' 0x56 */ 0xC0, 0x0F, 0x00, 0x7E, 0x01, 0x98, 0x06, 0x60, 0x39, 0xC0, 0xC3, 0x03, 0x0C, 0x1C, 0x38, 0x60, 0x61, 0x81, 0x8E, 0x07, 0x30, 0x0C, 0xC0, 0x37, 0x00, 0xF8, 0x01, 0xE0, 0x07, 0x80, 0x1C, 0x00,
/* 'W' 0x57 */ 0xE0, 0x30, 0x1D, 0x80, 0xE0, 0x76, 0x07, 0x81, 0xDC, 0x1E, 0x06, 0x70, 0x7C, 0x18, 0xC1, 0xB0, 0xE3, 0x0C, 0xC3, 0x8C, 0x33, 0x0C, 0x38, 0xC6, 0x30, 0x67, 0x18, 0xC1, 0x98, 0x67, 0x06, 0x61, 0xD8, 0x1D, 0x83, 0x60, 0x3C, 0x0D, 0x80, 0xF0, 0x3E, 0x03, 0xC0, 0x70, 0x0F, 0x01, 0xC0, 0x18, 0x07, 0x00,
/* 'X' 0x58 */ 0xE0, 0x1D, 0x80, 0xE7, 0x03, 0x0E, 0x1C, 0x18, 0x60, 0x73, 0x00, 0xFC, 0x01, 0xE0, 0x07, 0x00, 0x1E, 0x00, 0xF8, 0x03, 0x30, 0x1C, 0xE0, 0xE1, 0x83, 0x07, 0x1C, 0x0E, 0xE0, 0x1B, 0x00, 0x70,
/* 'Y' 0x59 */ 0xC0, 0x0F, 0x80, 0x76, 0x01, 0x9C, 0x0C, 0x38, 0x70, 0x61, 0x81, 0xCE, 0x03, 0x30, 0x0F, 0x80, 0x1E, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00,
/* 'Z' 0x5A */ 0xFF, 0xFF, 0xFF, 0xC0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0x60, 0x07, 0x00, 0x70, 0x07, 0x00, 0x30, 0x03, 0x80, 0x38, 0x03, 0x80, 0x18, 0x01, 0xC0, 0x1C, 0x00, 0xFF, 0xFF, 0xFF, 0xC0,
/* '[' 0x5B */ 0xFF, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCF, 0xF0,
/* '\' 0x5C */ 0x81, 0x81, 0x02, 0x06, 0x04, 0x08, 0x18, 0x10, 0x20, 0x60, 0x40, 0x81, 0x81, 0x02, 0x06, 0x04,
/* ']' 0x5D */ 0xFF, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0xF0,
/* '^' 0x5E */ 0x0C, 0x0E, 0x05, 0x86, 0xC3, 0x21, 0x19, 0x8C, 0x83, 0xC1, 0x80,
/* '_' 0x5F */ 0xFF, 0xFE,
/* '`' 0x60 */ 0xE3, 0x8C, 0x30,
/* 'a' 0x61 */ 0x3F, 0x07, 0xF8, 0xE1, 0xCC, 0x0C, 0x00, 0xC0, 0x1C, 0x3F, 0xCF, 0x8C, 0xC0, 0xCC, 0x0C, 0xE3, 0xC7, 0xEF, 0x3C, 0x70,
/* 'b' 0x62 */ 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0xF8, 0xDF, 0xCF, 0x0E, 0xE0, 0x7C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xE0, 0x6F, 0x0E, 0xDF, 0xCC, 0xF8,
/* 'c' 0x63 */ 0x1F, 0x0F, 0xE6, 0x1F, 0x83, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x38, 0x37, 0x1C, 0xFE, 0x1F, 0x00,
/* 'd' 0x64 */ 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x3C, 0xCF, 0xFB, 0x8F, 0xE0, 0xF8, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF8, 0x3B, 0x8F, 0x3F, 0x63, 0xCC,
/* 'e' 0x65 */ 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x3C, 0x07, 0xFF, 0xFF, 0xFE, 0x00, 0xC0, 0x1C, 0x0D, 0xC3, 0x1F, 0xC1, 0xF0,
/* 'f' 0x66 */ 0x3B, 0xD8, 0xC6, 0x7F, 0xEC, 0x63, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x00,
/* 'g' 0x67 */ 0x1E, 0x67, 0xFD, 0xC7, 0xF0, 0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x7C, 0x1D, 0xC7, 0x9F, 0xB1, 0xE6, 0x00, 0xC0, 0x3E, 0x0E, 0x7F, 0xC7, 0xE0,
/* 'h' 0x68 */ 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x33, 0xCD, 0xFB, 0xC7, 0xE0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x30,
/* 'i' 0x69 */ 0xF0, 0x3F, 0xFF, 0xFF, 0xF0,
/* 'j' 0x6A */ 0x33, 0x00, 0x03, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0xE0,
/* 'k' 0x6B */ 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x6C, 0x33, 0x18, 0xCC, 0x37, 0x0F, 0xC3, 0xB8, 0xC6, 0x31, 0xCC, 0x3B, 0x06, 0xC1, 0xF0, 0x30,
/* 'l' 0x6C */ 0xFF, 0xFF, 0xFF, 0xFF, 0xF0,
/* 'm' 0x6D */ 0xCF, 0x1F, 0x6F, 0xDF, 0xFC, 0x78, 0xFC, 0x18, 0x3C, 0x0C, 0x1E, 0x06, 0x0F, 0x03, 0x07, 0x81, 0x83, 0xC0, 0xC1, 0xE0, 0x60, 0xF0, 0x30, 0x78, 0x18, 0x3C, 0x0C, 0x18,
/* 'n' 0x6E */ 0xCF, 0x37, 0xEF, 0x1F, 0x83, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xC0,
/* 'o' 0x6F */ 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x7C, 0x1D, 0xC7, 0x1F, 0xC1, 0xF0,
/* 'p' 0x70 */ 0xCF, 0x8D, 0xFC, 0xF0, 0xEE, 0x06, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3E, 0x06, 0xF0, 0xEF, 0xFC, 0xCF, 0x8C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x00,
/* 'q' 0x71 */ 0x1E, 0x67, 0xFD, 0xC7, 0xF0, 0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x7C, 0x1D, 0xC7, 0x9F, 0xF1, 0xE6, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x60,
/* 'r' 0x72 */ 0xCF, 0x7F, 0x38, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC0,
/* 's' 0x73 */ 0x3E, 0x1F, 0xEE, 0x1B, 0x00, 0xC0, 0x3C, 0x07, 0xF0, 0x3F, 0x01, 0xF0, 0x3E, 0x1D, 0xFE, 0x3F, 0x00,
/* 't' 0x74 */ 0x63, 0x19, 0xFF, 0xB1, 0x8C, 0x63, 0x18, 0xC6, 0x31, 0xE7,
/* 'u' 0x75 */ 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x7E, 0x3D, 0xFB, 0x3C, 0xC0,
/* 'v' 0x76 */ 0xE0, 0x6C, 0x0D, 0x81, 0xB8, 0x63, 0x0C, 0x61, 0x8E, 0x60, 0xCC, 0x19, 0x83, 0xE0, 0x3C, 0x07, 0x00, 0xE0,
/* 'w' 0x77 */ 0xC1, 0xC1, 0xB0, 0xE1, 0xD8, 0x70, 0xCC, 0x2C, 0x66, 0x36, 0x31, 0x9B, 0x18, 0xCD, 0x98, 0x64, 0x6C, 0x16, 0x36, 0x0F, 0x1A, 0x07, 0x8F, 0x03, 0x83, 0x80, 0xC1, 0xC0,
/* 'x' 0x78 */ 0xC1, 0xF8, 0x66, 0x30, 0xCC, 0x3E, 0x07, 0x00, 0xC0, 0x78, 0x36, 0x0C, 0xC6, 0x3B, 0x06, 0xC0, 0xC0,
/* 'y' 0x79 */ 0xE0, 0x6C, 0x0D, 0x83, 0x38, 0x63, 0x0C, 0x63, 0x0C, 0x60, 0xCC, 0x1B, 0x03, 0x60, 0x3C, 0x07, 0x00, 0xE0, 0x18, 0x03, 0x00, 0xE0, 0x78, 0x0E, 0x00,
/* 'z' 0x7A */ 0xFF, 0xFF, 0xF0, 0x18, 0x0C, 0x07, 0x03, 0x81, 0xC0, 0x60, 0x30, 0x18, 0x0E, 0x03, 0xFF, 0xFF, 0xC0,
/* '{' 0x7B */ 0x19, 0xCC, 0x63, 0x18, 0xC6, 0x31, 0x99, 0x86, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x1C, 0x60,
/* '|' 0x7C */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC,
/* '}' 0x7D */ 0xC7, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x0C, 0x33, 0x31, 0x8C, 0x63, 0x18, 0xC6, 0x73, 0x00,
/* '~' 0x7E */ 0x70, 0x3E, 0x09, 0xE4, 0x1F, 0x03, 0x80,
/* 0x7F */
/* 0x80 */ 0x01, 0xF0, 0x1F, 0xF0, 0xE0, 0xC7, 0x00, 0x18, 0x00, 0xC0, 0x07, 0xFF, 0x3F, 0xFC, 0x30, 0x01, 0xFF, 0x8F, 0xFC, 0x0C, 0x00, 0x18, 0x00, 0x70, 0x00, 0xE0, 0x81, 0xFE, 0x03, 0xF0,
/* 0x81 */
/* 0x82 */ 0xF5, 0x80,
/* 0x83 */
/* 0x84 */ 0xCF, 0x34, 0x51, 0x88,
/* 0x85 */ 0xC6, 0x3C, 0x63,
/* 0x86 */ 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x3F, 0xFF, 0xFC, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x00,
/* 0x87 */ 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x3F, 0xFF, 0xFC, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x0F, 0xFF, 0xFF, 0x0C, 0x03, 0x00, 0xC0, 0x30,
/* 0x88 */
/* 0x89 */ 0x38, 0x18, 0x00, 0xF8, 0x30, 0x03, 0x18, 0xC0, 0x04, 0x11, 0x80, 0x0C, 0x66, 0x00, 0x0F, 0x8C, 0x00, 0x0E, 0x30, 0x00, 0x00, 0x40, 0x00, 0x01, 0x80, 0x00, 0x06, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x31, 0xC0, 0xE0, 0x67, 0xC3, 0xC1, 0x98, 0xCC, 0xC3, 0x20, 0x90, 0x8C, 0x63, 0x33, 0x10, 0x7C, 0x3C, 0x60, 0x70, 0x38,
/* 0x8A */ 0x0C, 0x40, 0x1F, 0x00, 0x38, 0x03, 0xF8, 0x1F, 0xF0, 0xE0, 0xE6, 0x01, 0xD8, 0x03, 0x60, 0x01, 0xC0, 0x07, 0x80, 0x0F, 0xE0, 0x0F, 0xF0, 0x03, 0xE0, 0x01, 0xF0, 0x03, 0xC0, 0x0F, 0x80, 0x37, 0x83, 0x8F, 0xFC, 0x0F, 0xE0,
/* 0x8B */ 0x2F, 0x49, 0x99,
/* 0x8C */ 0x01, 0x80, 0x0C, 0x00, 0x60, 0x00, 0x00, 0x0F, 0xE0, 0x7F, 0xC3, 0x83, 0x98, 0x07, 0x60, 0x0D, 0x80, 0x07, 0x00, 0x1E, 0x00, 0x3F, 0x80, 0x3F, 0xC0, 0x0F, 0x80, 0x07, 0xC0, 0x0F, 0x00, 0x3E, 0x00, 0xDE, 0x0E, 0x3F, 0xF0, 0x3F, 0x80,
/* 0x8D */ 0x0C, 0xC0, 0xF8, 0x07, 0x0F, 0xFF, 0xFF, 0xF0, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00,
/* 0x8E */ 0x0C, 0xC0, 0x3C, 0x00, 0xE1, 0xFF, 0xFF, 0xFF, 0x80, 0x1C, 0x01, 0xC0, 0x1C, 0x00, 0xC0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0x60, 0x07, 0x00, 0x70, 0x07, 0x00, 0x30, 0x03, 0x80, 0x38, 0x01, 0xFF, 0xFF, 0xFF, 0x80,
/* 0x8F */ 0x01, 0x80, 0x18, 0x01, 0x80, 0x00, 0x0F, 0xFF, 0xFF, 0xFC, 0x00, 0xE0, 0x0E, 0x00, 0xE0, 0x06, 0x00, 0x70, 0x07, 0x00, 0x70, 0x03, 0x00, 0x38, 0x03, 0x80, 0x38, 0x01, 0x80, 0x1C, 0x01, 0xC0, 0x0F, 0xFF, 0xFF, 0xFC,
/* 0x90 */
/* 0x91 */ 0x6A, 0xF0,
/* 0x92 */ 0xF5, 0x60,
/* 0x93 */ 0x4E, 0x28, 0xA2, 0xCF, 0x30,
/* 0x94 */ 0xCF, 0x34, 0x51, 0x4E, 0x20,
/* 0x95 */ 0x7B, 0xFF, 0xFF, 0xFD, 0xE0,
/* 0x96 */ 0xFF, 0xFF, 0xF0,
/* 0x97 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0,
/* 0x98 */
/* 0x99 */ 0xFF, 0x70, 0x1F, 0xFD, 0xC0, 0x71, 0x87, 0x83, 0xC6, 0x1E, 0x0F, 0x18, 0x68, 0x3C, 0x61, 0xB1, 0xB1, 0x86, 0xC6, 0xC6, 0x19, 0x1B, 0x18, 0x66, 0xCC, 0x61, 0x9B, 0x31, 0x86, 0x3C, 0xC6, 0x18, 0xE3, 0x18, 0x63, 0x8C,
/* 0x9A */ 0x63, 0x0D, 0x83, 0x60, 0x70, 0x00, 0x0F, 0x87, 0xFB, 0x86, 0xC0, 0x30, 0x0F, 0x01, 0xFC, 0x0F, 0xC0, 0x7C, 0x0F, 0x87, 0x7F, 0x8F, 0xC0,
/* 0x9B */ 0x99, 0x92, 0xF4,
/* 0x9C */ 0x07, 0x03, 0x80, 0xC0, 0x60, 0x00, 0x0F, 0x87, 0xFB, 0x86, 0xC0, 0x30, 0x0F, 0x01, 0xFC, 0x0F, 0xC0, 0x7C, 0x0F, 0x87, 0x7F, 0x8F, 0xC0,
/* 0x9D */ 0x03, 0x06, 0x66, 0x64, 0x60, 0xF8, 0xF8, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x78, 0x38,
/* 0x9E */ 0x63, 0x0C, 0x83, 0x60, 0x70, 0x00, 0x3F, 0xFF, 0xFC, 0x06, 0x03, 0x01, 0xC0, 0xE0, 0x70, 0x18, 0x0C, 0x06, 0x03, 0x80, 0xFF, 0xFF, 0xF0,
/* 0x9F */ 0x07, 0x01, 0x80, 0xC0, 0x20, 0x00, 0x3F, 0xFF, 0xFC, 0x06, 0x03, 0x01, 0xC0, 0xE0, 0x70, 0x18, 0x0C, 0x06, 0x03, 0x80, 0xFF, 0xFF, 0xF0,
/* 0xA0 */
/* 0xA1 */ 0xC6, 0xD9, 0xB1, 0xC0,
/* 0xA2 */ 0x83, 0x8D, 0xF1, 0xC0,
/* 0xA3 */ 0x30, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x18, 0x80, 0xCC, 0x06, 0xC0, 0x3C, 0x01, 0xC0, 0x3C, 0x01, 0x60, 0x03, 0x00, 0x18, 0x00, 0xC0, 0x06, 0x00, 0x3F, 0xF9, 0xFF, 0xC0,
/* 0xA4 */ 0xDD, 0xFF, 0xD8, 0xD8, 0x3C, 0x1E, 0x0F, 0x8D, 0xFF, 0xDD, 0x80,
/* 0xA5 */ 0x03, 0x80, 0x03, 0xC0, 0x07, 0xC0, 0x07, 0xC0, 0x04, 0xE0, 0x0C, 0xE0, 0x0C, 0xE0, 0x08, 0x70, 0x18, 0x70, 0x18, 0x70, 0x10, 0x38, 0x3F, 0xF8, 0x3F, 0xF8, 0x30, 0x1C, 0x70, 0x0C, 0x60, 0x0C, 0x60, 0x0E, 0xE0, 0x06, 0x00, 0x0E, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x0F,
/* 0xA6 */ 0xFF, 0xFF, 0xF0, 0x3F, 0xFF, 0xFC,
/* 0xA7 */ 0x0F, 0x03, 0xF0, 0xE7, 0x18, 0x63, 0x0C, 0x70, 0x07, 0x03, 0xF8, 0xC3, 0x98, 0x3B, 0x03, 0xF0, 0x37, 0x06, 0x78, 0xC7, 0xB0, 0x7C, 0x03, 0x80, 0x39, 0x83, 0x30, 0x67, 0x1C, 0x7F, 0x07, 0xC0,
/* 0xA8 */ 0xCF, 0x30,
/* 0xA9 */ 0x03, 0xF0, 0x03, 0xFF, 0x01, 0xE0, 0xE0, 0xE3, 0x1C, 0x73, 0xF3, 0x99, 0x86, 0x6C, 0xC1, 0x8F, 0x30, 0x03, 0xCC, 0x00, 0xF3, 0x00, 0x3C, 0xC1, 0x8D, 0x98, 0x66, 0x77, 0xF3, 0x8E, 0x79, 0xC1, 0xC0, 0xE0, 0x3F, 0xF0, 0x03, 0xF0, 0x00,
/* 0xAA */ 0x0F, 0xC0, 0xFF, 0xC3, 0x03, 0x98, 0x07, 0x60, 0x0D, 0x80, 0x07, 0x00, 0x1E, 0x00, 0x3F, 0x80, 0x3F, 0xC0, 0x0F, 0x80, 0x07, 0xC0, 0x0F, 0x00, 0x3E, 0x00, 0xDC, 0x0E, 0x3F, 0xF0, 0x3F, 0x00, 0x20, 0x01, 0xE0, 0x01, 0x80, 0x06, 0x00, 0xF0, 0x00,
/* 0xAB */ 0x21, 0x63, 0xE7, 0x84, 0x84, 0xE7, 0x63, 0x21,
/* 0xAC */ 0xFF, 0xFF, 0xFF, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03,
/* 0xAD */ 0xFF, 0xF0,
/* 0xAE */ 0x03, 0xF0, 0x03, 0xFF, 0x01, 0xE0, 0xE0, 0xFF, 0x1C, 0x7F, 0xF3, 0x9B, 0x04, 0x6C, 0xC1, 0x8F, 0x30, 0x43, 0xCF, 0xF0, 0xF3, 0xFC, 0x3C, 0xC1, 0x0D, 0xB0, 0x66, 0x7C, 0x1B, 0x8F, 0x07, 0xC1, 0xC0, 0xE0, 0x3F, 0xF0, 0x03, 0xF0, 0x00,
/* 0xAF */ 0x03, 0x00, 0x18, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x80, 0x1C, 0x01, 0xC0, 0x1C, 0x00, 0xC0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0x60, 0x07, 0x00, 0x70, 0x07, 0x00, 0x30, 0x03, 0x80, 0x38, 0x01, 0xFF, 0xFF, 0xFF, 0x80,
/* 0xB0 */ 0x38, 0xFB, 0x1C, 0x18, 0x38, 0xDF, 0x1C,
/* 0xB1 */ 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x7F, 0xE7, 0xFE, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xF0,
/* 0xB2 */ 0x76, 0x31, 0x87, 0x80,
/* 0xB3 */ 0x66, 0x66, 0x66, 0x67, 0x7E, 0xE6, 0x66, 0x66, 0x66,
/* 0xB4 */ 0x3B, 0x99, 0x80,
/* 0xB5 */ 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x1C, 0xE3, 0xCF, 0xEF, 0xFC, 0x7C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x00,
/* 0xB6 */ 0x1F, 0xE7, 0xFD, 0xF3, 0x7E, 0x6F, 0xCD, 0xF9, 0xBF, 0x37, 0xE6, 0x7C, 0xCF, 0x98, 0xF3, 0x06, 0x60, 0xCC, 0x19, 0x83, 0x30, 0x66, 0x0C, 0xC1, 0x98, 0x33, 0x06, 0x60, 0xCC,
/* 0xB7 */ 0xF0,
/* 0xB8 */ 0x10, 0xF0, 0xE3, 0x78,
/* 0xB9 */ 0x1F, 0x01, 0xFC, 0x1C, 0x70, 0xC1, 0x80, 0x0C, 0x00, 0xE0, 0xFF, 0x1F, 0x18, 0xC0, 0xC6, 0x06, 0x38, 0x70, 0xFF, 0xE3, 0xC7, 0x00, 0x30, 0x03, 0x00, 0x18, 0x00, 0xC0, 0x03, 0xC0,
/* 0xBA */ 0x3F, 0x1F, 0xEE, 0x1B, 0x00, 0xC0, 0x3C, 0x07, 0xF0, 0x3E, 0x01, 0xF0, 0x3C, 0x0D, 0xDE, 0x7F, 0x02, 0x01, 0xE0, 0x18, 0x46, 0x0F, 0x00,
/* 0xBB */ 0x88, 0xC6, 0xE7, 0x21, 0x21, 0xE7, 0xC6, 0x88,
/* 0xBC */ 0xC3, 0x31, 0x8C, 0x63, 0x10, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xFF, 0xFF, 0xF0,
/* 0xBD */ 0x77, 0x66, 0xCC, 0xC8,
/* 0xBE */ 0xC7, 0x9B, 0x36, 0xCC, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x80,
/* 0xBF */ 0x0C, 0x03, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x18, 0x0C, 0x07, 0x03, 0x81, 0xC0, 0x60, 0x30, 0x18, 0x0E, 0x03, 0xFF, 0xFF, 0xC0,
/* 0xC0 */ 0x03, 0x80, 0x18, 0x00, 0x40, 0x00, 0x00, 0xFF, 0xE3, 0xFF, 0xCC, 0x03, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x6C, 0x01, 0xB0, 0x0C, 0xFF, 0xE3, 0xFF, 0xCC, 0x03, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x6C, 0x01, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x70,
/* 0xC1 */ 0x01, 0xC0, 0x0C, 0x00, 0x20, 0x00, 0x00, 0x07, 0x80, 0x1E, 0x00, 0x78, 0x03, 0xF0, 0x0C, 0xC0, 0x33, 0x01, 0xCE, 0x06, 0x18, 0x18, 0x60, 0xE1, 0xC3, 0x03, 0x0F, 0xFC, 0x7F, 0xF9, 0x80, 0x66, 0x01, 0xB8, 0x07, 0xC0, 0x0F, 0x00, 0x30,
/* 0xC2 */ 0x07, 0x00, 0x3E, 0x01, 0x8C, 0x00, 0x00, 0x07, 0x80, 0x1E, 0x00, 0x78, 0x03, 0xF0, 0x0C, 0xC0, 0x33, 0x01, 0xCE, 0x06, 0x18, 0x18, 0x60, 0xE1, 0xC3, 0x03, 0x0F, 0xFC, 0x7F, 0xF9, 0x80, 0x66, 0x01, 0xB8, 0x07, 0xC0, 0x0F, 0x00, 0x30,
/* 0xC3 */ 0x10, 0x40, 0x63, 0x00, 0xF8, 0x01, 0xE0, 0x07, 0x80, 0x1E, 0x00, 0x78, 0x03, 0xF0, 0x0C, 0xC0, 0x33, 0x01, 0xCE, 0x06, 0x18, 0x18, 0x60, 0xE1, 0xC3, 0x03, 0x0F, 0xFC, 0x7F, 0xF9, 0x80, 0x66, 0x01, 0xB8, 0x07, 0xC0, 0x0F, 0x00, 0x30,
/* 0xC4 */ 0x0C, 0xC0, 0x33, 0x00, 0x00, 0x01, 0xE0, 0x07, 0x80, 0x1E, 0x00, 0xFC, 0x03, 0x30, 0x0C, 0xC0, 0x73, 0x81, 0x86, 0x06, 0x18, 0x38, 0x70, 0xC0, 0xC3, 0xFF, 0x1F, 0xFE, 0x60, 0x19, 0x80, 0x6E, 0x01, 0xF0, 0x03, 0xC0, 0x0C,
/* 0xC5 */ 0x18, 0x0C, 0x06, 0x00, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xFF, 0xFF, 0xF0,
/* 0xC6 */ 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x7E, 0x03, 0xFF, 0x0E, 0x07, 0x38, 0x07, 0x60, 0x06, 0xC0, 0x03, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x18, 0x00, 0x30, 0x00, 0x60, 0x00, 0xE0, 0x06, 0xC0, 0x0D, 0xC0, 0x31, 0xE0, 0xE1, 0xFF, 0x80, 0xFC, 0x00,
/* 0xC7 */ 0x07, 0xE0, 0x3F, 0xF0, 0xE0, 0x73, 0x80, 0x66, 0x00, 0x7C, 0x00, 0x30, 0x00, 0x60, 0x00, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x06, 0x00, 0x0E, 0x00, 0x6C, 0x01, 0xDC, 0x03, 0x1C, 0x1E, 0x1F, 0xF8, 0x0F, 0xC0, 0x08, 0x00, 0x1E, 0x00, 0x0C, 0x01, 0x18, 0x01, 0xE0, 0x00,
/* 0xC8 */ 0x06, 0x30, 0x07, 0xC0, 0x07, 0x00, 0x3F, 0x01, 0xFF, 0x87, 0x03, 0x9C, 0x03, 0xB0, 0x03, 0x60, 0x01, 0x80, 0x03, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x18, 0x00, 0x30, 0x00, 0x70, 0x03, 0x60, 0x06, 0xE0, 0x18, 0xF0, 0x70, 0xFF, 0xC0, 0x7E, 0x00,
/* 0xC9 */ 0x07, 0x00, 0x60, 0x0C, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xEF, 0xFE, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xFF, 0xFF,
/* 0xCA */ 0xFF, 0xE7, 0xFF, 0x30, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x18, 0x00, 0xFF, 0xE7, 0xFF, 0x30, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x18, 0x00, 0xFF, 0xF7, 0xFF, 0x80, 0x18, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x01, 0xE0,
/* 0xCB */ 0x19, 0x81, 0x98, 0x00, 0x0F, 0xFF, 0xFF, 0xFC, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0F, 0xFE, 0xFF, 0xEC, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0F, 0xFF, 0xFF, 0xF0,
/* 0xCC */ 0x08, 0xC0, 0xF8, 0x07, 0x0F, 0xFF, 0xFF, 0xFC, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0F, 0xFE, 0xFF, 0xEC, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0F, 0xFF, 0xFF, 0xF0,
/* 0xCD */ 0x36, 0xC0, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
/* 0xCE */ 0x39, 0xFC, 0x40, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC0,
/* 0xCF */ 0x18, 0xC0, 0x3E, 0x00, 0x70, 0x3F, 0xF0, 0xFF, 0xE3, 0x01, 0xCC, 0x03, 0xB0, 0x06, 0xC0, 0x1F, 0x00, 0x3C, 0x00, 0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x3C, 0x00, 0xF0, 0x07, 0xC0, 0x1B, 0x00, 0xEC, 0x07, 0x3F, 0xF8, 0xFF, 0x80,
/* 0xD0 */ 0x7F, 0xE0, 0xFF, 0xE1, 0x80, 0xE3, 0x00, 0xE6, 0x00, 0xCC, 0x01, 0xD8, 0x01, 0xB0, 0x03, 0xFE, 0x07, 0xFC, 0x0D, 0x80, 0x1B, 0x00, 0x36, 0x00, 0x6C, 0x01, 0x98, 0x07, 0x30, 0x1C, 0x7F, 0xF0, 0xFF, 0xC0,
/* 0xD1 */ 0x01, 0x80, 0x18, 0x01, 0x80, 0x00, 0x0E, 0x01, 0xF0, 0x0F, 0xC0, 0x7E, 0x03, 0xD8, 0x1E, 0xE0, 0xF3, 0x07, 0x9C, 0x3C, 0x61, 0xE1, 0x8F, 0x0E, 0x78, 0x33, 0xC1, 0xDE, 0x06, 0xF0, 0x1F, 0x80, 0xFC, 0x03, 0xE0, 0x1C,
/* 0xD2 */ 0x0C, 0xC0, 0x3C, 0x00, 0xE1, 0xC0, 0x3E, 0x01, 0xF8, 0x0F, 0xC0, 0x7B, 0x03, 0xDC, 0x1E, 0x60, 0xF3, 0x87, 0x8C, 0x3C, 0x31, 0xE1, 0xCF, 0x06, 0x78, 0x3B, 0xC0, 0xDE, 0x03, 0xF0, 0x1F, 0x80, 0x7C, 0x03, 0x80,
/* 0xD3 */ 0x00, 0xE0, 0x00, 0x60, 0x00, 0x40, 0x00, 0x00, 0x00, 0x7F, 0x00, 0xFF, 0xE0, 0xF0, 0x78, 0x60, 0x0C, 0x60, 0x03, 0x30, 0x01, 0xB0, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x0F, 0x00, 0x07, 0x80, 0x03, 0x60, 0x03, 0x30, 0x01, 0x8C, 0x01, 0x87, 0x83, 0xC1, 0xFF, 0xC0, 0x3F, 0x80,
/* 0xD4 */ 0x03, 0xC0, 0x01, 0xE0, 0x01, 0x98, 0x00, 0x00, 0x00, 0x7F, 0x00, 0xFF, 0xE0, 0xF0, 0x78, 0x60, 0x0C, 0x60, 0x03, 0x30, 0x01, 0xB0, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x0F, 0x00, 0x07, 0x80, 0x03, 0x60, 0x03, 0x30, 0x01, 0x8C, 0x01, 0x87, 0x83, 0xC1, 0xFF, 0xC0, 0x3F, 0x80,
/* 0xD5 */ 0x03, 0xB8, 0x01, 0x98, 0x00, 0x98, 0x00, 0x00, 0x00, 0x7F, 0x00, 0xFF, 0xE0, 0xF0, 0x78, 0x60, 0x0C, 0x60, 0x03, 0x30, 0x01, 0xB0, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x0F, 0x00, 0x07, 0x80, 0x03, 0x60, 0x03, 0x30, 0x01, 0x8C, 0x01, 0x87, 0x83, 0xC1, 0xFF, 0xC0, 0x3F, 0x80,
/* 0xD6 */ 0x06, 0x30, 0x03, 0x18, 0x00, 0x00, 0x00, 0xFE, 0x01, 0xFF, 0xC1, 0xE0, 0xF0, 0xC0, 0x18, 0xC0, 0x06, 0x60, 0x03, 0x60, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x0F, 0x00, 0x06, 0xC0, 0x06, 0x60, 0x03, 0x18, 0x03, 0x0F, 0x07, 0x83, 0xFF, 0x80, 0x7F, 0x00,
/* 0xD7 */ 0x81, 0xC3, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0xC3, 0x81,
/* 0xD8 */ 0x0C, 0xC0, 0x1E, 0x00, 0x78, 0x3F, 0xF8, 0xFF, 0xF3, 0x00, 0xEC, 0x01, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x6C, 0x03, 0x3F, 0xF8, 0xFF, 0xF3, 0x00, 0xEC, 0x01, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x6C, 0x01, 0xB0, 0x06, 0xC0, 0x1C,
/* 0xD9 */ 0x03, 0x00, 0x7C, 0x03, 0x70, 0x19, 0x80, 0xF8, 0x03, 0xC3, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3E, 0x03, 0xB8, 0x38, 0xFF, 0x83, 0xF0,
/* 0xDA */ 0x03, 0x80, 0x18, 0x01, 0x80, 0x00, 0x0C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF8, 0x0E, 0xE0, 0xE3, 0xFE, 0x0F, 0xC0,
/* 0xDB */ 0x0E, 0xE0, 0x66, 0x03, 0x60, 0x00, 0x0C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF8, 0x0E, 0xE0, 0xE3, 0xFE, 0x0F, 0xC0,
/* 0xDC */ 0x0C, 0xC0, 0x66, 0x00, 0x01, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1F, 0x01, 0xDC, 0x1C, 0x7F, 0xC1, 0xF8, 0x00,
/* 0xDD */ 0x01, 0x80, 0x0C, 0x00, 0x60, 0x00, 0x00, 0xC0, 0x0F, 0x80, 0x76, 0x01, 0x9C, 0x0C, 0x38, 0x70, 0x61, 0x81, 0xCE, 0x03, 0x30, 0x0F, 0x80, 0x1E, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00,
/* 0xDE */ 0xFF, 0xFF, 0xFF, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x04, 0x00, 0x78, 0x01, 0x81, 0x18, 0x0F, 0x00,
/* 0xDF */ 0x1F, 0x0F, 0xF3, 0x87, 0x60, 0x6C, 0x0D, 0x81, 0xB0, 0x66, 0x38, 0xC7, 0xD8, 0x1B, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x3E, 0x0E, 0xCF, 0x99, 0xE0,
/* 0xE0 */ 0x0C, 0x61, 0x8C, 0x03, 0x3D, 0xFC, 0xE3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x00,
/* 0xE1 */ 0x07, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x00, 0x03, 0xF0, 0x7F, 0x8E, 0x1C, 0xC0, 0xC0, 0x0C, 0x01, 0xC3, 0xFC, 0xF8, 0xCC, 0x0C, 0xC0, 0xCE, 0x3C, 0x7E, 0xF3, 0xC7,
/* 0xE2 */ 0x0C, 0x01, 0xE0, 0x1B, 0x03, 0x30, 0x00, 0x03, 0xF0, 0x7F, 0x8E, 0x1C, 0xC0, 0xC0, 0x0C, 0x01, 0xC3, 0xFC, 0xF8, 0xCC, 0x0C, 0xC0, 0xCE, 0x3C, 0x7E, 0xF3, 0xC7,
/* 0xE3 */ 0x20, 0x82, 0x10, 0x3F, 0x01, 0xE0, 0x00, 0x03, 0xF0, 0x7F, 0x8E, 0x1C, 0xC0, 0xC0, 0x0C, 0x01, 0xC3, 0xFC, 0xF8, 0xCC, 0x0C, 0xC0, 0xCE, 0x3C, 0x7E, 0xF3, 0xC7,
/* 0xE4 */ 0x19, 0x81, 0x98, 0x00, 0x00, 0x00, 0x3F, 0x07, 0xF8, 0xE1, 0xCC, 0x0C, 0x00, 0xC0, 0x1C, 0x3F, 0xCF, 0x8C, 0xC0, 0xCC, 0x0C, 0xE3, 0xC7, 0xEF, 0x3C, 0x70,
/* 0xE5 */ 0x3B, 0x30, 0x06, 0x31, 0x8C, 0x63, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x18, 0xC6, 0x30,
/* 0xE6 */ 0x07, 0x01, 0x80, 0xC0, 0x20, 0x00, 0x07, 0xC3, 0xF9, 0x87, 0xE0, 0xF0, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0E, 0x0D, 0xC7, 0x3F, 0x87, 0xC0,
/* 0xE7 */ 0x1F, 0x0F, 0xE7, 0x1D, 0x83, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x38, 0x37, 0x1C, 0xFE, 0x1F, 0x02, 0x00, 0xE0, 0x0C, 0x23, 0x07, 0x80,
/* 0xE8 */ 0x21, 0x0C, 0xC1, 0xE0, 0x78, 0x00, 0x07, 0xC3, 0xF9, 0x87, 0xE0, 0xF0, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0E, 0x0D, 0xC7, 0x3F, 0x87, 0xC0,
/* 0xE9 */ 0x03, 0x00, 0xC0, 0x30, 0x04, 0x00, 0x00, 0x3E, 0x0F, 0xE3, 0x8E, 0xE0, 0x78, 0x0F, 0xFF, 0xFF, 0xFC, 0x01, 0x80, 0x38, 0x1B, 0x86, 0x3F, 0x83, 0xE0,
/* 0xEA */ 0x1F, 0x07, 0xF1, 0x87, 0x60, 0x6C, 0x07, 0xFF, 0xFF, 0xFE, 0x00, 0xC0, 0x1C, 0x1D, 0x87, 0x1F, 0xC1, 0xF8, 0x06, 0x01, 0x80, 0x30, 0x06, 0x00, 0x78,
/* 0xEB */ 0x31, 0x86, 0x30, 0x00, 0x00, 0x01, 0xF0, 0x7F, 0x1C, 0x77, 0x03, 0xC0, 0x7F, 0xFF, 0xFF, 0xE0, 0x0C, 0x01, 0xC0, 0xDC, 0x31, 0xFC, 0x1F, 0x00,
/* 0xEC */ 0x31, 0x82, 0x60, 0x6C, 0x07, 0x00, 0x00, 0x3E, 0x0F, 0xE3, 0x8E, 0xE0, 0x78, 0x0F, 0xFF, 0xFF, 0xFC, 0x01, 0x80, 0x38, 0x1B, 0x86, 0x3F, 0x83, 0xE0,
/* 0xED */ 0x39, 0x99, 0x80, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x18, 0xC6, 0x31, 0x80,
/* 0xEE */ 0x71, 0xED, 0xA3, 0x00, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC0,
/* 0xEF */ 0x00, 0x67, 0x00, 0x66, 0x00, 0x64, 0x00, 0x6C, 0x00, 0x60, 0x1E, 0x60, 0x3F, 0xE0, 0x71, 0xE0, 0xE0, 0xE0, 0xC0, 0x60, 0xC0, 0x60, 0xC0, 0x60, 0xC0, 0x60, 0xC0, 0x60, 0xE0, 0xE0, 0x71, 0xE0, 0x3F, 0x60, 0x1E, 0x60,
/* 0xF0 */ 0x00, 0x60, 0x06, 0x03, 0xF0, 0x06, 0x00, 0x61, 0xE6, 0x3F, 0xE7, 0x1E, 0xE0, 0xEC, 0x06, 0xC0, 0x6C, 0x06, 0xC0, 0x6C, 0x06, 0xE0, 0xE7, 0x1E, 0x3F, 0xE1, 0xE6,
/* 0xF1 */ 0x03, 0x81, 0xC0, 0x60, 0x30, 0x00, 0x33, 0xCD, 0xFB, 0xC7, 0xE0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x30,
/* 0xF2 */ 0x31, 0x84, 0xC1, 0xA0, 0x38, 0x00, 0x33, 0xCD, 0xFB, 0xC7, 0xE0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x30,
/* 0xF3 */ 0x07, 0x00, 0xC0, 0x30, 0x04, 0x00, 0x00, 0x3E, 0x0F, 0xE3, 0x8E, 0xE0, 0xF8, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF8, 0x3B, 0x8E, 0x3F, 0x83, 0xE0,
/* 0xF4 */ 0x0C, 0x03, 0xC0, 0xD8, 0x19, 0x80, 0x00, 0x3E, 0x0F, 0xE3, 0x8E, 0xE0, 0xF8, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF8, 0x3B, 0x8E, 0x3F, 0x83, 0xE0,
/* 0xF5 */ 0x0C, 0xC3, 0xB8, 0x66, 0x0D, 0x80, 0x00, 0x3E, 0x0F, 0xE3, 0x8E, 0xE0, 0xF8, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF8, 0x3B, 0x8E, 0x3F, 0x83, 0xE0,
/* 0xF6 */ 0x31, 0x86, 0x30, 0x00, 0x00, 0x01, 0xF0, 0x7F, 0x1C, 0x77, 0x07, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0xC1, 0xDC, 0x71, 0xFC, 0x1F, 0x00,
/* 0xF7 */ 0x06, 0x00, 0x60, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x06, 0x00,
/* 0xF8 */ 0xC7, 0x37, 0x8E, 0x03, 0x3D, 0xFC, 0xE3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x00,
/* 0xF9 */ 0x0E, 0x07, 0xC1, 0xB8, 0x6C, 0x1F, 0x03, 0x8C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x07, 0xE3, 0xDF, 0xB3, 0xCC,
/* 0xFA */ 0x03, 0x01, 0x80, 0x60, 0x30, 0x00, 0x30, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x1F, 0x8F, 0x7E, 0xCF, 0x30,
/* 0xFB */ 0x1D, 0xC6, 0x61, 0xB0, 0xCC, 0x00, 0x30, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x1F, 0x8F, 0x7E, 0xCF, 0x30,
/* 0xFC */ 0x31, 0x8C, 0x60, 0x00, 0x00, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x7E, 0x3D, 0xFB, 0x3C, 0xC0,
/* 0xFD */ 0x03, 0x80, 0x60, 0x18, 0x06, 0x00, 0x01, 0xC0, 0xD8, 0x1B, 0x06, 0x70, 0xC6, 0x18, 0xC6, 0x18, 0xC1, 0x98, 0x36, 0x06, 0xC0, 0x78, 0x0E, 0x01, 0xC0, 0x30, 0x06, 0x01, 0xC0, 0xF0, 0x1C, 0x00,
/* 0xFE */ 0x61, 0x86, 0x3E, 0xF9, 0x86, 0x18, 0x61, 0x86, 0x18, 0x61, 0x87, 0x8E, 0x10, 0x83, 0xC3, 0x78,
/* 0xFF */ 0xF0,
};
const GFXglyph FreeSans12pt_Win1250Glyphs[] PROGMEM = {
/* 0x01 */ { 0, 19, 20, 21, 1, -17 },
/* 0x02 */ { 48, 19, 20, 21, 1, -17 },
/* 0x03 */ { 96, 21, 20, 23, 1, -17 },
/* 0x04 */ { 149, 21, 20, 23, 1, -17 },
/* 0x05 */ { 202, 20, 20, 22, 1, -17 },
/* 0x06 */ { 252, 20, 20, 22, 1, -17 },
/* 0x07 */ { 302, 0, 0, 8, 0, 0 },
/* 0x08 */ { 302, 23, 20, 25, 1, -17 },
/* 0x09 */ { 360, 23, 16, 25, 1, -16 },
/* 0x0A */ { 406, 0, 0, 8, 0, 0 },
/* 0x0B */ { 406, 21, 20, 23, 1, -17 },
/* 0x0C */ { 459, 19, 18, 21, 1, -15 },
/* 0x0D */ { 502, 0, 0, 8, 0, 0 },
/* 0x0E */ { 502, 20, 20, 22, 1, -17 },
/* 0x0F */ { 552, 21, 21, 23, 1, -18 },
/* 0x10 */ { 608, 19, 20, 21, 1, -17 },
/* 0x11 */ { 656, 21, 20, 23, 1, -17 },
/* 0x12 */ { 709, 20, 20, 22, 1, -17 },
/* 0x13 */ { 759, 21, 20, 23, 1, -17 },
/* 0x14 */ { 812, 21, 20, 23, 1, -17 },
/* 0x15 */ { 865, 22, 20, 24, 1, -17 },
/* 0x16 */ { 920, 16, 20, 18, 1, -17 },
/* 0x17 */ { 960, 21, 20, 23, 1, -17 },
/* 0x18 */ { 1013, 23, 20, 25, 1, -17 },
/* 0x19 */ { 1071, 21, 20, 23, 1, -17 },
/* 0x1A */ { 1124, 15, 19, 17, 1, -16 },
/* 0x1B */ { 1160, 24, 21, 26, 1, -18 },
/* 0x1C */ { 1223, 21, 20, 23, 1, -17 },
/* 0x1D */ { 1276, 21, 21, 23, 1, -18 },
/* 0x1E */ { 1332, 20, 20, 22, 1, -17 },
/* 0x1F */ { 1382, 15, 20, 17, 1, -17 },
/* ' ' 0x20 */ { 1420, 0, 0, 6, 0, 0 },
/* '!' 0x21 */ { 1420, 2, 18, 8, 3, -16 },
/* '"' 0x22 */ { 1425, 6, 6, 8, 1, -15 },
/* '#' 0x23 */ { 1430, 13, 16, 13, 0, -14 },
/* '$' 0x24 */ { 1456, 11, 20, 13, 1, -16 },
/* '%' 0x25 */ { 1484, 20, 17, 21, 1, -15 },
/* '&' 0x26 */ { 1527, 14, 17, 16, 1, -15 },
/* ''' 0x27 */ { 1557, 2, 6, 5, 1, -15 },
/* '(' 0x28 */ { 1559, 5, 23, 8, 2, -16 },
/* ')' 0x29 */ { 1574, 5, 23, 8, 1, -16 },
/* '*' 0x2A */ { 1589, 7, 7, 9, 1, -16 },
/* '+' 0x2B */ { 1596, 10, 11, 14, 2, -9 },
/* ',' 0x2C */ { 1610, 2, 6, 7, 2, 0 },
/* '-' 0x2D */ { 1612, 6, 2, 8, 1, -6 },
/* '.' 0x2E */ { 1614, 2, 2, 6, 2, 0 },
/* '/' 0x2F */ { 1615, 7, 18, 7, 0, -16 },
/* '0' 0x30 */ { 1631, 11, 17, 13, 1, -15 },
/* '1' 0x31 */ { 1655, 5, 17, 13, 3, -15 },
/* '2' 0x32 */ { 1666, 11, 17, 13, 1, -15 },
/* '3' 0x33 */ { 1690, 11, 17, 13, 1, -15 },
/* '4' 0x34 */ { 1714, 11, 17, 13, 1, -15 },
/* '5' 0x35 */ { 1738, 11, 17, 13, 1, -15 },
/* '6' 0x36 */ { 1762, 11, 17, 13, 1, -15 },
/* '7' 0x37 */ { 1786, 11, 17, 13, 1, -15 },
/* '8' 0x38 */ { 1810, 11, 17, 13, 1, -15 },
/* '9' 0x39 */ { 1834, 11, 17, 13, 1, -15 },
/* ':' 0x3A */ { 1858, 2, 13, 6, 2, -11 },
/* ';' 0x3B */ { 1862, 2, 16, 6, 2, -10 },
/* '<' 0x3C */ { 1866, 12, 11, 14, 1, -9 },
/* '=' 0x3D */ { 1883, 12, 6, 14, 1, -7 },
/* '>' 0x3E */ { 1892, 12, 11, 14, 1, -9 },
/* '?' 0x3F */ { 1909, 10, 18, 13, 2, -16 },
/* '@' 0x40 */ { 1932, 22, 21, 24, 1, -16 },
/* 'A' 0x41 */ { 1990, 14, 18, 16, 1, -16 },
/* 'B' 0x42 */ { 2022, 13, 18, 16, 2, -16 },
/* 'C' 0x43 */ { 2052, 15, 18, 17, 1, -16 },
/* 'D' 0x44 */ { 2086, 14, 18, 17, 2, -16 },
/* 'E' 0x45 */ { 2118, 12, 18, 15, 2, -16 },
/* 'F' 0x46 */ { 2145, 11, 18, 14, 2, -16 },
/* 'G' 0x47 */ { 2170, 16, 18, 18, 1, -16 },
/* 'H' 0x48 */ { 2206, 13, 18, 17, 2, -16 },
/* 'I' 0x49 */ { 2236, 2, 18, 7, 2, -16 },
/* 'J' 0x4A */ { 2241, 9, 18, 13, 1, -16 },
/* 'K' 0x4B */ { 2262, 13, 18, 16, 2, -16 },
/* 'L' 0x4C */ { 2292, 10, 18, 14, 2, -16 },
/* 'M' 0x4D */ { 2315, 16, 18, 20, 2, -16 },
/* 'N' 0x4E */ { 2351, 13, 18, 18, 2, -16 },
/* 'O' 0x4F */ { 2381, 17, 18, 19, 1, -16 },
/* 'P' 0x50 */ { 2420, 12, 18, 16, 2, -16 },
/* 'Q' 0x51 */ { 2447, 17, 19, 19, 1, -16 },
/* 'R' 0x52 */ { 2488, 14, 18, 17, 2, -16 },
/* 'S' 0x53 */ { 2520, 14, 18, 16, 1, -16 },
/* 'T' 0x54 */ { 2552, 12, 18, 15, 1, -16 },
/* 'U' 0x55 */ { 2579, 13, 18, 17, 2, -16 },
/* 'V' 0x56 */ { 2609, 14, 18, 15, 1, -16 },
/* 'W' 0x57 */ { 2641, 22, 18, 22, 0, -16 },
/* 'X' 0x58 */ { 2691, 14, 18, 16, 1, -16 },
/* 'Y' 0x59 */ { 2723, 14, 18, 16, 1, -16 },
/* 'Z' 0x5A */ { 2755, 13, 18, 15, 1, -16 },
/* '[' 0x5B */ { 2785, 4, 23, 7, 2, -16 },
/* '\' 0x5C */ { 2797, 7, 18, 7, 0, -16 },
/* ']' 0x5D */ { 2813, 4, 23, 7, 1, -16 },
/* '^' 0x5E */ { 2825, 9, 9, 11, 1, -15 },
/* '_' 0x5F */ { 2836, 15, 1, 13, -1, 5 },
/* '`' 0x60 */ { 2838, 5, 4, 6, 1, -16 },
/* 'a' 0x61 */ { 2841, 12, 13, 13, 1, -11 },
/* 'b' 0x62 */ { 2861, 12, 18, 13, 1, -16 },
/* 'c' 0x63 */ { 2888, 10, 13, 12, 1, -11 },
/* 'd' 0x64 */ { 2905, 11, 18, 13, 1, -16 },
/* 'e' 0x65 */ { 2930, 11, 13, 13, 1, -11 },
/* 'f' 0x66 */ { 2948, 5, 18, 7, 1, -16 },
/* 'g' 0x67 */ { 2960, 11, 18, 13, 1, -11 },
/* 'h' 0x68 */ { 2985, 10, 18, 13, 1, -16 },
/* 'i' 0x69 */ { 3008, 2, 18, 5, 2, -16 },
/* 'j' 0x6A */ { 3013, 4, 23, 6, 0, -16 },
/* 'k' 0x6B */ { 3025, 10, 18, 12, 1, -16 },
/* 'l' 0x6C */ { 3048, 2, 18, 5, 1, -16 },
/* 'm' 0x6D */ { 3053, 17, 13, 19, 1, -11 },
/* 'n' 0x6E */ { 3081, 10, 13, 13, 1, -11 },
/* 'o' 0x6F */ { 3098, 11, 13, 13, 1, -11 },
/* 'p' 0x70 */ { 3116, 12, 17, 13, 1, -11 },
/* 'q' 0x71 */ { 3142, 11, 17, 13, 1, -11 },
/* 'r' 0x72 */ { 3166, 6, 13, 8, 1, -11 },
/* 's' 0x73 */ { 3176, 10, 13, 12, 1, -11 },
/* 't' 0x74 */ { 3193, 5, 16, 7, 1, -14 },
/* 'u' 0x75 */ { 3203, 10, 13, 13, 1, -11 },
/* 'v' 0x76 */ { 3220, 11, 13, 12, 0, -11 },
/* 'w' 0x77 */ { 3238, 17, 13, 17, 0, -11 },
/* 'x' 0x78 */ { 3266, 10, 13, 11, 1, -11 },
/* 'y' 0x79 */ { 3283, 11, 18, 11, 0, -11 },
/* 'z' 0x7A */ { 3308, 10, 13, 12, 1, -11 },
/* '{' 0x7B */ { 3325, 5, 23, 8, 1, -16 },
/* '|' 0x7C */ { 3340, 2, 23, 6, 2, -16 },
/* '}' 0x7D */ { 3346, 5, 23, 8, 2, -16 },
/* '~' 0x7E */ { 3361, 10, 5, 12, 1, -9 },
/* 0x7F */ { 3368, 0, 0, 0, 0, 0 },
/* 0x80 */ { 3368, 14, 17, 16, 1, -17 },
/* 0x81 */ { 3398, 0, 0, 8, 0, 0 },
/* 0x82 */ { 3398, 2, 5, 6, 2, -2 },
/* 0x83 */ { 3400, 0, 0, 8, 0, 0 },
/* 0x84 */ { 3400, 6, 5, 10, 2, -2 },
/* 0x85 */ { 3404, 12, 2, 16, 2, -2 },
/* 0x86 */ { 3407, 10, 21, 13, 2, -17 },
/* 0x87 */ { 3434, 10, 20, 13, 2, -17 },
/* 0x88 */ { 3459, 0, 0, 8, 0, 0 },
/* 0x89 */ { 3459, 23, 18, 24, 0, -18 },
/* 0x8A */ { 3511, 14, 21, 16, 1, -21 },
/* 0x8B */ { 3548, 3, 8, 6, 1, -11 },
/* 0x8C */ { 3551, 14, 22, 16, 1, -22 },
/* 0x8D */ { 3590, 12, 21, 15, 1, -21 },
/* 0x8E */ { 3622, 13, 21, 15, 1, -21 },
/* 0x8F */ { 3657, 13, 22, 15, 1, -22 },
/* 0x90 */ { 3693, 0, 0, 8, 0, 0 },
/* 0x91 */ { 3693, 2, 6, 6, 2, -18 },
/* 0x92 */ { 3695, 2, 6, 6, 2, -18 },
/* 0x93 */ { 3697, 6, 6, 10, 2, -18 },
/* 0x94 */ { 3702, 6, 6, 10, 2, -18 },
/* 0x95 */ { 3707, 6, 6, 10, 2, -11 },
/* 0x96 */ { 3712, 10, 2, 12, 1, -8 },
/* 0x97 */ { 3715, 22, 2, 24, 1, -8 },
/* 0x98 */ { 3721, 0, 0, 8, 0, 0 },
/* 0x99 */ { 3721, 22, 13, 24, 2, -18 },
/* 0x9A */ { 3757, 10, 18, 12, 1, -18 },
/* 0x9B */ { 3780, 3, 8, 6, 2, -10 },
/* 0x9C */ { 3783, 10, 18, 12, 1, -18 },
/* 0x9D */ { 3806, 8, 18, 11, 1, -18 },
/* 0x9E */ { 3824, 10, 18, 12, 1, -18 },
/* 0x9F */ { 3847, 10, 18, 12, 1, -18 },
/* 0xA0 */ { 3870, 0, 0, 7, 0, 0 },
/* 0xA1 */ { 3870, 7, 4, 8, 0, -18 },
/* 0xA2 */ { 3874, 7, 4, 8, 0, -18 },
/* 0xA3 */ { 3878, 13, 18, 15, 1, -18 },
/* 0xA4 */ { 3908, 9, 9, 13, 2, -13 },
/* 0xA5 */ { 3919, 16, 23, 16, 1, -18 },
/* 0xA6 */ { 3965, 2, 23, 6, 2, -18 },
/* 0xA7 */ { 3971, 11, 23, 13, 1, -18 },
/* 0xA8 */ { 4003, 6, 2, 8, 1, -17 },
/* 0xA9 */ { 4005, 18, 17, 19, 1, -17 },
/* 0xAA */ { 4044, 14, 23, 16, 1, -18 },
/* 0xAB */ { 4085, 8, 8, 12, 2, -11 },
/* 0xAC */ { 4093, 12, 6, 14, 1, -9 },
/* 0xAD */ { 4102, 6, 2, 8, 1, -8 },
/* 0xAE */ { 4104, 18, 17, 19, 1, -17 },
/* 0xAF */ { 4143, 13, 21, 15, 1, -21 },
/* 0xB0 */ { 4178, 7, 8, 15, 4, -17 },
/* 0xB1 */ { 4185, 12, 15, 14, 1, -15 },
/* 0xB2 */ { 4208, 5, 5, 8, 1, 0 },
/* 0xB3 */ { 4212, 4, 18, 6, 1, -18 },
/* 0xB4 */ { 4221, 5, 4, 8, 2, -18 },
/* 0xB5 */ { 4224, 12, 17, 13, 2, -13 },
/* 0xB6 */ { 4250, 11, 21, 13, 2, -18 },
/* 0xB7 */ { 4279, 2, 2, 6, 2, -8 },
/* 0xB8 */ { 4280, 6, 5, 8, 1, 0 },
/* 0xB9 */ { 4284, 13, 18, 13, 1, -13 },
/* 0xBA */ { 4314, 10, 18, 12, 1, -13 },
/* 0xBB */ { 4337, 8, 8, 12, 2, -10 },
/* 0xBC */ { 4345, 10, 18, 14, 2, -18 },
/* 0xBD */ { 4368, 8, 4, 8, 0, -18 },
/* 0xBE */ { 4372, 7, 18, 9, 1, -18 },
/* 0xBF */ { 4388, 10, 17, 12, 1, -17 },
/* 0xC0 */ { 4410, 14, 22, 17, 2, -22 },
/* 0xC1 */ { 4449, 14, 22, 16, 1, -22 },
/* 0xC2 */ { 4488, 14, 22, 16, 1, -22 },
/* 0xC3 */ { 4527, 14, 22, 16, 1, -22 },
/* 0xC4 */ { 4566, 14, 21, 16, 1, -21 },
/* 0xC5 */ { 4603, 10, 22, 14, 2, -22 },
/* 0xC6 */ { 4631, 15, 22, 17, 1, -22 },
/* 0xC7 */ { 4673, 15, 23, 17, 1, -18 },
/* 0xC8 */ { 4717, 15, 21, 17, 1, -21 },
/* 0xC9 */ { 4757, 12, 22, 15, 2, -22 },
/* 0xCA */ { 4790, 13, 23, 15, 2, -18 },
/* 0xCB */ { 4828, 12, 21, 15, 2, -21 },
/* 0xCC */ { 4860, 12, 21, 15, 2, -21 },
/* 0xCD */ { 4892, 4, 22, 7, 1, -22 },
/* 0xCE */ { 4903, 6, 22, 7, 0, -22 },
/* 0xCF */ { 4920, 14, 21, 17, 2, -21 },
/* 0xD0 */ { 4957, 15, 18, 17, 1, -18 },
/* 0xD1 */ { 4991, 13, 22, 18, 2, -22 },
/* 0xD2 */ { 5027, 13, 21, 18, 2, -21 },
/* 0xD3 */ { 5062, 17, 22, 19, 1, -22 },
/* 0xD4 */ { 5109, 17, 22, 19, 1, -22 },
/* 0xD5 */ { 5156, 17, 22, 19, 1, -22 },
/* 0xD6 */ { 5203, 17, 21, 19, 1, -21 },
/* 0xD7 */ { 5248, 8, 9, 14, 3, -10 },
/* 0xD8 */ { 5257, 14, 21, 17, 2, -21 },
/* 0xD9 */ { 5294, 13, 24, 17, 2, -24 },
/* 0xDA */ { 5333, 13, 22, 17, 2, -22 },
/* 0xDB */ { 5369, 13, 22, 17, 2, -22 },
/* 0xDC */ { 5405, 13, 21, 17, 2, -21 },
/* 0xDD */ { 5440, 14, 22, 16, 1, -22 },
/* 0xDE */ { 5479, 12, 23, 15, 1, -18 },
/* 0xDF */ { 5514, 11, 18, 14, 2, -18 },
/* 0xE0 */ { 5539, 6, 18, 8, 1, -18 },
/* 0xE1 */ { 5553, 12, 18, 13, 1, -18 },
/* 0xE2 */ { 5580, 12, 18, 13, 1, -18 },
/* 0xE3 */ { 5607, 12, 18, 13, 1, -18 },
/* 0xE4 */ { 5634, 12, 17, 13, 1, -17 },
/* 0xE5 */ { 5660, 5, 22, 6, 0, -22 },
/* 0xE6 */ { 5674, 10, 18, 12, 1, -18 },
/* 0xE7 */ { 5697, 10, 18, 12, 1, -13 },
/* 0xE8 */ { 5720, 10, 18, 12, 1, -18 },
/* 0xE9 */ { 5743, 11, 18, 13, 1, -18 },
/* 0xEA */ { 5768, 11, 18, 13, 1, -13 },
/* 0xEB */ { 5793, 11, 17, 13, 1, -17 },
/* 0xEC */ { 5817, 11, 18, 13, 1, -18 },
/* 0xED */ { 5842, 5, 18, 5, 0, -18 },
/* 0xEE */ { 5854, 6, 18, 6, 0, -18 },
/* 0xEF */ { 5868, 16, 18, 18, 1, -18 },
/* 0xF0 */ { 5904, 12, 18, 14, 1, -18 },
/* 0xF1 */ { 5931, 10, 18, 13, 1, -18 },
/* 0xF2 */ { 5954, 10, 18, 13, 1, -18 },
/* 0xF3 */ { 5977, 11, 18, 13, 1, -18 },
/* 0xF4 */ { 6002, 11, 18, 13, 1, -18 },
/* 0xF5 */ { 6027, 11, 18, 13, 1, -18 },
/* 0xF6 */ { 6052, 11, 17, 13, 1, -17 },
/* 0xF7 */ { 6076, 12, 11, 14, 1, -11 },
/* 0xF8 */ { 6093, 6, 18, 8, 1, -18 },
/* 0xF9 */ { 6107, 10, 19, 13, 1, -19 },
/* 0xFA */ { 6131, 10, 18, 13, 1, -18 },
/* 0xFB */ { 6154, 10, 18, 13, 1, -18 },
/* 0xFC */ { 6177, 10, 17, 13, 1, -17 },
/* 0xFD */ { 6199, 11, 23, 11, 0, -18 },
/* 0xFE */ { 6231, 6, 21, 7, 1, -16 },
/* 0xFF */ { 6247, 2, 2, 8, 3, -17 },
};
const GFXfont FreeSans12pt_Win1250 PROGMEM = {
(uint8_t*)FreeSans12pt_Win1250Bitmaps,
(GFXglyph*)FreeSans12pt_Win1250Glyphs,
0x01, 0xFF, 19
};
@@ -0,0 +1,527 @@
// trunk-ignore-all(clang-format)
#pragma once
/* PROPERTIES
FONT_NAME FreeSans12pt_Win1251
*/
const uint8_t FreeSans12pt_Win1251Bitmaps[] PROGMEM = {
/* 0x01 */ 0x00, 0x30, 0x00, 0x09, 0x00, 0x01, 0x20, 0x00, 0x24, 0x00, 0x04, 0x80, 0x01, 0x90, 0x00, 0x62, 0x00, 0x30, 0xFE, 0x04, 0x10, 0x5F, 0x02, 0x0B, 0x00, 0x7F, 0xE0, 0x0C, 0x1C, 0x02, 0x83, 0x81, 0x9F, 0xF0, 0x02, 0x1E, 0x00, 0x41, 0xC0, 0x0E, 0x7F, 0x81, 0x78, 0x18, 0x62, 0x00, 0xFF, 0xC0,
/* 0x02 */ 0x00, 0xFF, 0x80, 0x61, 0x13, 0xF0, 0x62, 0x60, 0x07, 0xFC, 0x00, 0x83, 0x80, 0x10, 0xF0, 0x33, 0xF6, 0x01, 0x41, 0xC0, 0x18, 0x38, 0x03, 0xFF, 0xE0, 0x47, 0x02, 0x08, 0x20, 0x61, 0xC4, 0x06, 0x17, 0x00, 0x22, 0x00, 0x02, 0x40, 0x00, 0x48, 0x00, 0x09, 0x00, 0x01, 0x20, 0x00, 0x3C, 0x00,
/* 0x03 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x04, 0x08, 0x48, 0x70, 0xE1, 0xC3, 0x87, 0x0E, 0x08, 0x10, 0x70, 0x00, 0x03, 0x80, 0x00, 0x14, 0x00, 0x00, 0xA1, 0x81, 0x8D, 0x87, 0xF0, 0x44, 0x00, 0x06, 0x30, 0x00, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x04 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x10, 0x02, 0x48, 0xE0, 0x61, 0xC1, 0xCC, 0x0E, 0x78, 0x1C, 0x70, 0x00, 0x03, 0x80, 0x00, 0x14, 0xFF, 0xFC, 0xA6, 0x00, 0xCD, 0x9F, 0xFE, 0x44, 0x71, 0xE6, 0x30, 0xFC, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x05 */ 0x00, 0x18, 0x00, 0x00, 0x40, 0x01, 0x90, 0x01, 0xF4, 0x08, 0x12, 0x23, 0xC1, 0x91, 0x2C, 0x1C, 0x8A, 0xC3, 0x64, 0x64, 0x13, 0x22, 0x41, 0x98, 0x26, 0x2C, 0xC4, 0x22, 0x60, 0x42, 0x13, 0x04, 0x30, 0x80, 0x61, 0xA4, 0x02, 0x18, 0x20, 0x03, 0x41, 0x00, 0x20, 0x08, 0x02, 0x00, 0x60, 0x40, 0x03, 0xF8,
/* 0x06 */ 0x00, 0x10, 0x00, 0x03, 0x00, 0x1C, 0x48, 0x00, 0xB4, 0x80, 0x09, 0xF9, 0xC0, 0xE0, 0xE4, 0x0C, 0x02, 0x8F, 0x80, 0x38, 0x88, 0x01, 0x0D, 0x00, 0x18, 0x30, 0x01, 0x60, 0x80, 0x13, 0x18, 0x03, 0xF2, 0xC0, 0x20, 0x26, 0x06, 0x07, 0xFF, 0xA0, 0x02, 0x39, 0x00, 0x14, 0x70, 0x01, 0xC3, 0x00, 0x18, 0x00,
/* 0x07 */
/* 0x08 */ 0x00, 0x1F, 0x80, 0x00, 0x60, 0x80, 0x01, 0x00, 0x80, 0x06, 0x00, 0x80, 0x3C, 0x01, 0x01, 0x8C, 0x02, 0x02, 0x08, 0x04, 0x04, 0x08, 0x0C, 0x38, 0x00, 0x04, 0x80, 0x00, 0x06, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x2E, 0xC0, 0x01, 0x83, 0x7E, 0x0C, 0x10, 0x37, 0xE2, 0x61, 0x00, 0x0C, 0xC6, 0x10, 0x98, 0x0C, 0x63, 0x00, 0x00, 0xC6, 0x00,
/* 0x09 */ 0x00, 0x1F, 0x80, 0x00, 0x60, 0x80, 0x01, 0x00, 0x80, 0x06, 0x00, 0x80, 0x3C, 0x01, 0x01, 0x8C, 0x02, 0x02, 0x08, 0x04, 0x04, 0x08, 0x0C, 0x38, 0x00, 0x04, 0x80, 0x00, 0x06, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x2E, 0xC0, 0x01, 0x83, 0x7E, 0x0C, 0x00, 0x37, 0xE0,
/* 0x0A */
/* 0x0B */ 0x1F, 0x07, 0xC1, 0x86, 0x41, 0x10, 0x0C, 0x04, 0x80, 0x40, 0x18, 0x00, 0x00, 0xC0, 0x00, 0x06, 0x00, 0x00, 0x30, 0x00, 0x01, 0x40, 0x00, 0x0A, 0x00, 0x00, 0x88, 0x00, 0x04, 0x40, 0x00, 0x41, 0x00, 0x02, 0x04, 0x00, 0x20, 0x20, 0x02, 0x00, 0x80, 0x20, 0x02, 0x02, 0x00, 0x08, 0x20, 0x00, 0x22, 0x00, 0x00, 0xE0, 0x00,
/* 0x0C */ 0x01, 0x00, 0x00, 0x38, 0x00, 0x04, 0xC0, 0x01, 0x08, 0x00, 0x18, 0x80, 0x1C, 0x10, 0x02, 0x07, 0x80, 0x81, 0x10, 0x1F, 0xC2, 0x02, 0x00, 0x60, 0x80, 0x1A, 0x20, 0x1C, 0x42, 0x1C, 0x08, 0xFE, 0x03, 0xA0, 0x01, 0x8C, 0x01, 0xC1, 0x43, 0xD0, 0x27, 0x81, 0xF8,
/* 0x0D */
/* 0x0E */ 0x00, 0xE0, 0x00, 0x11, 0x00, 0x01, 0x10, 0x00, 0x0B, 0x00, 0x03, 0xF8, 0x00, 0x60, 0x60, 0x09, 0x02, 0x00, 0xA0, 0x10, 0x16, 0x01, 0x01, 0x40, 0x10, 0x10, 0x01, 0x01, 0x00, 0x08, 0x10, 0x00, 0x82, 0x1F, 0x08, 0x3F, 0x90, 0x44, 0x00, 0x06, 0xBF, 0xFF, 0xAF, 0xF0, 0xFF, 0xFF, 0x0F, 0xE3, 0xFB, 0xFC,
/* 0x0F */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x40, 0x12, 0x34, 0x00, 0x69, 0x40, 0x01, 0x49, 0xE0, 0xF1, 0xCD, 0x06, 0x8E, 0x28, 0x14, 0x71, 0x40, 0xA3, 0x8B, 0xFD, 0x14, 0x50, 0x68, 0xA2, 0x81, 0x4D, 0x97, 0xFA, 0x44, 0xBF, 0xD6, 0x31, 0x02, 0xE0, 0xC8, 0x16, 0x08, 0x61, 0x08, 0x21, 0xF0, 0x80, 0xF8, 0x78, 0x00,
/* 0x10 */ 0x00, 0xF0, 0x00, 0x3A, 0x00, 0x07, 0xC0, 0x00, 0xA8, 0x00, 0x1F, 0x00, 0x02, 0xB0, 0x00, 0x52, 0x00, 0x0A, 0x40, 0x02, 0x48, 0x00, 0x49, 0x00, 0x09, 0x30, 0x01, 0x22, 0x01, 0xC4, 0x70, 0xF0, 0x85, 0xE1, 0x10, 0x88, 0x37, 0x20, 0x03, 0x9C, 0x00, 0x37, 0x00, 0x06, 0x40, 0x01, 0x86, 0x00,
/* 0x11 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x60, 0x02, 0x36, 0x00, 0x09, 0x04, 0x0C, 0x48, 0x60, 0xC1, 0xC3, 0x0F, 0x0E, 0x00, 0x08, 0x70, 0x00, 0x23, 0x80, 0x63, 0x84, 0x01, 0x9F, 0x20, 0x0C, 0xFD, 0x80, 0x27, 0xE4, 0x03, 0x3F, 0x30, 0x33, 0xE0, 0xC0, 0x00, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x12 */ 0x00, 0xC2, 0x00, 0x1C, 0x24, 0x02, 0x18, 0x60, 0x64, 0x02, 0x02, 0x40, 0x20, 0x00, 0xF2, 0x03, 0x89, 0xE0, 0x7C, 0x80, 0x0E, 0x25, 0x80, 0xE1, 0x00, 0x1A, 0x08, 0x71, 0xB0, 0xC4, 0x39, 0x84, 0xC2, 0xCC, 0x40, 0x76, 0x7C, 0x05, 0xBB, 0x80, 0x4C, 0xE0, 0x0A, 0x78, 0x00, 0x9C, 0x00, 0x0F, 0x00, 0x00,
/* 0x13 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x00, 0x00, 0x48, 0x60, 0xC1, 0xC6, 0xC9, 0x0E, 0x00, 0x00, 0x70, 0x00, 0x03, 0x80, 0x00, 0x14, 0xFF, 0xF8, 0xA6, 0x00, 0xCD, 0x9F, 0xFE, 0x44, 0x71, 0xE6, 0x30, 0xFC, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x14 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x20, 0x22, 0x33, 0x01, 0x89, 0x20, 0x02, 0x48, 0x60, 0xE1, 0xC8, 0x80, 0x8E, 0x46, 0x46, 0x72, 0x32, 0x33, 0x9F, 0x9F, 0x94, 0x78, 0x78, 0xA0, 0x00, 0x0D, 0x80, 0x00, 0x44, 0x0E, 0x06, 0x30, 0x00, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x15 */ 0x03, 0xFC, 0x20, 0x38, 0x1C, 0x81, 0x80, 0x1D, 0x08, 0x00, 0x32, 0x60, 0x00, 0x89, 0x00, 0x02, 0x18, 0x00, 0x08, 0x61, 0xC3, 0x22, 0x8D, 0x93, 0x72, 0x00, 0x00, 0x48, 0x00, 0x01, 0x20, 0x00, 0x04, 0x9F, 0xFF, 0x92, 0x60, 0x0E, 0x44, 0xFF, 0xF2, 0x11, 0xC3, 0x88, 0x21, 0xF8, 0x40, 0x40, 0x02, 0x00, 0xC0, 0x30, 0x00, 0xFF, 0x00,
/* 0x16 */ 0x03, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x01, 0xE0, 0x03, 0xF0, 0x03, 0xF0, 0x27, 0xF0, 0x6F, 0x70, 0x6E, 0x60, 0xFC, 0x60, 0xFC, 0x7E, 0xFC, 0x7E, 0xFC, 0x3F, 0xF4, 0x1F, 0xF4, 0x1F, 0xF0, 0x0E, 0x70, 0x0E, 0x30, 0x1C, 0x38, 0x38, 0x0F, 0xF0,
/* 0x17 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x00, 0x00, 0x48, 0x00, 0x21, 0xC0, 0x02, 0x8E, 0x20, 0xF4, 0x70, 0x84, 0x11, 0x82, 0x40, 0x84, 0x01, 0x03, 0x20, 0x0F, 0x85, 0x80, 0x03, 0x04, 0x00, 0x04, 0x30, 0x78, 0x10, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x18 */ 0x00, 0xFC, 0x00, 0x02, 0x06, 0x00, 0x08, 0x24, 0x00, 0x21, 0xA4, 0x00, 0x4C, 0x48, 0x00, 0xA0, 0x50, 0x01, 0x92, 0x60, 0x03, 0x24, 0xC0, 0x06, 0x01, 0x81, 0x28, 0x03, 0x49, 0x6C, 0xC4, 0xAD, 0xD8, 0x16, 0xA4, 0xCC, 0xC4, 0x44, 0x86, 0x13, 0x05, 0x00, 0x28, 0x0A, 0x00, 0x50, 0x14, 0x00, 0x90, 0x48, 0x01, 0x20, 0x90, 0x02, 0x41, 0x20, 0x00, 0x00,
/* 0x19 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x00, 0x00, 0x49, 0xC3, 0x81, 0xC0, 0x00, 0x0E, 0x78, 0xF0, 0x77, 0xEF, 0xC3, 0xA7, 0x4E, 0x15, 0x0A, 0x10, 0xA7, 0x8F, 0x0D, 0x80, 0x00, 0x44, 0x00, 0x06, 0x33, 0xF0, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x1A */ 0xFF, 0xFF, 0x00, 0x06, 0x00, 0x0C, 0x3E, 0x18, 0x82, 0x32, 0x02, 0x64, 0x04, 0xC8, 0x09, 0x80, 0x23, 0x00, 0x86, 0x02, 0x0C, 0x08, 0x18, 0x10, 0x30, 0x00, 0x60, 0x00, 0xC0, 0x81, 0x80, 0x03, 0x00, 0x07, 0xFF, 0xF8,
/* 0x1B */ 0x00, 0xFE, 0x00, 0x03, 0x81, 0x80, 0x04, 0x00, 0x60, 0x08, 0x00, 0x30, 0x10, 0x00, 0x10, 0x30, 0x07, 0x88, 0x23, 0xC8, 0x08, 0x22, 0x00, 0x04, 0x60, 0x00, 0x44, 0x60, 0x00, 0x84, 0x63, 0x03, 0x04, 0x61, 0xFC, 0x04, 0x6B, 0x00, 0x9E, 0xA5, 0x01, 0x6A, 0xD5, 0x01, 0x43, 0xA8, 0x81, 0x05, 0xD0, 0x82, 0x0A, 0xA0, 0x82, 0x05, 0xC0, 0x82, 0x02, 0x61, 0xFF, 0x0C, 0x1E, 0x00, 0xF0,
/* 0x1C */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x30, 0x02, 0x32, 0x00, 0x09, 0x00, 0x00, 0x48, 0x20, 0x61, 0xC3, 0x84, 0x0E, 0x1C, 0x78, 0x70, 0x40, 0x03, 0x80, 0x00, 0x14, 0x00, 0x00, 0xA0, 0x03, 0x0D, 0x83, 0xF0, 0x44, 0x00, 0x06, 0x30, 0x00, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x1D */ 0x01, 0xFE, 0x00, 0x3A, 0x1C, 0x03, 0x00, 0x30, 0x23, 0x1E, 0xC3, 0x38, 0x03, 0x10, 0xC3, 0x09, 0x00, 0x18, 0x68, 0x00, 0xC1, 0x40, 0x00, 0x0A, 0x07, 0x80, 0x50, 0x46, 0x02, 0x80, 0x00, 0x1A, 0x1E, 0x00, 0xCB, 0x10, 0x0D, 0x03, 0x00, 0x48, 0x60, 0x06, 0x40, 0x00, 0x22, 0x0C, 0x02, 0x10, 0x60, 0x60, 0x43, 0xFC, 0x01, 0xE0, 0x00, 0x00,
/* 0x1E */ 0x01, 0xF0, 0x00, 0xEA, 0xC0, 0x31, 0x5F, 0x04, 0x5F, 0x88, 0x80, 0xA0, 0x48, 0x0E, 0x02, 0x8F, 0x40, 0x3C, 0x10, 0x21, 0x66, 0x87, 0x15, 0x98, 0x71, 0x41, 0x02, 0x14, 0x00, 0x01, 0x40, 0x00, 0x14, 0x00, 0x01, 0x21, 0xFE, 0x12, 0x00, 0x02, 0x10, 0x00, 0x60, 0x80, 0x0C, 0x06, 0x01, 0x80, 0x3F, 0xE0,
/* 0x1F */ 0x0E, 0x00, 0x13, 0x00, 0x23, 0x00, 0xF3, 0x01, 0x31, 0x01, 0x11, 0x03, 0xD3, 0x06, 0xF2, 0x30, 0x34, 0xC7, 0x25, 0x33, 0x2B, 0xC2, 0x57, 0x04, 0x3A, 0x08, 0x72, 0x30, 0xA3, 0xC3, 0x40, 0x04, 0x40, 0x18, 0x40, 0x60, 0x7F, 0x80,
/* ' ' 0x20 */
/* '!' 0x21 */ 0xFF, 0xFF, 0xFF, 0xF0, 0xF0,
/* '"' 0x22 */ 0xCF, 0x3C, 0xF3, 0x8A, 0x20,
/* '#' 0x23 */ 0x06, 0x30, 0x31, 0x03, 0x18, 0x18, 0xC7, 0xFF, 0xBF, 0xFC, 0x31, 0x01, 0x18, 0x18, 0xC7, 0xFF, 0xBF, 0xFC, 0x31, 0x01, 0x18, 0x18, 0xC0, 0xC6, 0x06, 0x30,
/* '$' 0x24 */ 0x04, 0x03, 0xE1, 0xFF, 0x72, 0x7C, 0x47, 0x88, 0xF1, 0x07, 0xA0, 0x7E, 0x03, 0xF0, 0x17, 0x02, 0x7C, 0x47, 0x88, 0xF1, 0x1B, 0x26, 0x7F, 0xC3, 0xE0, 0x10, 0x02, 0x00,
/* '%' 0x25 */ 0x00, 0x06, 0x03, 0xC0, 0x40, 0x7E, 0x0C, 0x0E, 0x70, 0x80, 0xC3, 0x18, 0x0C, 0x31, 0x00, 0xE7, 0x30, 0x07, 0xE6, 0x00, 0x3C, 0x40, 0x00, 0x0C, 0x7C, 0x00, 0x8F, 0xE0, 0x19, 0xC7, 0x01, 0x18, 0x30, 0x31, 0x83, 0x02, 0x1C, 0x70, 0x40, 0xFE, 0x04, 0x07, 0xC0,
/* '&' 0x26 */ 0x0F, 0x00, 0x7E, 0x03, 0x9C, 0x0C, 0x30, 0x30, 0xC0, 0xE7, 0x01, 0xF8, 0x03, 0x80, 0x3E, 0x01, 0xCC, 0x6E, 0x39, 0xB0, 0x7C, 0xC0, 0xF3, 0x03, 0xCE, 0x1F, 0x9F, 0xE6, 0x3E, 0x1C,
/* ''' 0x27 */ 0xFF, 0xA0,
/* '(' 0x28 */ 0x08, 0x8C, 0x46, 0x31, 0x98, 0xC6, 0x31, 0x8C, 0x63, 0x08, 0x63, 0x08, 0x61, 0x0C, 0x20,
/* ')' 0x29 */ 0x82, 0x18, 0xC3, 0x18, 0xC3, 0x18, 0xC6, 0x31, 0x8C, 0x62, 0x31, 0x88, 0xC4, 0x62, 0x00,
/* '*' 0x2A */ 0x10, 0x23, 0x5B, 0xE3, 0x8D, 0x91, 0x00,
/* '+' 0x2B */ 0x0C, 0x03, 0x00, 0xC0, 0x30, 0xFF, 0xFF, 0xF0, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0,
/* ',' 0x2C */ 0xF5, 0x60,
/* '-' 0x2D */ 0xFF, 0xF0,
/* '.' 0x2E */ 0xF0,
/* '/' 0x2F */ 0x02, 0x0C, 0x10, 0x20, 0xC1, 0x02, 0x0C, 0x10, 0x20, 0xC1, 0x02, 0x0C, 0x10, 0x20, 0xC1, 0x00,
/* '0' 0x30 */ 0x1F, 0x07, 0xF1, 0xC7, 0x30, 0x6C, 0x0F, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3E, 0x0E, 0xC1, 0x9C, 0x71, 0xFC, 0x1F, 0x00,
/* '1' 0x31 */ 0x08, 0xCF, 0xFF, 0x8C, 0x63, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x18,
/* '2' 0x32 */ 0x1F, 0x0F, 0xF9, 0x87, 0x60, 0x7C, 0x06, 0x00, 0xC0, 0x18, 0x07, 0x01, 0xC0, 0xF0, 0x78, 0x1C, 0x06, 0x00, 0xC0, 0x30, 0x07, 0xFF, 0xFF, 0xE0,
/* '3' 0x33 */ 0x3F, 0x0F, 0xF3, 0x87, 0x60, 0x6C, 0x0C, 0x01, 0x80, 0x60, 0x78, 0x0F, 0x80, 0x18, 0x01, 0x80, 0x3C, 0x07, 0x80, 0xD8, 0x73, 0xFC, 0x3F, 0x00,
/* '4' 0x34 */ 0x01, 0x80, 0x70, 0x0E, 0x03, 0xC0, 0xD8, 0x1B, 0x06, 0x61, 0x8C, 0x21, 0x8C, 0x33, 0x06, 0x7F, 0xFF, 0xFE, 0x03, 0x00, 0x60, 0x0C, 0x01, 0x80,
/* '5' 0x35 */ 0x3F, 0xCF, 0xF9, 0x80, 0x30, 0x06, 0x00, 0xDE, 0x1F, 0xE7, 0x0E, 0x00, 0xE0, 0x0C, 0x01, 0x80, 0x30, 0x07, 0x81, 0xB8, 0x73, 0xFC, 0x1F, 0x00,
/* '6' 0x36 */ 0x0F, 0x07, 0xF9, 0xC3, 0x30, 0x74, 0x01, 0x80, 0x33, 0xC7, 0xFE, 0xF1, 0xDC, 0x1F, 0x01, 0xE0, 0x3C, 0x06, 0xC1, 0xDC, 0x71, 0xFC, 0x1F, 0x00,
/* '7' 0x37 */ 0xFF, 0xFF, 0xFC, 0x01, 0x00, 0x60, 0x18, 0x02, 0x00, 0xC0, 0x30, 0x06, 0x01, 0x80, 0x30, 0x04, 0x01, 0x80, 0x30, 0x06, 0x01, 0x80, 0x30, 0x00,
/* '8' 0x38 */ 0x1F, 0x07, 0xF1, 0xC7, 0x30, 0x66, 0x0C, 0xC1, 0x8C, 0x61, 0xF8, 0x3F, 0x8E, 0x3B, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xD8, 0x31, 0xFC, 0x1F, 0x00,
/* '9' 0x39 */ 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x6C, 0x07, 0x80, 0xF0, 0x1E, 0x07, 0x61, 0xEF, 0xFC, 0x79, 0x80, 0x30, 0x05, 0xC1, 0x98, 0x73, 0xFC, 0x1E, 0x00,
/* ':' 0x3A */ 0xF0, 0x00, 0x03, 0xC0,
/* ';' 0x3B */ 0xF0, 0x00, 0x0F, 0x56,
/* '<' 0x3C */ 0x00, 0x70, 0x1E, 0x0F, 0x83, 0xC0, 0xF0, 0x0E, 0x00, 0x7C, 0x00, 0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x10,
/* '=' 0x3D */ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF,
/* '>' 0x3E */ 0xE0, 0x07, 0x80, 0x1F, 0x00, 0x7C, 0x00, 0xF0, 0x07, 0x01, 0xE0, 0xF0, 0x3C, 0x0F, 0x00, 0x80, 0x00,
/* '?' 0x3F */ 0x3F, 0x1F, 0xEE, 0x1F, 0x03, 0xC0, 0xC0, 0x30, 0x0C, 0x06, 0x03, 0x81, 0xC0, 0xE0, 0x30, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x0C, 0x03, 0x00,
/* '@' 0x40 */ 0x00, 0xFE, 0x00, 0x0F, 0xFE, 0x00, 0xF0, 0x3E, 0x07, 0x00, 0x3C, 0x38, 0x00, 0x38, 0xC1, 0xE0, 0x66, 0x0F, 0xD9, 0xD8, 0x61, 0xC3, 0xC3, 0x07, 0x0F, 0x1C, 0x1C, 0x3C, 0x60, 0x60, 0xF1, 0x81, 0x83, 0xC6, 0x06, 0x1B, 0x18, 0x38, 0xEE, 0x71, 0xE7, 0x18, 0xFD, 0xF8, 0x71, 0xE7, 0xC0, 0xE0, 0x00, 0x01, 0xE0, 0x00, 0x01, 0xFF, 0xC0, 0x01, 0xFC, 0x00,
/* 'A' 0x41 */ 0x07, 0x80, 0x1E, 0x00, 0x78, 0x03, 0xF0, 0x0C, 0xC0, 0x33, 0x01, 0xCE, 0x06, 0x18, 0x18, 0x60, 0xE1, 0xC3, 0x03, 0x0F, 0xFC, 0x7F, 0xF9, 0x80, 0x66, 0x01, 0xB8, 0x07, 0xC0, 0x0F, 0x00, 0x30,
/* 'B' 0x42 */ 0xFF, 0xC7, 0xFF, 0x30, 0x1D, 0x80, 0x6C, 0x03, 0x60, 0x1B, 0x00, 0xD8, 0x0C, 0xFF, 0xC7, 0xFF, 0x30, 0x0D, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x06, 0xFF, 0xF7, 0xFE, 0x00,
/* 'C' 0x43 */ 0x07, 0xE0, 0x3F, 0xF0, 0xE0, 0x73, 0x80, 0x76, 0x00, 0x6C, 0x00, 0x30, 0x00, 0x60, 0x00, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x06, 0x00, 0x0E, 0x00, 0x6C, 0x00, 0xDC, 0x03, 0x1E, 0x0E, 0x1F, 0xF8, 0x0F, 0xC0,
/* 'D' 0x44 */ 0xFF, 0xC3, 0xFF, 0x8C, 0x07, 0x30, 0x0E, 0xC0, 0x1B, 0x00, 0x7C, 0x00, 0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x3C, 0x00, 0xF0, 0x03, 0xC0, 0x1F, 0x00, 0x6C, 0x03, 0xB0, 0x1C, 0xFF, 0xE3, 0xFE, 0x00,
/* 'E' 0x45 */ 0xFF, 0xFF, 0xFF, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xEF, 0xFE, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xFF, 0xFF,
/* 'F' 0x46 */ 0xFF, 0xFF, 0xFF, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xFF, 0xDF, 0xFB, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x18, 0x00,
/* 'G' 0x47 */ 0x07, 0xF0, 0x1F, 0xFC, 0x3C, 0x1E, 0x70, 0x07, 0x60, 0x03, 0xE0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x7F, 0xC0, 0x7F, 0xC0, 0x03, 0xC0, 0x03, 0x60, 0x03, 0x60, 0x07, 0x30, 0x0F, 0x3C, 0x1F, 0x1F, 0xFB, 0x07, 0xE1,
/* 'H' 0x48 */ 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xFF, 0xFF, 0xFF, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xC0,
/* 'I' 0x49 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xF0,
/* 'J' 0x4A */ 0x01, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x80, 0xC0, 0x60, 0x3C, 0x1E, 0x0F, 0x07, 0xC7, 0x7F, 0x1F, 0x00,
/* 'K' 0x4B */ 0xC0, 0x3E, 0x03, 0xB0, 0x39, 0x83, 0x8C, 0x38, 0x63, 0x83, 0x38, 0x19, 0xC0, 0xDE, 0x07, 0xB8, 0x38, 0xE1, 0x83, 0x0C, 0x1C, 0x60, 0x73, 0x01, 0x98, 0x0E, 0xC0, 0x3E, 0x00, 0xC0,
/* 'L' 0x4C */ 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xFF, 0xFF, 0xF0,
/* 'M' 0x4D */ 0xE0, 0x07, 0xE0, 0x07, 0xF0, 0x0F, 0xF0, 0x0F, 0xD0, 0x0F, 0xD8, 0x1B, 0xD8, 0x1B, 0xD8, 0x1B, 0xCC, 0x33, 0xCC, 0x33, 0xCC, 0x33, 0xC6, 0x63, 0xC6, 0x63, 0xC6, 0x63, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC1, 0x83,
/* 'N' 0x4E */ 0xE0, 0x1F, 0x00, 0xFC, 0x07, 0xE0, 0x3D, 0x81, 0xEE, 0x0F, 0x30, 0x79, 0xC3, 0xC6, 0x1E, 0x18, 0xF0, 0xE7, 0x83, 0x3C, 0x1D, 0xE0, 0x6F, 0x01, 0xF8, 0x0F, 0xC0, 0x3E, 0x01, 0xC0,
/* 'O' 0x4F */ 0x07, 0xF0, 0x0F, 0xFE, 0x0F, 0x07, 0x86, 0x00, 0xC6, 0x00, 0x33, 0x00, 0x1B, 0x00, 0x07, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x36, 0x00, 0x33, 0x00, 0x18, 0xC0, 0x18, 0x78, 0x3C, 0x1F, 0xFC, 0x03, 0xF8, 0x00,
/* 'P' 0x50 */ 0xFF, 0x8F, 0xFE, 0xC0, 0x6C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x06, 0xFF, 0xEF, 0xFC, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00,
/* 'Q' 0x51 */ 0x07, 0xF0, 0x0F, 0xFE, 0x0F, 0x07, 0x86, 0x00, 0xC6, 0x00, 0x33, 0x00, 0x1B, 0x00, 0x07, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x36, 0x00, 0x33, 0x01, 0x98, 0xC0, 0xFC, 0x78, 0x3C, 0x1F, 0xFF, 0x03, 0xF9, 0x80, 0x00, 0x40,
/* 'R' 0x52 */ 0xFF, 0xE3, 0xFF, 0xCC, 0x03, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x6C, 0x01, 0xB0, 0x0C, 0xFF, 0xE3, 0xFF, 0xCC, 0x03, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x6C, 0x01, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x70,
/* 'S' 0x53 */ 0x0F, 0xE0, 0x7F, 0xC3, 0x83, 0x98, 0x07, 0x60, 0x0D, 0x80, 0x07, 0x00, 0x1E, 0x00, 0x3F, 0x80, 0x3F, 0xC0, 0x0F, 0x80, 0x07, 0xC0, 0x0F, 0x00, 0x3E, 0x00, 0xDE, 0x0E, 0x3F, 0xF0, 0x3F, 0x80,
/* 'T' 0x54 */ 0xFF, 0xFF, 0xFF, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60,
/* 'U' 0x55 */ 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x80, 0xEE, 0x0E, 0x3F, 0xE0, 0xFC, 0x00,
/* 'V' 0x56 */ 0xC0, 0x0F, 0x00, 0x7E, 0x01, 0x98, 0x06, 0x60, 0x39, 0xC0, 0xC3, 0x03, 0x0C, 0x1C, 0x38, 0x60, 0x61, 0x81, 0x8E, 0x07, 0x30, 0x0C, 0xC0, 0x37, 0x00, 0xF8, 0x01, 0xE0, 0x07, 0x80, 0x1C, 0x00,
/* 'W' 0x57 */ 0xE0, 0x30, 0x1D, 0x80, 0xE0, 0x76, 0x07, 0x81, 0xDC, 0x1E, 0x06, 0x70, 0x7C, 0x18, 0xC1, 0xB0, 0xE3, 0x0C, 0xC3, 0x8C, 0x33, 0x0C, 0x38, 0xC6, 0x30, 0x67, 0x18, 0xC1, 0x98, 0x67, 0x06, 0x61, 0xD8, 0x1D, 0x83, 0x60, 0x3C, 0x0D, 0x80, 0xF0, 0x3E, 0x03, 0xC0, 0x70, 0x0F, 0x01, 0xC0, 0x18, 0x07, 0x00,
/* 'X' 0x58 */ 0xE0, 0x1D, 0x80, 0xE7, 0x03, 0x0E, 0x1C, 0x18, 0x60, 0x73, 0x00, 0xFC, 0x01, 0xE0, 0x07, 0x00, 0x1E, 0x00, 0xF8, 0x03, 0x30, 0x1C, 0xE0, 0xE1, 0x83, 0x07, 0x1C, 0x0E, 0xE0, 0x1B, 0x00, 0x70,
/* 'Y' 0x59 */ 0xC0, 0x0F, 0x80, 0x76, 0x01, 0x9C, 0x0C, 0x38, 0x70, 0x61, 0x81, 0xCE, 0x03, 0x30, 0x0F, 0x80, 0x1E, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00,
/* 'Z' 0x5A */ 0xFF, 0xFF, 0xFF, 0xC0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0x60, 0x07, 0x00, 0x70, 0x07, 0x00, 0x30, 0x03, 0x80, 0x38, 0x03, 0x80, 0x18, 0x01, 0xC0, 0x1C, 0x00, 0xFF, 0xFF, 0xFF, 0xC0,
/* '[' 0x5B */ 0xFF, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCF, 0xF0,
/* '\' 0x5C */ 0x81, 0x81, 0x02, 0x06, 0x04, 0x08, 0x18, 0x10, 0x20, 0x60, 0x40, 0x81, 0x81, 0x02, 0x06, 0x04,
/* ']' 0x5D */ 0xFF, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0xF0,
/* '^' 0x5E */ 0x0C, 0x0E, 0x05, 0x86, 0xC3, 0x21, 0x19, 0x8C, 0x83, 0xC1, 0x80,
/* '_' 0x5F */ 0xFF, 0xFE,
/* '`' 0x60 */ 0xE3, 0x8C, 0x30,
/* 'a' 0x61 */ 0x3F, 0x07, 0xF8, 0xE1, 0xCC, 0x0C, 0x00, 0xC0, 0x1C, 0x3F, 0xCF, 0x8C, 0xC0, 0xCC, 0x0C, 0xE3, 0xC7, 0xEF, 0x3C, 0x70,
/* 'b' 0x62 */ 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0xF8, 0xDF, 0xCF, 0x0E, 0xE0, 0x7C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xE0, 0x6F, 0x0E, 0xDF, 0xCC, 0xF8,
/* 'c' 0x63 */ 0x1F, 0x0F, 0xE6, 0x1F, 0x83, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x38, 0x37, 0x1C, 0xFE, 0x1F, 0x00,
/* 'd' 0x64 */ 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x3C, 0xCF, 0xFB, 0x8F, 0xE0, 0xF8, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF8, 0x3B, 0x8F, 0x3F, 0x63, 0xCC,
/* 'e' 0x65 */ 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x3C, 0x07, 0xFF, 0xFF, 0xFE, 0x00, 0xC0, 0x1C, 0x0D, 0xC3, 0x1F, 0xC1, 0xF0,
/* 'f' 0x66 */ 0x3B, 0xD8, 0xC6, 0x7F, 0xEC, 0x63, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x00,
/* 'g' 0x67 */ 0x1E, 0x67, 0xFD, 0xC7, 0xF0, 0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x7C, 0x1D, 0xC7, 0x9F, 0xB1, 0xE6, 0x00, 0xC0, 0x3E, 0x0E, 0x7F, 0xC7, 0xE0,
/* 'h' 0x68 */ 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x33, 0xCD, 0xFB, 0xC7, 0xE0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x30,
/* 'i' 0x69 */ 0xF0, 0x3F, 0xFF, 0xFF, 0xF0,
/* 'j' 0x6A */ 0x33, 0x00, 0x03, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0xE0,
/* 'k' 0x6B */ 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x6C, 0x33, 0x18, 0xCC, 0x37, 0x0F, 0xC3, 0xB8, 0xC6, 0x31, 0xCC, 0x3B, 0x06, 0xC1, 0xF0, 0x30,
/* 'l' 0x6C */ 0xFF, 0xFF, 0xFF, 0xFF, 0xF0,
/* 'm' 0x6D */ 0xCF, 0x1F, 0x6F, 0xDF, 0xFC, 0x78, 0xFC, 0x18, 0x3C, 0x0C, 0x1E, 0x06, 0x0F, 0x03, 0x07, 0x81, 0x83, 0xC0, 0xC1, 0xE0, 0x60, 0xF0, 0x30, 0x78, 0x18, 0x3C, 0x0C, 0x18,
/* 'n' 0x6E */ 0xCF, 0x37, 0xEF, 0x1F, 0x83, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xC0,
/* 'o' 0x6F */ 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x7C, 0x1D, 0xC7, 0x1F, 0xC1, 0xF0,
/* 'p' 0x70 */ 0xCF, 0x8D, 0xFC, 0xF0, 0xEE, 0x06, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3E, 0x06, 0xF0, 0xEF, 0xFC, 0xCF, 0x8C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x00,
/* 'q' 0x71 */ 0x1E, 0x67, 0xFD, 0xC7, 0xF0, 0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x7C, 0x1D, 0xC7, 0x9F, 0xF1, 0xE6, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x60,
/* 'r' 0x72 */ 0xCF, 0x7F, 0x38, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC0,
/* 's' 0x73 */ 0x3E, 0x1F, 0xEE, 0x1B, 0x00, 0xC0, 0x3C, 0x07, 0xF0, 0x3F, 0x01, 0xF0, 0x3E, 0x1D, 0xFE, 0x3F, 0x00,
/* 't' 0x74 */ 0x63, 0x19, 0xFF, 0xB1, 0x8C, 0x63, 0x18, 0xC6, 0x31, 0xE7,
/* 'u' 0x75 */ 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x7E, 0x3D, 0xFB, 0x3C, 0xC0,
/* 'v' 0x76 */ 0xE0, 0x6C, 0x0D, 0x81, 0xB8, 0x63, 0x0C, 0x61, 0x8E, 0x60, 0xCC, 0x19, 0x83, 0xE0, 0x3C, 0x07, 0x00, 0xE0,
/* 'w' 0x77 */ 0xC1, 0xC1, 0xB0, 0xE1, 0xD8, 0x70, 0xCC, 0x2C, 0x66, 0x36, 0x31, 0x9B, 0x18, 0xCD, 0x98, 0x64, 0x6C, 0x16, 0x36, 0x0F, 0x1A, 0x07, 0x8F, 0x03, 0x83, 0x80, 0xC1, 0xC0,
/* 'x' 0x78 */ 0xC1, 0xF8, 0x66, 0x30, 0xCC, 0x3E, 0x07, 0x00, 0xC0, 0x78, 0x36, 0x0C, 0xC6, 0x3B, 0x06, 0xC0, 0xC0,
/* 'y' 0x79 */ 0xE0, 0x6C, 0x0D, 0x83, 0x38, 0x63, 0x0C, 0x63, 0x0C, 0x60, 0xCC, 0x1B, 0x03, 0x60, 0x3C, 0x07, 0x00, 0xE0, 0x18, 0x03, 0x00, 0xE0, 0x78, 0x0E, 0x00,
/* 'z' 0x7A */ 0xFF, 0xFF, 0xF0, 0x18, 0x0C, 0x07, 0x03, 0x81, 0xC0, 0x60, 0x30, 0x18, 0x0E, 0x03, 0xFF, 0xFF, 0xC0,
/* '{' 0x7B */ 0x19, 0xCC, 0x63, 0x18, 0xC6, 0x31, 0x99, 0x86, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x1C, 0x60,
/* '|' 0x7C */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC,
/* '}' 0x7D */ 0xC7, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x0C, 0x33, 0x31, 0x8C, 0x63, 0x18, 0xC6, 0x73, 0x00,
/* '~' 0x7E */ 0x70, 0x3E, 0x09, 0xE4, 0x1F, 0x03, 0x80,
/* 0x7F */
/* 0x80 */ 0xFF, 0xE0, 0xFF, 0xE0, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x07, 0xFC, 0x07, 0xFE, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x1E, 0x00, 0x1C,
/* 0x81 */ 0x07, 0x01, 0xC0, 0x20, 0x00, 0x0F, 0xFF, 0xFF, 0xF0, 0x06, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x00,
/* 0x82 */ 0xF5, 0x80,
/* 0x83 */ 0x0C, 0x38, 0x61, 0x80, 0x1F, 0xFF, 0xE0, 0xC1, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x80,
/* 0x84 */ 0xCF, 0x34, 0x51, 0x88,
/* 0x85 */ 0xC6, 0x3C, 0x63,
/* 0x86 */ 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x3F, 0xFF, 0xFC, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x00,
/* 0x87 */ 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x3F, 0xFF, 0xFC, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x0F, 0xFF, 0xFF, 0x0C, 0x03, 0x00, 0xC0, 0x30,
/* 0x88 */ 0x01, 0xF0, 0x1F, 0xF0, 0xE0, 0xC7, 0x00, 0x18, 0x00, 0xC0, 0x07, 0xFF, 0x3F, 0xFC, 0x30, 0x01, 0xFF, 0x8F, 0xFC, 0x0C, 0x00, 0x18, 0x00, 0x70, 0x00, 0xE0, 0x81, 0xFE, 0x03, 0xF0,
/* 0x89 */ 0x38, 0x18, 0x00, 0xF8, 0x30, 0x03, 0x18, 0xC0, 0x04, 0x11, 0x80, 0x0C, 0x66, 0x00, 0x0F, 0x8C, 0x00, 0x0E, 0x30, 0x00, 0x00, 0x40, 0x00, 0x01, 0x80, 0x00, 0x06, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x31, 0xC0, 0xE0, 0x67, 0xC3, 0xC1, 0x98, 0xCC, 0xC3, 0x20, 0x90, 0x8C, 0x63, 0x33, 0x10, 0x7C, 0x3C, 0x60, 0x70, 0x38,
/* 0x8A */ 0x1F, 0xF8, 0x00, 0x3F, 0xF0, 0x00, 0x60, 0x60, 0x00, 0xC0, 0xC0, 0x01, 0x81, 0x80, 0x03, 0x03, 0x00, 0x06, 0x06, 0x00, 0x0C, 0x0C, 0x00, 0x18, 0x1F, 0xF0, 0x30, 0x3F, 0xF0, 0x60, 0x60, 0x30, 0xC0, 0xC0, 0x31, 0x81, 0x80, 0x66, 0x03, 0x00, 0xCC, 0x06, 0x01, 0xB8, 0x0C, 0x06, 0xE0, 0x1F, 0xFD, 0x80, 0x3F, 0xE0,
/* 0x8B */ 0x2F, 0x49, 0x99,
/* 0x8C */ 0xC0, 0x60, 0x06, 0x03, 0x00, 0x30, 0x18, 0x01, 0x80, 0xC0, 0x0C, 0x06, 0x00, 0x60, 0x30, 0x03, 0x01, 0x80, 0x18, 0x0C, 0x00, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, 0x30, 0x18, 0x1D, 0x80, 0xC0, 0x3C, 0x06, 0x01, 0xE0, 0x30, 0x0F, 0x01, 0x80, 0x78, 0x0C, 0x06, 0xC0, 0x7F, 0xF6, 0x03, 0xFE, 0x00,
/* 0x8D */ 0x03, 0x00, 0x60, 0x0C, 0x00, 0x00, 0xC0, 0x7C, 0x0E, 0xC1, 0xCC, 0x38, 0xC7, 0x0C, 0xE0, 0xDC, 0x0F, 0x80, 0xF0, 0x0F, 0x80, 0xDC, 0x0C, 0xE0, 0xC7, 0x0C, 0x30, 0xC3, 0x8C, 0x1C, 0xC0, 0xEC, 0x07,
/* 0x8E */ 0xFF, 0xE0, 0xFF, 0xE0, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x07, 0xFC, 0x07, 0xFE, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03,
/* 0x8F */ 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xFF, 0xFF, 0xFF, 0xC0, 0xC0, 0x06, 0x00, 0x30, 0x00,
/* 0x90 */ 0x30, 0x0F, 0xF0, 0xFF, 0x03, 0x00, 0x30, 0x03, 0x3C, 0x37, 0xE3, 0xC7, 0x38, 0x33, 0x03, 0x30, 0x33, 0x03, 0x30, 0x33, 0x03, 0x30, 0x33, 0x03, 0x30, 0x33, 0x03, 0x00, 0x60, 0x06, 0x01, 0x80, 0x30,
/* 0x91 */ 0x6A, 0xF0,
/* 0x92 */ 0xF5, 0x60,
/* 0x93 */ 0x4E, 0x28, 0xA2, 0xCF, 0x30,
/* 0x94 */ 0xCF, 0x34, 0x51, 0x4E, 0x20,
/* 0x95 */ 0x7B, 0xFF, 0xFF, 0xFD, 0xE0,
/* 0x96 */ 0xFF, 0xFF, 0xF0,
/* 0x97 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0,
/* 0x98 */
/* 0x99 */ 0xFF, 0x70, 0x1F, 0xFD, 0xC0, 0x71, 0x87, 0x83, 0xC6, 0x1E, 0x0F, 0x18, 0x68, 0x3C, 0x61, 0xB1, 0xB1, 0x86, 0xC6, 0xC6, 0x19, 0x1B, 0x18, 0x66, 0xCC, 0x61, 0x9B, 0x31, 0x86, 0x3C, 0xC6, 0x18, 0xE3, 0x18, 0x63, 0x8C,
/* 0x9A */ 0x7F, 0x80, 0x3F, 0xC0, 0x18, 0x60, 0x0C, 0x30, 0x06, 0x18, 0x03, 0x0F, 0xF1, 0x87, 0xFC, 0xC3, 0x07, 0x61, 0x81, 0xB0, 0xC0, 0xD0, 0x60, 0xF8, 0x3F, 0xEC, 0x1F, 0xE0,
/* 0x9B */ 0x99, 0x92, 0xF4,
/* 0x9C */ 0xC0, 0xC0, 0x30, 0x30, 0x0C, 0x0C, 0x03, 0x03, 0x00, 0xC0, 0xC0, 0x3F, 0xFF, 0xCF, 0xFF, 0xFB, 0x03, 0x07, 0xC0, 0xC0, 0xF0, 0x30, 0x3C, 0x0C, 0x1F, 0x03, 0xFE, 0xC0, 0xFF, 0x00,
/* 0x9D */ 0x07, 0x07, 0x03, 0x03, 0x00, 0x06, 0x0F, 0x0D, 0x8C, 0xCC, 0x6C, 0x3C, 0x1E, 0x0F, 0x86, 0xE3, 0x39, 0x8E, 0xC3, 0xE0, 0xC0,
/* 0x9E */ 0x30, 0x1F, 0xE3, 0xFC, 0x18, 0x03, 0x00, 0x67, 0x0D, 0xF9, 0xC7, 0x38, 0x66, 0x0C, 0xC1, 0x98, 0x33, 0x06, 0x60, 0xCC, 0x19, 0x83, 0x30, 0x66, 0x0C,
/* 0x9F */ 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0xFF, 0xFF, 0xC1, 0x80, 0x60,
/* 0xA0 */
/* 0xA1 */ 0x10, 0x40, 0xC4, 0x07, 0xE0, 0x1E, 0x0C, 0x01, 0xF0, 0x1D, 0x80, 0xCE, 0x0E, 0x30, 0x61, 0xC7, 0x06, 0x30, 0x3B, 0x81, 0xD8, 0x07, 0xC0, 0x3C, 0x00, 0xE0, 0x06, 0x00, 0x70, 0x03, 0x80, 0x38, 0x01, 0xC0, 0x1C, 0x00,
/* 0xA2 */ 0x21, 0x86, 0x20, 0xFC, 0x07, 0x0E, 0x06, 0xC0, 0xD8, 0x33, 0x86, 0x30, 0xC6, 0x30, 0xC6, 0x0C, 0xC1, 0xB0, 0x36, 0x03, 0xC0, 0x70, 0x0E, 0x01, 0x80, 0x30, 0x0E, 0x07, 0x80, 0xE0, 0x00,
/* 0xA3 */ 0x01, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x80, 0xC0, 0x60, 0x3C, 0x1E, 0x0F, 0x07, 0xC7, 0x7F, 0x1F, 0x00,
/* 0xA4 */ 0xDD, 0xFF, 0xD8, 0xD8, 0x3C, 0x1E, 0x0F, 0x8D, 0xFF, 0xDD, 0x80,
/* 0xA5 */ 0x00, 0x60, 0x0F, 0xFF, 0xFF, 0xFC, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x60, 0x00,
/* 0xA6 */ 0xFF, 0xFF, 0xF0, 0x3F, 0xFF, 0xFC,
/* 0xA7 */ 0x0F, 0x03, 0xF0, 0xE7, 0x18, 0x63, 0x0C, 0x70, 0x07, 0x03, 0xF8, 0xC3, 0x98, 0x3B, 0x03, 0xF0, 0x37, 0x06, 0x78, 0xC7, 0xB0, 0x7C, 0x03, 0x80, 0x39, 0x83, 0x30, 0x67, 0x1C, 0x7F, 0x07, 0xC0,
/* 0xA8 */ 0x19, 0x81, 0x98, 0x00, 0x0F, 0xFF, 0xFF, 0xFC, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0F, 0xFE, 0xFF, 0xEC, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0F, 0xFF, 0xFF, 0xF0,
/* 0xA9 */ 0x03, 0xF0, 0x03, 0xFF, 0x01, 0xE0, 0xE0, 0xE3, 0x1C, 0x73, 0xF3, 0x99, 0x86, 0x6C, 0xC1, 0x8F, 0x30, 0x03, 0xCC, 0x00, 0xF3, 0x00, 0x3C, 0xC1, 0x8D, 0x98, 0x66, 0x77, 0xF3, 0x8E, 0x79, 0xC1, 0xC0, 0xE0, 0x3F, 0xF0, 0x03, 0xF0, 0x00,
/* 0xAA */ 0x07, 0xE0, 0x3F, 0xF0, 0xF0, 0x71, 0x80, 0x76, 0x00, 0x6C, 0x00, 0x30, 0x00, 0x60, 0x00, 0xFF, 0xE1, 0xFF, 0xC3, 0x00, 0x06, 0x00, 0x0E, 0x00, 0x6C, 0x00, 0xDC, 0x03, 0x1C, 0x0E, 0x1F, 0xF8, 0x0F, 0xC0,
/* 0xAB */ 0x21, 0x63, 0xE7, 0x84, 0x84, 0xE7, 0x63, 0x21,
/* 0xAC */ 0xFF, 0xFF, 0xFF, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03,
/* 0xAD */ 0xFF, 0xF0,
/* 0xAE */ 0x03, 0xF0, 0x03, 0xFF, 0x01, 0xE0, 0xE0, 0xFF, 0x1C, 0x7F, 0xF3, 0x9B, 0x04, 0x6C, 0xC1, 0x8F, 0x30, 0x43, 0xCF, 0xF0, 0xF3, 0xFC, 0x3C, 0xC1, 0x0D, 0xB0, 0x66, 0x7C, 0x1B, 0x8F, 0x07, 0xC1, 0xC0, 0xE0, 0x3F, 0xF0, 0x03, 0xF0, 0x00,
/* 0xAF */ 0xC7, 0x8C, 0x01, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x00,
/* 0xB0 */ 0x38, 0xFB, 0x1C, 0x18, 0x38, 0xDF, 0x1C,
/* 0xB1 */ 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x7F, 0xE7, 0xFE, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xF0,
/* 0xB2 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xF0,
/* 0xB3 */ 0xF0, 0x3F, 0xFF, 0xFF, 0xF0,
/* 0xB4 */ 0x03, 0x03, 0xFF, 0xFF, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0,
/* 0xB5 */ 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x1C, 0xE3, 0xCF, 0xEF, 0xFC, 0x7C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x00,
/* 0xB6 */ 0x1F, 0xE7, 0xFD, 0xF3, 0x7E, 0x6F, 0xCD, 0xF9, 0xBF, 0x37, 0xE6, 0x7C, 0xCF, 0x98, 0xF3, 0x06, 0x60, 0xCC, 0x19, 0x83, 0x30, 0x66, 0x0C, 0xC1, 0x98, 0x33, 0x06, 0x60, 0xCC,
/* 0xB7 */ 0xF0,
/* 0xB8 */ 0x19, 0x83, 0x30, 0x00, 0x00, 0x01, 0xF0, 0x7F, 0x1C, 0x77, 0x03, 0xC0, 0x7F, 0xFF, 0xFF, 0xE0, 0x0C, 0x01, 0xC0, 0xDC, 0x31, 0xFC, 0x1F, 0x00,
/* 0xB9 */ 0xC0, 0xC0, 0x18, 0x18, 0x03, 0x83, 0x00, 0x70, 0x60, 0x0B, 0x0C, 0x01, 0x61, 0x8F, 0xA6, 0x33, 0x1C, 0xC6, 0x41, 0x88, 0xC8, 0x31, 0x99, 0x06, 0x13, 0x20, 0xC3, 0x66, 0x38, 0x6C, 0xEF, 0x07, 0x8F, 0xA0, 0xF0, 0x04, 0x0E, 0x00, 0x81, 0xC7, 0xF0, 0x18, 0xFC,
/* 0xBA */ 0x1F, 0x87, 0xF9, 0xC3, 0x30, 0x3E, 0x01, 0xFE, 0x3F, 0xC6, 0x00, 0xE0, 0x0C, 0x0D, 0xC3, 0x9F, 0xE1, 0xF8,
/* 0xBB */ 0x88, 0xC6, 0xE7, 0x21, 0x21, 0xE7, 0xC6, 0x88,
/* 0xBC */ 0x33, 0x00, 0x03, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0xE0,
/* 0xBD */ 0x0F, 0xE0, 0x7F, 0xC3, 0x83, 0x98, 0x07, 0x60, 0x0D, 0x80, 0x07, 0x00, 0x1E, 0x00, 0x3F, 0x80, 0x3F, 0xC0, 0x0F, 0x80, 0x07, 0xC0, 0x0F, 0x00, 0x3E, 0x00, 0xDE, 0x0E, 0x3F, 0xF0, 0x3F, 0x80,
/* 0xBE */ 0x3E, 0x1F, 0xEE, 0x1B, 0x00, 0xC0, 0x3C, 0x07, 0xF0, 0x3F, 0x00, 0xF0, 0x3E, 0x1D, 0xFE, 0x3E, 0x00,
/* 0xBF */ 0xCF, 0x30, 0x00, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30,
/* 0xC0 */ 0x07, 0x80, 0x1E, 0x00, 0x78, 0x03, 0xF0, 0x0C, 0xC0, 0x33, 0x01, 0xCE, 0x06, 0x18, 0x18, 0x60, 0xE1, 0xC3, 0x03, 0x0F, 0xFC, 0x7F, 0xF9, 0x80, 0x66, 0x01, 0xB8, 0x07, 0xC0, 0x0F, 0x00, 0x30,
/* 0xC1 */ 0xFF, 0xE7, 0xFF, 0x30, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x1F, 0xF8, 0xFF, 0xF6, 0x01, 0xB0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x06, 0xFF, 0xF7, 0xFE, 0x00,
/* 0xC2 */ 0xFF, 0xC7, 0xFF, 0x30, 0x1D, 0x80, 0x6C, 0x03, 0x60, 0x1B, 0x00, 0xD8, 0x0C, 0xFF, 0xC7, 0xFF, 0x30, 0x0D, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x06, 0xFF, 0xF7, 0xFE, 0x00,
/* 0xC3 */ 0xFF, 0xFF, 0xFF, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x18, 0x00,
/* 0xC4 */ 0x07, 0xFE, 0x01, 0xFF, 0x80, 0x60, 0x60, 0x18, 0x18, 0x06, 0x06, 0x01, 0x81, 0x80, 0x60, 0x60, 0x18, 0x18, 0x06, 0x06, 0x01, 0x81, 0x80, 0x60, 0x60, 0x18, 0x18, 0x0E, 0x06, 0x03, 0x01, 0x80, 0xC0, 0x60, 0x70, 0x18, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x0F, 0x00, 0x03, 0xC0, 0x00, 0xC0,
/* 0xC5 */ 0xFF, 0xFF, 0xFF, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xEF, 0xFE, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xFF, 0xFF,
/* 0xC6 */ 0x70, 0x60, 0xE3, 0x06, 0x0C, 0x38, 0x61, 0xC1, 0xC6, 0x38, 0x0E, 0x67, 0x00, 0x66, 0x60, 0x03, 0x6C, 0x00, 0x3F, 0xC0, 0x01, 0xF8, 0x00, 0x1F, 0x80, 0x03, 0xFC, 0x00, 0x76, 0xE0, 0x0E, 0x67, 0x01, 0xC6, 0x38, 0x38, 0x61, 0xC3, 0x06, 0x0C, 0x60, 0x60, 0xEE, 0x06, 0x07,
/* 0xC7 */ 0x0F, 0x81, 0xFF, 0x0C, 0x18, 0xC0, 0x66, 0x03, 0x00, 0x18, 0x01, 0xC0, 0x1C, 0x07, 0xC0, 0x3F, 0x00, 0x1C, 0x00, 0x7C, 0x01, 0xE0, 0x0F, 0x80, 0x6E, 0x0E, 0x3F, 0xE0, 0x7E, 0x00,
/* 0xC8 */ 0xC0, 0x3E, 0x01, 0xF0, 0x1F, 0x80, 0xFC, 0x0D, 0xE0, 0xEF, 0x06, 0x78, 0x73, 0xC3, 0x1E, 0x30, 0xF1, 0x87, 0x98, 0x3D, 0xC1, 0xEC, 0x0F, 0xC0, 0x7E, 0x03, 0xE0, 0x1F, 0x00, 0xC0,
/* 0xC9 */ 0x10, 0x40, 0xC4, 0x07, 0xE0, 0x1E, 0x0C, 0x03, 0xE0, 0x1F, 0x01, 0xF8, 0x0F, 0xC0, 0xDE, 0x0E, 0xF0, 0x67, 0x87, 0x3C, 0x31, 0xE3, 0x0F, 0x18, 0x79, 0x83, 0xDC, 0x1E, 0xC0, 0xFC, 0x07, 0xE0, 0x3E, 0x01, 0xF0, 0x0C,
/* 0xCA */ 0xC0, 0x7C, 0x0E, 0xC1, 0xCC, 0x38, 0xC7, 0x0C, 0xE0, 0xDC, 0x0F, 0x80, 0xF0, 0x0F, 0x80, 0xDC, 0x0C, 0xE0, 0xC7, 0x0C, 0x30, 0xC3, 0x8C, 0x1C, 0xC0, 0xEC, 0x07,
/* 0xCB */ 0x1F, 0xFC, 0x7F, 0xF1, 0x80, 0xC6, 0x03, 0x18, 0x0C, 0x60, 0x31, 0x80, 0xC6, 0x03, 0x18, 0x0C, 0x60, 0x31, 0x80, 0xC6, 0x03, 0x18, 0x0C, 0xC0, 0x33, 0x00, 0xDC, 0x03, 0xE0, 0x0F, 0x00, 0x30,
/* 0xCC */ 0xE0, 0x07, 0xE0, 0x07, 0xF0, 0x0F, 0xF0, 0x0F, 0xD0, 0x0F, 0xD8, 0x1B, 0xD8, 0x1B, 0xD8, 0x1B, 0xCC, 0x33, 0xCC, 0x33, 0xCC, 0x33, 0xC6, 0x63, 0xC6, 0x63, 0xC6, 0x63, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC1, 0x83,
/* 0xCD */ 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xFF, 0xFF, 0xFF, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xC0,
/* 0xCE */ 0x07, 0xF0, 0x0F, 0xFE, 0x0F, 0x07, 0x86, 0x00, 0xC6, 0x00, 0x33, 0x00, 0x1B, 0x00, 0x07, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x36, 0x00, 0x33, 0x00, 0x18, 0xC0, 0x18, 0x78, 0x3C, 0x1F, 0xFC, 0x03, 0xF8, 0x00,
/* 0xCF */ 0xFF, 0xFF, 0xFF, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xC0,
/* 0xD0 */ 0xFF, 0x8F, 0xFE, 0xC0, 0x6C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x06, 0xFF, 0xEF, 0xFC, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00,
/* 0xD1 */ 0x07, 0xE0, 0x3F, 0xF0, 0xE0, 0x73, 0x80, 0x76, 0x00, 0x6C, 0x00, 0x30, 0x00, 0x60, 0x00, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x06, 0x00, 0x0E, 0x00, 0x6C, 0x00, 0xDC, 0x03, 0x1E, 0x0E, 0x1F, 0xF8, 0x0F, 0xC0,
/* 0xD2 */ 0xFF, 0xFF, 0xFF, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60,
/* 0xD3 */ 0xC0, 0x1F, 0x01, 0xD8, 0x0C, 0xE0, 0xE3, 0x06, 0x1C, 0x70, 0x63, 0x03, 0xB8, 0x1D, 0x80, 0x7C, 0x03, 0xC0, 0x0E, 0x00, 0x60, 0x07, 0x00, 0x38, 0x03, 0x80, 0x1C, 0x01, 0xC0, 0x00,
/* 0xD4 */ 0x00, 0xC0, 0x00, 0x30, 0x00, 0xFF, 0xC0, 0xFF, 0xFC, 0x78, 0xC7, 0x98, 0x30, 0x6E, 0x0C, 0x1F, 0x03, 0x03, 0xC0, 0xC0, 0xF0, 0x30, 0x3C, 0x0C, 0x0F, 0x83, 0x07, 0x60, 0xC1, 0x9E, 0x31, 0xE3, 0xFF, 0xF0, 0x3F, 0xF0, 0x00, 0xC0, 0x00, 0x30, 0x00,
/* 0xD5 */ 0xE0, 0x1D, 0x80, 0xE7, 0x03, 0x0E, 0x1C, 0x18, 0x60, 0x73, 0x00, 0xFC, 0x01, 0xE0, 0x07, 0x00, 0x1E, 0x00, 0xF8, 0x03, 0x30, 0x1C, 0xE0, 0xE1, 0x83, 0x07, 0x1C, 0x0E, 0xE0, 0x1B, 0x00, 0x70,
/* 0xD6 */ 0xC0, 0x19, 0x80, 0x33, 0x00, 0x66, 0x00, 0xCC, 0x01, 0x98, 0x03, 0x30, 0x06, 0x60, 0x0C, 0xC0, 0x19, 0x80, 0x33, 0x00, 0x66, 0x00, 0xCC, 0x01, 0x98, 0x03, 0x30, 0x06, 0x60, 0x0C, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x18, 0x00, 0x30, 0x00, 0x60,
/* 0xD7 */ 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x37, 0xFF, 0x3F, 0xF0, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03,
/* 0xD8 */ 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xFF, 0xFF, 0xFF, 0xFF,
/* 0xD9 */ 0xC1, 0x83, 0x30, 0x60, 0xCC, 0x18, 0x33, 0x06, 0x0C, 0xC1, 0x83, 0x30, 0x60, 0xCC, 0x18, 0x33, 0x06, 0x0C, 0xC1, 0x83, 0x30, 0x60, 0xCC, 0x18, 0x33, 0x06, 0x0C, 0xC1, 0x83, 0x30, 0x60, 0xCC, 0x18, 0x33, 0x06, 0x0C, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x0C, 0x00, 0x03, 0x00, 0x00, 0xC0,
/* 0xDA */ 0xFE, 0x00, 0x3F, 0x80, 0x00, 0x60, 0x00, 0x18, 0x00, 0x06, 0x00, 0x01, 0x80, 0x00, 0x60, 0x00, 0x1F, 0xF8, 0x07, 0xFF, 0x01, 0x80, 0xE0, 0x60, 0x1C, 0x18, 0x03, 0x06, 0x00, 0xC1, 0x80, 0x30, 0x60, 0x1C, 0x18, 0x0E, 0x07, 0xFF, 0x01, 0xFF, 0x80,
/* 0xDB */ 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x0F, 0x00, 0x07, 0xFF, 0x83, 0xFF, 0xE1, 0xE0, 0x38, 0xF0, 0x0E, 0x78, 0x03, 0x3C, 0x01, 0x9E, 0x00, 0xCF, 0x00, 0xE7, 0x80, 0xE3, 0xFF, 0xE1, 0xFF, 0xE0, 0xC0,
/* 0xDC */ 0xC0, 0x06, 0x00, 0x30, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x1F, 0xF8, 0xFF, 0xE6, 0x03, 0xB0, 0x0F, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0xF8, 0x0E, 0xFF, 0xE7, 0xFE, 0x00,
/* 0xDD */ 0x0F, 0xC0, 0x7F, 0xE1, 0xC1, 0xE7, 0x01, 0xCC, 0x01, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x1F, 0xFE, 0x3F, 0xFC, 0x00, 0x38, 0x00, 0x7C, 0x00, 0xD8, 0x03, 0x98, 0x0E, 0x38, 0x3C, 0x3F, 0xF0, 0x1F, 0x80,
/* 0xDE */ 0xC0, 0x3F, 0x06, 0x07, 0xFE, 0x30, 0x70, 0x39, 0x87, 0x00, 0xEC, 0x30, 0x03, 0x61, 0x80, 0x1B, 0x18, 0x00, 0x78, 0xC0, 0x03, 0xFE, 0x00, 0x1F, 0xF0, 0x00, 0xF1, 0x80, 0x07, 0x8C, 0x00, 0x3C, 0x70, 0x03, 0xE1, 0x80, 0x1B, 0x0E, 0x01, 0xD8, 0x38, 0x1C, 0xC0, 0xFF, 0xC6, 0x01, 0xF8, 0x00,
/* 0xDF */ 0x0F, 0xFC, 0xFF, 0xF3, 0x00, 0xD8, 0x03, 0x60, 0x0D, 0x80, 0x36, 0x00, 0xCC, 0x03, 0x3F, 0xFC, 0x3F, 0xF0, 0x38, 0xC1, 0xC3, 0x0E, 0x0C, 0x70, 0x31, 0x80, 0xCE, 0x03, 0x70, 0x0F, 0x80, 0x30,
/* 0xE0 */ 0x3F, 0x07, 0xF8, 0xE1, 0xCC, 0x0C, 0x00, 0xC0, 0x1C, 0x3F, 0xCF, 0x8C, 0xC0, 0xCC, 0x0C, 0xE3, 0xC7, 0xEF, 0x3C, 0x70,
/* 0xE1 */ 0x00, 0xC0, 0x38, 0x3F, 0x1F, 0x87, 0x00, 0xC0, 0x17, 0xC7, 0xFC, 0xF1, 0xDC, 0x1F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x1F, 0x07, 0x71, 0xC7, 0xF0, 0x7C, 0x00,
/* 0xE2 */ 0xFE, 0x3F, 0xEC, 0x3B, 0x06, 0xC1, 0xB0, 0xEF, 0xF3, 0x0E, 0xC0, 0xF0, 0x3C, 0x1F, 0xFE, 0xFF, 0x00,
/* 0xE3 */ 0xFF, 0xFF, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x06, 0x0C, 0x00,
/* 0xE4 */ 0x0F, 0xF0, 0x3F, 0xC0, 0xC3, 0x03, 0x0C, 0x0C, 0x30, 0x30, 0xC0, 0xC3, 0x03, 0x0C, 0x1C, 0x30, 0x60, 0xC1, 0x83, 0x3F, 0xFF, 0xFF, 0xFF, 0x00, 0x3C, 0x00, 0xC0,
/* 0xE5 */ 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x3C, 0x07, 0xFF, 0xFF, 0xFE, 0x00, 0xC0, 0x1C, 0x0D, 0xC3, 0x1F, 0xC1, 0xF0,
/* 0xE6 */ 0xE1, 0x87, 0x71, 0x8E, 0x39, 0x9C, 0x1D, 0xB8, 0x0F, 0xF0, 0x07, 0xE0, 0x07, 0xE0, 0x0F, 0xF0, 0x1D, 0xB8, 0x39, 0x9C, 0x71, 0x8E, 0xE1, 0x87, 0xC1, 0x83,
/* 0xE7 */ 0x3E, 0x7F, 0xB0, 0xE0, 0x30, 0x18, 0x78, 0x3C, 0x07, 0x01, 0xE0, 0xF8, 0xEF, 0xE3, 0xE0,
/* 0xE8 */ 0xC0, 0xF8, 0x3F, 0x07, 0xE1, 0xFC, 0x37, 0x8C, 0xF3, 0x9E, 0x63, 0xD8, 0x7F, 0x0F, 0xC1, 0xF8, 0x3E, 0x06,
/* 0xE9 */ 0x21, 0x86, 0x20, 0xFC, 0x0F, 0x0C, 0x0F, 0x83, 0xF0, 0x7E, 0x1F, 0xC3, 0x78, 0xCF, 0x39, 0xE6, 0x3D, 0x87, 0xF0, 0xFC, 0x1F, 0x83, 0xE0, 0x60,
/* 0xEA */ 0xC1, 0xE1, 0xB1, 0x99, 0x8D, 0x87, 0x83, 0xC1, 0xF0, 0xDC, 0x67, 0x31, 0xD8, 0x7C, 0x18,
/* 0xEB */ 0x3F, 0xCF, 0xF3, 0x0C, 0xC3, 0x30, 0xCC, 0x33, 0x0C, 0xC3, 0x30, 0xDC, 0x36, 0x0F, 0x83, 0xC0, 0xC0,
/* 0xEC */ 0xE0, 0x7E, 0x07, 0xF0, 0xFF, 0x0F, 0xF0, 0xFD, 0x9B, 0xD9, 0xBD, 0xFB, 0xCF, 0x3C, 0xF3, 0xC6, 0x3C, 0x63, 0xC0, 0x30,
/* 0xED */ 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xFF, 0xFF, 0xFF, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xC0,
/* 0xEE */ 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x7C, 0x1D, 0xC7, 0x1F, 0xC1, 0xF0,
/* 0xEF */ 0xFF, 0xFF, 0xFC, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xC0,
/* 0xF0 */ 0xCF, 0x8D, 0xFC, 0xF0, 0xEE, 0x06, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3E, 0x06, 0xF0, 0xEF, 0xFC, 0xCF, 0x8C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x00,
/* 0xF1 */ 0x1F, 0x0F, 0xE6, 0x1F, 0x83, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x38, 0x37, 0x1C, 0xFE, 0x1F, 0x00,
/* 0xF2 */ 0xFF, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
/* 0xF3 */ 0xE0, 0x6C, 0x0D, 0x83, 0x38, 0x63, 0x0C, 0x63, 0x0C, 0x60, 0xCC, 0x1B, 0x03, 0x60, 0x3C, 0x07, 0x00, 0xE0, 0x18, 0x03, 0x00, 0xE0, 0x78, 0x0E, 0x00,
/* 0xF4 */ 0x00, 0xC0, 0x00, 0x18, 0x00, 0x03, 0x00, 0x0F, 0x67, 0x87, 0xFD, 0xF8, 0xC3, 0xE3, 0xB8, 0x78, 0x3E, 0x06, 0x03, 0xC0, 0xC0, 0x78, 0x18, 0x0F, 0x03, 0x01, 0xE0, 0x60, 0x3E, 0x1E, 0x0E, 0xC3, 0xE3, 0x9F, 0xFF, 0xE1, 0xF6, 0x78, 0x00, 0xC0, 0x00, 0x18, 0x00, 0x03, 0x00, 0x00, 0x60, 0x00,
/* 0xF5 */ 0xC1, 0xF8, 0x66, 0x30, 0xCC, 0x3E, 0x07, 0x00, 0xC0, 0x78, 0x36, 0x0C, 0xC6, 0x3B, 0x06, 0xC0, 0xC0,
/* 0xF6 */ 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x0C, 0xC0, 0xCF, 0xFF, 0xFF, 0xF0, 0x03, 0x00, 0x30,
/* 0xF7 */ 0xC1, 0xE0, 0xF0, 0x78, 0x3C, 0x1E, 0x0F, 0xFE, 0xFF, 0x01, 0x80, 0xC0, 0x60, 0x30, 0x18,
/* 0xF8 */ 0xC3, 0x0F, 0x0C, 0x3C, 0x30, 0xF0, 0xC3, 0xC3, 0x0F, 0x0C, 0x3C, 0x30, 0xF0, 0xC3, 0xC3, 0x0F, 0x0C, 0x3C, 0x30, 0xFF, 0xFF, 0xFF, 0xFC,
/* 0xF9 */ 0xC3, 0x0C, 0xC3, 0x0C, 0xC3, 0x0C, 0xC3, 0x0C, 0xC3, 0x0C, 0xC3, 0x0C, 0xC3, 0x0C, 0xC3, 0x0C, 0xC3, 0x0C, 0xC3, 0x0C, 0xC3, 0x0C, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x03, 0x00, 0x03,
/* 0xFA */ 0xFC, 0x03, 0xF0, 0x00, 0xC0, 0x03, 0x00, 0x0F, 0xF0, 0x3F, 0xE0, 0xC1, 0xC3, 0x03, 0x0C, 0x0C, 0x30, 0x30, 0xC1, 0xC3, 0xFE, 0x0F, 0xF0,
/* 0xFB */ 0xC0, 0x0F, 0x00, 0x3C, 0x00, 0xF0, 0x03, 0xFF, 0x0F, 0xFE, 0x3C, 0x1C, 0xF0, 0x33, 0xC0, 0xCF, 0x03, 0x3C, 0x1C, 0xFF, 0xE3, 0xFF, 0x0C,
/* 0xFC */ 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xFF, 0x3F, 0xEC, 0x1F, 0x03, 0xC0, 0xF0, 0x3C, 0x1F, 0xFE, 0xFF, 0x00,
/* 0xFD */ 0x3F, 0x0F, 0xF1, 0x87, 0x60, 0x60, 0x0E, 0x3F, 0xC7, 0xF8, 0x03, 0x00, 0xD8, 0x1B, 0x87, 0x3F, 0xC3, 0xF0,
/* 0xFE */ 0xC0, 0xF8, 0xC1, 0xFC, 0xC3, 0x8E, 0xC7, 0x07, 0xC6, 0x03, 0xFE, 0x03, 0xFE, 0x03, 0xC6, 0x03, 0xC6, 0x03, 0xC7, 0x06, 0xC3, 0x8E, 0xC1, 0xFC, 0xC0, 0xF8,
/* 0xFF */ 0x1F, 0xCF, 0xF7, 0x0D, 0x83, 0x60, 0xD8, 0x33, 0xFC, 0x7F, 0x0C, 0xC6, 0x33, 0x0D, 0x83, 0xC0, 0xC0,
};
const GFXglyph FreeSans12pt_Win1251Glyphs[] PROGMEM = {
/* 0x01 */ { 0, 19, 20, 21, 1, -17 },
/* 0x02 */ { 48, 19, 20, 21, 1, -17 },
/* 0x03 */ { 96, 21, 20, 23, 1, -17 },
/* 0x04 */ { 149, 21, 20, 23, 1, -17 },
/* 0x05 */ { 202, 20, 20, 22, 1, -17 },
/* 0x06 */ { 252, 20, 20, 22, 1, -17 },
/* 0x07 */ { 302, 0, 0, 8, 0, 0 },
/* 0x08 */ { 302, 23, 20, 25, 1, -17 },
/* 0x09 */ { 360, 23, 16, 25, 1, -16 },
/* 0x0A */ { 406, 0, 0, 8, 0, 0 },
/* 0x0B */ { 406, 21, 20, 23, 1, -17 },
/* 0x0C */ { 459, 19, 18, 21, 1, -15 },
/* 0x0D */ { 502, 0, 0, 8, 0, 0 },
/* 0x0E */ { 502, 20, 20, 22, 1, -17 },
/* 0x0F */ { 552, 21, 21, 23, 1, -18 },
/* 0x10 */ { 608, 19, 20, 21, 1, -17 },
/* 0x11 */ { 656, 21, 20, 23, 1, -17 },
/* 0x12 */ { 709, 20, 20, 22, 1, -17 },
/* 0x13 */ { 759, 21, 20, 23, 1, -17 },
/* 0x14 */ { 812, 21, 20, 23, 1, -17 },
/* 0x15 */ { 865, 22, 20, 24, 1, -17 },
/* 0x16 */ { 920, 16, 20, 18, 1, -17 },
/* 0x17 */ { 960, 21, 20, 23, 1, -17 },
/* 0x18 */ { 1013, 23, 20, 25, 1, -17 },
/* 0x19 */ { 1071, 21, 20, 23, 1, -17 },
/* 0x1A */ { 1124, 15, 19, 17, 1, -16 },
/* 0x1B */ { 1160, 24, 21, 26, 1, -18 },
/* 0x1C */ { 1223, 21, 20, 23, 1, -17 },
/* 0x1D */ { 1276, 21, 21, 23, 1, -18 },
/* 0x1E */ { 1332, 20, 20, 22, 1, -17 },
/* 0x1F */ { 1382, 15, 20, 17, 1, -17 },
/* ' ' 0x20 */ { 1420, 0, 0, 6, 0, 0 },
/* '!' 0x21 */ { 1420, 2, 18, 8, 3, -16 },
/* '"' 0x22 */ { 1425, 6, 6, 8, 1, -15 },
/* '#' 0x23 */ { 1430, 13, 16, 13, 0, -14 },
/* '$' 0x24 */ { 1456, 11, 20, 13, 1, -16 },
/* '%' 0x25 */ { 1484, 20, 17, 21, 1, -15 },
/* '&' 0x26 */ { 1527, 14, 17, 16, 1, -15 },
/* ''' 0x27 */ { 1557, 2, 6, 5, 1, -15 },
/* '(' 0x28 */ { 1559, 5, 23, 8, 2, -16 },
/* ')' 0x29 */ { 1574, 5, 23, 8, 1, -16 },
/* '*' 0x2A */ { 1589, 7, 7, 9, 1, -16 },
/* '+' 0x2B */ { 1596, 10, 11, 14, 2, -9 },
/* ',' 0x2C */ { 1610, 2, 6, 7, 2, 0 },
/* '-' 0x2D */ { 1612, 6, 2, 8, 1, -6 },
/* '.' 0x2E */ { 1614, 2, 2, 6, 2, 0 },
/* '/' 0x2F */ { 1615, 7, 18, 7, 0, -16 },
/* '0' 0x30 */ { 1631, 11, 17, 13, 1, -15 },
/* '1' 0x31 */ { 1655, 5, 17, 13, 3, -15 },
/* '2' 0x32 */ { 1666, 11, 17, 13, 1, -15 },
/* '3' 0x33 */ { 1690, 11, 17, 13, 1, -15 },
/* '4' 0x34 */ { 1714, 11, 17, 13, 1, -15 },
/* '5' 0x35 */ { 1738, 11, 17, 13, 1, -15 },
/* '6' 0x36 */ { 1762, 11, 17, 13, 1, -15 },
/* '7' 0x37 */ { 1786, 11, 17, 13, 1, -15 },
/* '8' 0x38 */ { 1810, 11, 17, 13, 1, -15 },
/* '9' 0x39 */ { 1834, 11, 17, 13, 1, -15 },
/* ':' 0x3A */ { 1858, 2, 13, 6, 2, -11 },
/* ';' 0x3B */ { 1862, 2, 16, 6, 2, -10 },
/* '<' 0x3C */ { 1866, 12, 11, 14, 1, -9 },
/* '=' 0x3D */ { 1883, 12, 6, 14, 1, -7 },
/* '>' 0x3E */ { 1892, 12, 11, 14, 1, -9 },
/* '?' 0x3F */ { 1909, 10, 18, 13, 2, -16 },
/* '@' 0x40 */ { 1932, 22, 21, 24, 1, -16 },
/* 'A' 0x41 */ { 1990, 14, 18, 16, 1, -16 },
/* 'B' 0x42 */ { 2022, 13, 18, 16, 2, -16 },
/* 'C' 0x43 */ { 2052, 15, 18, 17, 1, -16 },
/* 'D' 0x44 */ { 2086, 14, 18, 17, 2, -16 },
/* 'E' 0x45 */ { 2118, 12, 18, 15, 2, -16 },
/* 'F' 0x46 */ { 2145, 11, 18, 14, 2, -16 },
/* 'G' 0x47 */ { 2170, 16, 18, 18, 1, -16 },
/* 'H' 0x48 */ { 2206, 13, 18, 17, 2, -16 },
/* 'I' 0x49 */ { 2236, 2, 18, 7, 2, -16 },
/* 'J' 0x4A */ { 2241, 9, 18, 13, 1, -16 },
/* 'K' 0x4B */ { 2262, 13, 18, 16, 2, -16 },
/* 'L' 0x4C */ { 2292, 10, 18, 14, 2, -16 },
/* 'M' 0x4D */ { 2315, 16, 18, 20, 2, -16 },
/* 'N' 0x4E */ { 2351, 13, 18, 18, 2, -16 },
/* 'O' 0x4F */ { 2381, 17, 18, 19, 1, -16 },
/* 'P' 0x50 */ { 2420, 12, 18, 16, 2, -16 },
/* 'Q' 0x51 */ { 2447, 17, 19, 19, 1, -16 },
/* 'R' 0x52 */ { 2488, 14, 18, 17, 2, -16 },
/* 'S' 0x53 */ { 2520, 14, 18, 16, 1, -16 },
/* 'T' 0x54 */ { 2552, 12, 18, 15, 1, -16 },
/* 'U' 0x55 */ { 2579, 13, 18, 17, 2, -16 },
/* 'V' 0x56 */ { 2609, 14, 18, 15, 1, -16 },
/* 'W' 0x57 */ { 2641, 22, 18, 22, 0, -16 },
/* 'X' 0x58 */ { 2691, 14, 18, 16, 1, -16 },
/* 'Y' 0x59 */ { 2723, 14, 18, 16, 1, -16 },
/* 'Z' 0x5A */ { 2755, 13, 18, 15, 1, -16 },
/* '[' 0x5B */ { 2785, 4, 23, 7, 2, -16 },
/* '\' 0x5C */ { 2797, 7, 18, 7, 0, -16 },
/* ']' 0x5D */ { 2813, 4, 23, 7, 1, -16 },
/* '^' 0x5E */ { 2825, 9, 9, 11, 1, -15 },
/* '_' 0x5F */ { 2836, 15, 1, 13, -1, 5 },
/* '`' 0x60 */ { 2838, 5, 4, 6, 1, -16 },
/* 'a' 0x61 */ { 2841, 12, 13, 13, 1, -11 },
/* 'b' 0x62 */ { 2861, 12, 18, 13, 1, -16 },
/* 'c' 0x63 */ { 2888, 10, 13, 12, 1, -11 },
/* 'd' 0x64 */ { 2905, 11, 18, 13, 1, -16 },
/* 'e' 0x65 */ { 2930, 11, 13, 13, 1, -11 },
/* 'f' 0x66 */ { 2948, 5, 18, 7, 1, -16 },
/* 'g' 0x67 */ { 2960, 11, 18, 13, 1, -11 },
/* 'h' 0x68 */ { 2985, 10, 18, 13, 1, -16 },
/* 'i' 0x69 */ { 3008, 2, 18, 5, 2, -16 },
/* 'j' 0x6A */ { 3013, 4, 23, 6, 0, -16 },
/* 'k' 0x6B */ { 3025, 10, 18, 12, 1, -16 },
/* 'l' 0x6C */ { 3048, 2, 18, 5, 1, -16 },
/* 'm' 0x6D */ { 3053, 17, 13, 19, 1, -11 },
/* 'n' 0x6E */ { 3081, 10, 13, 13, 1, -11 },
/* 'o' 0x6F */ { 3098, 11, 13, 13, 1, -11 },
/* 'p' 0x70 */ { 3116, 12, 17, 13, 1, -11 },
/* 'q' 0x71 */ { 3142, 11, 17, 13, 1, -11 },
/* 'r' 0x72 */ { 3166, 6, 13, 8, 1, -11 },
/* 's' 0x73 */ { 3176, 10, 13, 12, 1, -11 },
/* 't' 0x74 */ { 3193, 5, 16, 7, 1, -14 },
/* 'u' 0x75 */ { 3203, 10, 13, 13, 1, -11 },
/* 'v' 0x76 */ { 3220, 11, 13, 12, 0, -11 },
/* 'w' 0x77 */ { 3238, 17, 13, 17, 0, -11 },
/* 'x' 0x78 */ { 3266, 10, 13, 11, 1, -11 },
/* 'y' 0x79 */ { 3283, 11, 18, 11, 0, -11 },
/* 'z' 0x7A */ { 3308, 10, 13, 12, 1, -11 },
/* '{' 0x7B */ { 3325, 5, 23, 8, 1, -16 },
/* '|' 0x7C */ { 3340, 2, 23, 6, 2, -16 },
/* '}' 0x7D */ { 3346, 5, 23, 8, 2, -16 },
/* '~' 0x7E */ { 3361, 10, 5, 12, 1, -9 },
/* 0x7F */ { 3368, 0, 0, 0, 0, 0 },
/* 0x80 */ { 3368, 16, 22, 18, 1, -18 },
/* 0x81 */ { 3412, 11, 22, 14, 2, -22 },
/* 0x82 */ { 3443, 2, 5, 6, 2, -2 },
/* 0x83 */ { 3445, 7, 18, 9, 1, -18 },
/* 0x84 */ { 3461, 6, 5, 10, 2, -2 },
/* 0x85 */ { 3465, 12, 2, 16, 2, -2 },
/* 0x86 */ { 3468, 10, 21, 13, 2, -17 },
/* 0x87 */ { 3495, 10, 20, 13, 2, -17 },
/* 0x88 */ { 3520, 14, 17, 16, 1, -17 },
/* 0x89 */ { 3550, 23, 18, 24, 0, -18 },
/* 0x8A */ { 3602, 23, 18, 24, 0, -18 },
/* 0x8B */ { 3654, 3, 8, 6, 1, -11 },
/* 0x8C */ { 3657, 21, 18, 24, 2, -18 },
/* 0x8D */ { 3705, 12, 22, 15, 2, -22 },
/* 0x8E */ { 3738, 16, 18, 18, 1, -18 },
/* 0x8F */ { 3774, 13, 21, 17, 2, -18 },
/* 0x90 */ { 3809, 12, 22, 14, 0, -18 },
/* 0x91 */ { 3842, 2, 6, 6, 2, -18 },
/* 0x92 */ { 3844, 2, 6, 6, 2, -18 },
/* 0x93 */ { 3846, 6, 6, 10, 2, -18 },
/* 0x94 */ { 3851, 6, 6, 10, 2, -18 },
/* 0x95 */ { 3856, 6, 6, 10, 2, -11 },
/* 0x96 */ { 3861, 10, 2, 12, 1, -8 },
/* 0x97 */ { 3864, 22, 2, 24, 1, -8 },
/* 0x98 */ { 3870, 0, 0, 8, 0, 0 },
/* 0x99 */ { 3870, 22, 13, 24, 2, -18 },
/* 0x9A */ { 3906, 17, 13, 19, 1, -13 },
/* 0x9B */ { 3934, 3, 8, 6, 2, -10 },
/* 0x9C */ { 3937, 18, 13, 20, 1, -13 },
/* 0x9D */ { 3967, 9, 18, 12, 1, -18 },
/* 0x9E */ { 3988, 11, 18, 14, 1, -18 },
/* 0x9F */ { 4013, 10, 15, 13, 1, -13 },
/* 0xA0 */ { 4032, 0, 0, 7, 0, 0 },
/* 0xA1 */ { 4032, 13, 22, 15, 1, -22 },
/* 0xA2 */ { 4068, 11, 22, 11, 0, -17 },
/* 0xA3 */ { 4099, 9, 18, 13, 1, -18 },
/* 0xA4 */ { 4120, 9, 9, 13, 2, -13 },
/* 0xA5 */ { 4131, 11, 20, 14, 2, -20 },
/* 0xA6 */ { 4159, 2, 23, 6, 2, -18 },
/* 0xA7 */ { 4165, 11, 23, 13, 1, -18 },
/* 0xA8 */ { 4197, 12, 21, 15, 2, -21 },
/* 0xA9 */ { 4229, 18, 17, 19, 1, -17 },
/* 0xAA */ { 4268, 15, 18, 17, 1, -18 },
/* 0xAB */ { 4302, 8, 8, 12, 2, -11 },
/* 0xAC */ { 4310, 12, 6, 14, 1, -9 },
/* 0xAD */ { 4319, 6, 2, 8, 1, -8 },
/* 0xAE */ { 4321, 18, 17, 19, 1, -17 },
/* 0xAF */ { 4360, 7, 21, 7, 0, -21 },
/* 0xB0 */ { 4379, 7, 8, 15, 4, -17 },
/* 0xB1 */ { 4386, 12, 15, 14, 1, -15 },
/* 0xB2 */ { 4409, 2, 18, 7, 2, -18 },
/* 0xB3 */ { 4414, 2, 18, 5, 2, -18 },
/* 0xB4 */ { 4419, 8, 15, 9, 1, -15 },
/* 0xB5 */ { 4434, 12, 17, 13, 2, -13 },
/* 0xB6 */ { 4460, 11, 21, 13, 2, -18 },
/* 0xB7 */ { 4489, 2, 2, 6, 2, -8 },
/* 0xB8 */ { 4490, 11, 17, 13, 1, -17 },
/* 0xB9 */ { 4514, 19, 18, 22, 2, -18 },
/* 0xBA */ { 4557, 11, 13, 12, 0, -13 },
/* 0xBB */ { 4575, 8, 8, 12, 2, -10 },
/* 0xBC */ { 4583, 4, 23, 6, 0, -18 },
/* 0xBD */ { 4595, 14, 18, 16, 1, -18 },
/* 0xBE */ { 4627, 10, 13, 12, 1, -13 },
/* 0xBF */ { 4644, 6, 17, 6, 0, -17 },
/* 0xC0 */ { 4657, 14, 18, 16, 1, -18 },
/* 0xC1 */ { 4689, 13, 18, 16, 2, -18 },
/* 0xC2 */ { 4719, 13, 18, 16, 2, -18 },
/* 0xC3 */ { 4749, 11, 18, 14, 2, -18 },
/* 0xC4 */ { 4774, 18, 21, 19, 1, -18 },
/* 0xC5 */ { 4822, 12, 18, 15, 2, -18 },
/* 0xC6 */ { 4849, 20, 18, 22, 1, -18 },
/* 0xC7 */ { 4894, 13, 18, 16, 1, -18 },
/* 0xC8 */ { 4924, 13, 18, 18, 2, -18 },
/* 0xC9 */ { 4954, 13, 22, 18, 2, -22 },
/* 0xCA */ { 4990, 12, 18, 15, 2, -18 },
/* 0xCB */ { 5017, 14, 18, 16, 0, -18 },
/* 0xCC */ { 5049, 16, 18, 20, 2, -18 },
/* 0xCD */ { 5085, 13, 18, 17, 2, -18 },
/* 0xCE */ { 5115, 17, 18, 19, 1, -18 },
/* 0xCF */ { 5154, 13, 18, 17, 2, -18 },
/* 0xD0 */ { 5184, 12, 18, 16, 2, -18 },
/* 0xD1 */ { 5211, 15, 18, 17, 1, -18 },
/* 0xD2 */ { 5245, 12, 18, 15, 1, -18 },
/* 0xD3 */ { 5272, 13, 18, 15, 1, -18 },
/* 0xD4 */ { 5302, 18, 18, 20, 1, -18 },
/* 0xD5 */ { 5343, 14, 18, 16, 1, -18 },
/* 0xD6 */ { 5375, 15, 21, 18, 2, -18 },
/* 0xD7 */ { 5415, 12, 18, 15, 1, -18 },
/* 0xD8 */ { 5442, 16, 18, 20, 2, -18 },
/* 0xD9 */ { 5478, 18, 21, 20, 2, -18 },
/* 0xDA */ { 5526, 18, 18, 20, 1, -18 },
/* 0xDB */ { 5567, 17, 18, 21, 2, -18 },
/* 0xDC */ { 5606, 13, 18, 16, 2, -18 },
/* 0xDD */ { 5636, 15, 18, 17, 1, -18 },
/* 0xDE */ { 5670, 21, 18, 24, 2, -18 },
/* 0xDF */ { 5718, 14, 18, 16, 0, -18 },
/* 0xE0 */ { 5750, 12, 13, 13, 1, -13 },
/* 0xE1 */ { 5770, 11, 19, 13, 1, -19 },
/* 0xE2 */ { 5797, 10, 13, 12, 1, -13 },
/* 0xE3 */ { 5814, 7, 13, 9, 1, -13 },
/* 0xE4 */ { 5826, 14, 15, 14, 0, -13 },
/* 0xE5 */ { 5853, 11, 13, 13, 1, -13 },
/* 0xE6 */ { 5871, 16, 13, 18, 1, -13 },
/* 0xE7 */ { 5897, 9, 13, 12, 1, -13 },
/* 0xE8 */ { 5912, 11, 13, 13, 1, -13 },
/* 0xE9 */ { 5930, 11, 17, 13, 1, -17 },
/* 0xEA */ { 5954, 9, 13, 12, 1, -13 },
/* 0xEB */ { 5969, 10, 13, 12, 0, -13 },
/* 0xEC */ { 5986, 12, 13, 14, 1, -13 },
/* 0xED */ { 6006, 10, 13, 13, 1, -13 },
/* 0xEE */ { 6023, 11, 13, 13, 1, -13 },
/* 0xEF */ { 6041, 10, 13, 13, 1, -13 },
/* 0xF0 */ { 6058, 12, 17, 13, 1, -13 },
/* 0xF1 */ { 6084, 10, 13, 12, 1, -13 },
/* 0xF2 */ { 6101, 8, 13, 10, 1, -13 },
/* 0xF3 */ { 6114, 11, 18, 11, 0, -13 },
/* 0xF4 */ { 6139, 19, 20, 20, 1, -16 },
/* 0xF5 */ { 6187, 10, 13, 11, 1, -13 },
/* 0xF6 */ { 6204, 12, 15, 13, 1, -13 },
/* 0xF7 */ { 6227, 9, 13, 12, 1, -13 },
/* 0xF8 */ { 6242, 14, 13, 16, 1, -13 },
/* 0xF9 */ { 6265, 16, 15, 17, 1, -13 },
/* 0xFA */ { 6295, 14, 13, 15, 1, -13 },
/* 0xFB */ { 6318, 14, 13, 16, 1, -13 },
/* 0xFC */ { 6341, 10, 13, 12, 1, -13 },
/* 0xFD */ { 6358, 11, 13, 12, 1, -13 },
/* 0xFE */ { 6376, 16, 13, 18, 1, -13 },
/* 0xFF */ { 6402, 10, 13, 13, 1, -13 },
};
const GFXfont FreeSans12pt_Win1251 PROGMEM = {
(uint8_t*)FreeSans12pt_Win1251Bitmaps,
(GFXglyph*)FreeSans12pt_Win1251Glyphs,
0x01, 0xFF, 19
};
@@ -0,0 +1,527 @@
// trunk-ignore-all(clang-format)
#pragma once
/* PROPERTIES
FONT_NAME FreeSans12pt_Win1252
*/
const uint8_t FreeSans12pt_Win1252Bitmaps[] PROGMEM = {
/* 0x01 */ 0x00, 0x30, 0x00, 0x09, 0x00, 0x01, 0x20, 0x00, 0x24, 0x00, 0x04, 0x80, 0x01, 0x90, 0x00, 0x62, 0x00, 0x30, 0xFE, 0x04, 0x10, 0x5F, 0x02, 0x0B, 0x00, 0x7F, 0xE0, 0x0C, 0x1C, 0x02, 0x83, 0x81, 0x9F, 0xF0, 0x02, 0x1E, 0x00, 0x41, 0xC0, 0x0E, 0x7F, 0x81, 0x78, 0x18, 0x62, 0x00, 0xFF, 0xC0,
/* 0x02 */ 0x00, 0xFF, 0x80, 0x61, 0x13, 0xF0, 0x62, 0x60, 0x07, 0xFC, 0x00, 0x83, 0x80, 0x10, 0xF0, 0x33, 0xF6, 0x01, 0x41, 0xC0, 0x18, 0x38, 0x03, 0xFF, 0xE0, 0x47, 0x02, 0x08, 0x20, 0x61, 0xC4, 0x06, 0x17, 0x00, 0x22, 0x00, 0x02, 0x40, 0x00, 0x48, 0x00, 0x09, 0x00, 0x01, 0x20, 0x00, 0x3C, 0x00,
/* 0x03 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x04, 0x08, 0x48, 0x70, 0xE1, 0xC3, 0x87, 0x0E, 0x08, 0x10, 0x70, 0x00, 0x03, 0x80, 0x00, 0x14, 0x00, 0x00, 0xA1, 0x81, 0x8D, 0x87, 0xF0, 0x44, 0x00, 0x06, 0x30, 0x00, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x04 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x10, 0x02, 0x48, 0xE0, 0x61, 0xC1, 0xCC, 0x0E, 0x78, 0x1C, 0x70, 0x00, 0x03, 0x80, 0x00, 0x14, 0xFF, 0xFC, 0xA6, 0x00, 0xCD, 0x9F, 0xFE, 0x44, 0x71, 0xE6, 0x30, 0xFC, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x05 */ 0x00, 0x18, 0x00, 0x00, 0x40, 0x01, 0x90, 0x01, 0xF4, 0x08, 0x12, 0x23, 0xC1, 0x91, 0x2C, 0x1C, 0x8A, 0xC3, 0x64, 0x64, 0x13, 0x22, 0x41, 0x98, 0x26, 0x2C, 0xC4, 0x22, 0x60, 0x42, 0x13, 0x04, 0x30, 0x80, 0x61, 0xA4, 0x02, 0x18, 0x20, 0x03, 0x41, 0x00, 0x20, 0x08, 0x02, 0x00, 0x60, 0x40, 0x03, 0xF8,
/* 0x06 */ 0x00, 0x10, 0x00, 0x03, 0x00, 0x1C, 0x48, 0x00, 0xB4, 0x80, 0x09, 0xF9, 0xC0, 0xE0, 0xE4, 0x0C, 0x02, 0x8F, 0x80, 0x38, 0x88, 0x01, 0x0D, 0x00, 0x18, 0x30, 0x01, 0x60, 0x80, 0x13, 0x18, 0x03, 0xF2, 0xC0, 0x20, 0x26, 0x06, 0x07, 0xFF, 0xA0, 0x02, 0x39, 0x00, 0x14, 0x70, 0x01, 0xC3, 0x00, 0x18, 0x00,
/* 0x07 */
/* 0x08 */ 0x00, 0x1F, 0x80, 0x00, 0x60, 0x80, 0x01, 0x00, 0x80, 0x06, 0x00, 0x80, 0x3C, 0x01, 0x01, 0x8C, 0x02, 0x02, 0x08, 0x04, 0x04, 0x08, 0x0C, 0x38, 0x00, 0x04, 0x80, 0x00, 0x06, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x2E, 0xC0, 0x01, 0x83, 0x7E, 0x0C, 0x10, 0x37, 0xE2, 0x61, 0x00, 0x0C, 0xC6, 0x10, 0x98, 0x0C, 0x63, 0x00, 0x00, 0xC6, 0x00,
/* 0x09 */ 0x00, 0x1F, 0x80, 0x00, 0x60, 0x80, 0x01, 0x00, 0x80, 0x06, 0x00, 0x80, 0x3C, 0x01, 0x01, 0x8C, 0x02, 0x02, 0x08, 0x04, 0x04, 0x08, 0x0C, 0x38, 0x00, 0x04, 0x80, 0x00, 0x06, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x2E, 0xC0, 0x01, 0x83, 0x7E, 0x0C, 0x00, 0x37, 0xE0,
/* 0x0A */
/* 0x0B */ 0x1F, 0x07, 0xC1, 0x86, 0x41, 0x10, 0x0C, 0x04, 0x80, 0x40, 0x18, 0x00, 0x00, 0xC0, 0x00, 0x06, 0x00, 0x00, 0x30, 0x00, 0x01, 0x40, 0x00, 0x0A, 0x00, 0x00, 0x88, 0x00, 0x04, 0x40, 0x00, 0x41, 0x00, 0x02, 0x04, 0x00, 0x20, 0x20, 0x02, 0x00, 0x80, 0x20, 0x02, 0x02, 0x00, 0x08, 0x20, 0x00, 0x22, 0x00, 0x00, 0xE0, 0x00,
/* 0x0C */ 0x01, 0x00, 0x00, 0x38, 0x00, 0x04, 0xC0, 0x01, 0x08, 0x00, 0x18, 0x80, 0x1C, 0x10, 0x02, 0x07, 0x80, 0x81, 0x10, 0x1F, 0xC2, 0x02, 0x00, 0x60, 0x80, 0x1A, 0x20, 0x1C, 0x42, 0x1C, 0x08, 0xFE, 0x03, 0xA0, 0x01, 0x8C, 0x01, 0xC1, 0x43, 0xD0, 0x27, 0x81, 0xF8,
/* 0x0D */
/* 0x0E */ 0x00, 0xE0, 0x00, 0x11, 0x00, 0x01, 0x10, 0x00, 0x0B, 0x00, 0x03, 0xF8, 0x00, 0x60, 0x60, 0x09, 0x02, 0x00, 0xA0, 0x10, 0x16, 0x01, 0x01, 0x40, 0x10, 0x10, 0x01, 0x01, 0x00, 0x08, 0x10, 0x00, 0x82, 0x1F, 0x08, 0x3F, 0x90, 0x44, 0x00, 0x06, 0xBF, 0xFF, 0xAF, 0xF0, 0xFF, 0xFF, 0x0F, 0xE3, 0xFB, 0xFC,
/* 0x0F */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x40, 0x12, 0x34, 0x00, 0x69, 0x40, 0x01, 0x49, 0xE0, 0xF1, 0xCD, 0x06, 0x8E, 0x28, 0x14, 0x71, 0x40, 0xA3, 0x8B, 0xFD, 0x14, 0x50, 0x68, 0xA2, 0x81, 0x4D, 0x97, 0xFA, 0x44, 0xBF, 0xD6, 0x31, 0x02, 0xE0, 0xC8, 0x16, 0x08, 0x61, 0x08, 0x21, 0xF0, 0x80, 0xF8, 0x78, 0x00,
/* 0x10 */ 0x00, 0xF0, 0x00, 0x3A, 0x00, 0x07, 0xC0, 0x00, 0xA8, 0x00, 0x1F, 0x00, 0x02, 0xB0, 0x00, 0x52, 0x00, 0x0A, 0x40, 0x02, 0x48, 0x00, 0x49, 0x00, 0x09, 0x30, 0x01, 0x22, 0x01, 0xC4, 0x70, 0xF0, 0x85, 0xE1, 0x10, 0x88, 0x37, 0x20, 0x03, 0x9C, 0x00, 0x37, 0x00, 0x06, 0x40, 0x01, 0x86, 0x00,
/* 0x11 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x60, 0x02, 0x36, 0x00, 0x09, 0x04, 0x0C, 0x48, 0x60, 0xC1, 0xC3, 0x0F, 0x0E, 0x00, 0x08, 0x70, 0x00, 0x23, 0x80, 0x63, 0x84, 0x01, 0x9F, 0x20, 0x0C, 0xFD, 0x80, 0x27, 0xE4, 0x03, 0x3F, 0x30, 0x33, 0xE0, 0xC0, 0x00, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x12 */ 0x00, 0xC2, 0x00, 0x1C, 0x24, 0x02, 0x18, 0x60, 0x64, 0x02, 0x02, 0x40, 0x20, 0x00, 0xF2, 0x03, 0x89, 0xE0, 0x7C, 0x80, 0x0E, 0x25, 0x80, 0xE1, 0x00, 0x1A, 0x08, 0x71, 0xB0, 0xC4, 0x39, 0x84, 0xC2, 0xCC, 0x40, 0x76, 0x7C, 0x05, 0xBB, 0x80, 0x4C, 0xE0, 0x0A, 0x78, 0x00, 0x9C, 0x00, 0x0F, 0x00, 0x00,
/* 0x13 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x00, 0x00, 0x48, 0x60, 0xC1, 0xC6, 0xC9, 0x0E, 0x00, 0x00, 0x70, 0x00, 0x03, 0x80, 0x00, 0x14, 0xFF, 0xF8, 0xA6, 0x00, 0xCD, 0x9F, 0xFE, 0x44, 0x71, 0xE6, 0x30, 0xFC, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x14 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x20, 0x22, 0x33, 0x01, 0x89, 0x20, 0x02, 0x48, 0x60, 0xE1, 0xC8, 0x80, 0x8E, 0x46, 0x46, 0x72, 0x32, 0x33, 0x9F, 0x9F, 0x94, 0x78, 0x78, 0xA0, 0x00, 0x0D, 0x80, 0x00, 0x44, 0x0E, 0x06, 0x30, 0x00, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x15 */ 0x03, 0xFC, 0x20, 0x38, 0x1C, 0x81, 0x80, 0x1D, 0x08, 0x00, 0x32, 0x60, 0x00, 0x89, 0x00, 0x02, 0x18, 0x00, 0x08, 0x61, 0xC3, 0x22, 0x8D, 0x93, 0x72, 0x00, 0x00, 0x48, 0x00, 0x01, 0x20, 0x00, 0x04, 0x9F, 0xFF, 0x92, 0x60, 0x0E, 0x44, 0xFF, 0xF2, 0x11, 0xC3, 0x88, 0x21, 0xF8, 0x40, 0x40, 0x02, 0x00, 0xC0, 0x30, 0x00, 0xFF, 0x00,
/* 0x16 */ 0x03, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x01, 0xE0, 0x03, 0xF0, 0x03, 0xF0, 0x27, 0xF0, 0x6F, 0x70, 0x6E, 0x60, 0xFC, 0x60, 0xFC, 0x7E, 0xFC, 0x7E, 0xFC, 0x3F, 0xF4, 0x1F, 0xF4, 0x1F, 0xF0, 0x0E, 0x70, 0x0E, 0x30, 0x1C, 0x38, 0x38, 0x0F, 0xF0,
/* 0x17 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x00, 0x00, 0x48, 0x00, 0x21, 0xC0, 0x02, 0x8E, 0x20, 0xF4, 0x70, 0x84, 0x11, 0x82, 0x40, 0x84, 0x01, 0x03, 0x20, 0x0F, 0x85, 0x80, 0x03, 0x04, 0x00, 0x04, 0x30, 0x78, 0x10, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x18 */ 0x00, 0xFC, 0x00, 0x02, 0x06, 0x00, 0x08, 0x24, 0x00, 0x21, 0xA4, 0x00, 0x4C, 0x48, 0x00, 0xA0, 0x50, 0x01, 0x92, 0x60, 0x03, 0x24, 0xC0, 0x06, 0x01, 0x81, 0x28, 0x03, 0x49, 0x6C, 0xC4, 0xAD, 0xD8, 0x16, 0xA4, 0xCC, 0xC4, 0x44, 0x86, 0x13, 0x05, 0x00, 0x28, 0x0A, 0x00, 0x50, 0x14, 0x00, 0x90, 0x48, 0x01, 0x20, 0x90, 0x02, 0x41, 0x20, 0x00, 0x00,
/* 0x19 */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x00, 0x02, 0x30, 0x00, 0x09, 0x00, 0x00, 0x49, 0xC3, 0x81, 0xC0, 0x00, 0x0E, 0x78, 0xF0, 0x77, 0xEF, 0xC3, 0xA7, 0x4E, 0x15, 0x0A, 0x10, 0xA7, 0x8F, 0x0D, 0x80, 0x00, 0x44, 0x00, 0x06, 0x33, 0xF0, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x1A */ 0xFF, 0xFF, 0x00, 0x06, 0x00, 0x0C, 0x3E, 0x18, 0x82, 0x32, 0x02, 0x64, 0x04, 0xC8, 0x09, 0x80, 0x23, 0x00, 0x86, 0x02, 0x0C, 0x08, 0x18, 0x10, 0x30, 0x00, 0x60, 0x00, 0xC0, 0x81, 0x80, 0x03, 0x00, 0x07, 0xFF, 0xF8,
/* 0x1B */ 0x00, 0xFE, 0x00, 0x03, 0x81, 0x80, 0x04, 0x00, 0x60, 0x08, 0x00, 0x30, 0x10, 0x00, 0x10, 0x30, 0x07, 0x88, 0x23, 0xC8, 0x08, 0x22, 0x00, 0x04, 0x60, 0x00, 0x44, 0x60, 0x00, 0x84, 0x63, 0x03, 0x04, 0x61, 0xFC, 0x04, 0x6B, 0x00, 0x9E, 0xA5, 0x01, 0x6A, 0xD5, 0x01, 0x43, 0xA8, 0x81, 0x05, 0xD0, 0x82, 0x0A, 0xA0, 0x82, 0x05, 0xC0, 0x82, 0x02, 0x61, 0xFF, 0x0C, 0x1E, 0x00, 0xF0,
/* 0x1C */ 0x01, 0xFC, 0x00, 0x38, 0x18, 0x02, 0x00, 0x30, 0x20, 0x00, 0xC2, 0x30, 0x02, 0x32, 0x00, 0x09, 0x00, 0x00, 0x48, 0x20, 0x61, 0xC3, 0x84, 0x0E, 0x1C, 0x78, 0x70, 0x40, 0x03, 0x80, 0x00, 0x14, 0x00, 0x00, 0xA0, 0x03, 0x0D, 0x83, 0xF0, 0x44, 0x00, 0x06, 0x30, 0x00, 0x60, 0xC0, 0x06, 0x03, 0x80, 0x60, 0x07, 0xFC, 0x00,
/* 0x1D */ 0x01, 0xFE, 0x00, 0x3A, 0x1C, 0x03, 0x00, 0x30, 0x23, 0x1E, 0xC3, 0x38, 0x03, 0x10, 0xC3, 0x09, 0x00, 0x18, 0x68, 0x00, 0xC1, 0x40, 0x00, 0x0A, 0x07, 0x80, 0x50, 0x46, 0x02, 0x80, 0x00, 0x1A, 0x1E, 0x00, 0xCB, 0x10, 0x0D, 0x03, 0x00, 0x48, 0x60, 0x06, 0x40, 0x00, 0x22, 0x0C, 0x02, 0x10, 0x60, 0x60, 0x43, 0xFC, 0x01, 0xE0, 0x00, 0x00,
/* 0x1E */ 0x01, 0xF0, 0x00, 0xEA, 0xC0, 0x31, 0x5F, 0x04, 0x5F, 0x88, 0x80, 0xA0, 0x48, 0x0E, 0x02, 0x8F, 0x40, 0x3C, 0x10, 0x21, 0x66, 0x87, 0x15, 0x98, 0x71, 0x41, 0x02, 0x14, 0x00, 0x01, 0x40, 0x00, 0x14, 0x00, 0x01, 0x21, 0xFE, 0x12, 0x00, 0x02, 0x10, 0x00, 0x60, 0x80, 0x0C, 0x06, 0x01, 0x80, 0x3F, 0xE0,
/* 0x1F */ 0x0E, 0x00, 0x13, 0x00, 0x23, 0x00, 0xF3, 0x01, 0x31, 0x01, 0x11, 0x03, 0xD3, 0x06, 0xF2, 0x30, 0x34, 0xC7, 0x25, 0x33, 0x2B, 0xC2, 0x57, 0x04, 0x3A, 0x08, 0x72, 0x30, 0xA3, 0xC3, 0x40, 0x04, 0x40, 0x18, 0x40, 0x60, 0x7F, 0x80,
/* ' ' 0x20 */
/* '!' 0x21 */ 0xFF, 0xFF, 0xFF, 0xF0, 0xF0,
/* '"' 0x22 */ 0xCF, 0x3C, 0xF3, 0x8A, 0x20,
/* '#' 0x23 */ 0x06, 0x30, 0x31, 0x03, 0x18, 0x18, 0xC7, 0xFF, 0xBF, 0xFC, 0x31, 0x01, 0x18, 0x18, 0xC7, 0xFF, 0xBF, 0xFC, 0x31, 0x01, 0x18, 0x18, 0xC0, 0xC6, 0x06, 0x30,
/* '$' 0x24 */ 0x04, 0x03, 0xE1, 0xFF, 0x72, 0x7C, 0x47, 0x88, 0xF1, 0x07, 0xA0, 0x7E, 0x03, 0xF0, 0x17, 0x02, 0x7C, 0x47, 0x88, 0xF1, 0x1B, 0x26, 0x7F, 0xC3, 0xE0, 0x10, 0x02, 0x00,
/* '%' 0x25 */ 0x00, 0x06, 0x03, 0xC0, 0x40, 0x7E, 0x0C, 0x0E, 0x70, 0x80, 0xC3, 0x18, 0x0C, 0x31, 0x00, 0xE7, 0x30, 0x07, 0xE6, 0x00, 0x3C, 0x40, 0x00, 0x0C, 0x7C, 0x00, 0x8F, 0xE0, 0x19, 0xC7, 0x01, 0x18, 0x30, 0x31, 0x83, 0x02, 0x1C, 0x70, 0x40, 0xFE, 0x04, 0x07, 0xC0,
/* '&' 0x26 */ 0x0F, 0x00, 0x7E, 0x03, 0x9C, 0x0C, 0x30, 0x30, 0xC0, 0xE7, 0x01, 0xF8, 0x03, 0x80, 0x3E, 0x01, 0xCC, 0x6E, 0x39, 0xB0, 0x7C, 0xC0, 0xF3, 0x03, 0xCE, 0x1F, 0x9F, 0xE6, 0x3E, 0x1C,
/* ''' 0x27 */ 0xFF, 0xA0,
/* '(' 0x28 */ 0x08, 0x8C, 0x46, 0x31, 0x98, 0xC6, 0x31, 0x8C, 0x63, 0x08, 0x63, 0x08, 0x61, 0x0C, 0x20,
/* ')' 0x29 */ 0x82, 0x18, 0xC3, 0x18, 0xC3, 0x18, 0xC6, 0x31, 0x8C, 0x62, 0x31, 0x88, 0xC4, 0x62, 0x00,
/* '*' 0x2A */ 0x10, 0x23, 0x5B, 0xE3, 0x8D, 0x91, 0x00,
/* '+' 0x2B */ 0x0C, 0x03, 0x00, 0xC0, 0x30, 0xFF, 0xFF, 0xF0, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0,
/* ',' 0x2C */ 0xF5, 0x60,
/* '-' 0x2D */ 0xFF, 0xF0,
/* '.' 0x2E */ 0xF0,
/* '/' 0x2F */ 0x02, 0x0C, 0x10, 0x20, 0xC1, 0x02, 0x0C, 0x10, 0x20, 0xC1, 0x02, 0x0C, 0x10, 0x20, 0xC1, 0x00,
/* '0' 0x30 */ 0x1F, 0x07, 0xF1, 0xC7, 0x30, 0x6C, 0x0F, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3E, 0x0E, 0xC1, 0x9C, 0x71, 0xFC, 0x1F, 0x00,
/* '1' 0x31 */ 0x08, 0xCF, 0xFF, 0x8C, 0x63, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x18,
/* '2' 0x32 */ 0x1F, 0x0F, 0xF9, 0x87, 0x60, 0x7C, 0x06, 0x00, 0xC0, 0x18, 0x07, 0x01, 0xC0, 0xF0, 0x78, 0x1C, 0x06, 0x00, 0xC0, 0x30, 0x07, 0xFF, 0xFF, 0xE0,
/* '3' 0x33 */ 0x3F, 0x0F, 0xF3, 0x87, 0x60, 0x6C, 0x0C, 0x01, 0x80, 0x60, 0x78, 0x0F, 0x80, 0x18, 0x01, 0x80, 0x3C, 0x07, 0x80, 0xD8, 0x73, 0xFC, 0x3F, 0x00,
/* '4' 0x34 */ 0x01, 0x80, 0x70, 0x0E, 0x03, 0xC0, 0xD8, 0x1B, 0x06, 0x61, 0x8C, 0x21, 0x8C, 0x33, 0x06, 0x7F, 0xFF, 0xFE, 0x03, 0x00, 0x60, 0x0C, 0x01, 0x80,
/* '5' 0x35 */ 0x3F, 0xCF, 0xF9, 0x80, 0x30, 0x06, 0x00, 0xDE, 0x1F, 0xE7, 0x0E, 0x00, 0xE0, 0x0C, 0x01, 0x80, 0x30, 0x07, 0x81, 0xB8, 0x73, 0xFC, 0x1F, 0x00,
/* '6' 0x36 */ 0x0F, 0x07, 0xF9, 0xC3, 0x30, 0x74, 0x01, 0x80, 0x33, 0xC7, 0xFE, 0xF1, 0xDC, 0x1F, 0x01, 0xE0, 0x3C, 0x06, 0xC1, 0xDC, 0x71, 0xFC, 0x1F, 0x00,
/* '7' 0x37 */ 0xFF, 0xFF, 0xFC, 0x01, 0x00, 0x60, 0x18, 0x02, 0x00, 0xC0, 0x30, 0x06, 0x01, 0x80, 0x30, 0x04, 0x01, 0x80, 0x30, 0x06, 0x01, 0x80, 0x30, 0x00,
/* '8' 0x38 */ 0x1F, 0x07, 0xF1, 0xC7, 0x30, 0x66, 0x0C, 0xC1, 0x8C, 0x61, 0xF8, 0x3F, 0x8E, 0x3B, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xD8, 0x31, 0xFC, 0x1F, 0x00,
/* '9' 0x39 */ 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x6C, 0x07, 0x80, 0xF0, 0x1E, 0x07, 0x61, 0xEF, 0xFC, 0x79, 0x80, 0x30, 0x05, 0xC1, 0x98, 0x73, 0xFC, 0x1E, 0x00,
/* ':' 0x3A */ 0xF0, 0x00, 0x03, 0xC0,
/* ';' 0x3B */ 0xF0, 0x00, 0x0F, 0x56,
/* '<' 0x3C */ 0x00, 0x70, 0x1E, 0x0F, 0x83, 0xC0, 0xF0, 0x0E, 0x00, 0x7C, 0x00, 0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x10,
/* '=' 0x3D */ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF,
/* '>' 0x3E */ 0xE0, 0x07, 0x80, 0x1F, 0x00, 0x7C, 0x00, 0xF0, 0x07, 0x01, 0xE0, 0xF0, 0x3C, 0x0F, 0x00, 0x80, 0x00,
/* '?' 0x3F */ 0x3F, 0x1F, 0xEE, 0x1F, 0x03, 0xC0, 0xC0, 0x30, 0x0C, 0x06, 0x03, 0x81, 0xC0, 0xE0, 0x30, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x0C, 0x03, 0x00,
/* '@' 0x40 */ 0x00, 0xFE, 0x00, 0x0F, 0xFE, 0x00, 0xF0, 0x3E, 0x07, 0x00, 0x3C, 0x38, 0x00, 0x38, 0xC1, 0xE0, 0x66, 0x0F, 0xD9, 0xD8, 0x61, 0xC3, 0xC3, 0x07, 0x0F, 0x1C, 0x1C, 0x3C, 0x60, 0x60, 0xF1, 0x81, 0x83, 0xC6, 0x06, 0x1B, 0x18, 0x38, 0xEE, 0x71, 0xE7, 0x18, 0xFD, 0xF8, 0x71, 0xE7, 0xC0, 0xE0, 0x00, 0x01, 0xE0, 0x00, 0x01, 0xFF, 0xC0, 0x01, 0xFC, 0x00,
/* 'A' 0x41 */ 0x07, 0x80, 0x1E, 0x00, 0x78, 0x03, 0xF0, 0x0C, 0xC0, 0x33, 0x01, 0xCE, 0x06, 0x18, 0x18, 0x60, 0xE1, 0xC3, 0x03, 0x0F, 0xFC, 0x7F, 0xF9, 0x80, 0x66, 0x01, 0xB8, 0x07, 0xC0, 0x0F, 0x00, 0x30,
/* 'B' 0x42 */ 0xFF, 0xC7, 0xFF, 0x30, 0x1D, 0x80, 0x6C, 0x03, 0x60, 0x1B, 0x00, 0xD8, 0x0C, 0xFF, 0xC7, 0xFF, 0x30, 0x0D, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x06, 0xFF, 0xF7, 0xFE, 0x00,
/* 'C' 0x43 */ 0x07, 0xE0, 0x3F, 0xF0, 0xE0, 0x73, 0x80, 0x76, 0x00, 0x6C, 0x00, 0x30, 0x00, 0x60, 0x00, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x06, 0x00, 0x0E, 0x00, 0x6C, 0x00, 0xDC, 0x03, 0x1E, 0x0E, 0x1F, 0xF8, 0x0F, 0xC0,
/* 'D' 0x44 */ 0xFF, 0xC3, 0xFF, 0x8C, 0x07, 0x30, 0x0E, 0xC0, 0x1B, 0x00, 0x7C, 0x00, 0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x3C, 0x00, 0xF0, 0x03, 0xC0, 0x1F, 0x00, 0x6C, 0x03, 0xB0, 0x1C, 0xFF, 0xE3, 0xFE, 0x00,
/* 'E' 0x45 */ 0xFF, 0xFF, 0xFF, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xEF, 0xFE, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xFF, 0xFF,
/* 'F' 0x46 */ 0xFF, 0xFF, 0xFF, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xFF, 0xDF, 0xFB, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x18, 0x00,
/* 'G' 0x47 */ 0x07, 0xF0, 0x1F, 0xFC, 0x3C, 0x1E, 0x70, 0x07, 0x60, 0x03, 0xE0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x7F, 0xC0, 0x7F, 0xC0, 0x03, 0xC0, 0x03, 0x60, 0x03, 0x60, 0x07, 0x30, 0x0F, 0x3C, 0x1F, 0x1F, 0xFB, 0x07, 0xE1,
/* 'H' 0x48 */ 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xFF, 0xFF, 0xFF, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xC0,
/* 'I' 0x49 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xF0,
/* 'J' 0x4A */ 0x01, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x80, 0xC0, 0x60, 0x3C, 0x1E, 0x0F, 0x07, 0xC7, 0x7F, 0x1F, 0x00,
/* 'K' 0x4B */ 0xC0, 0x3E, 0x03, 0xB0, 0x39, 0x83, 0x8C, 0x38, 0x63, 0x83, 0x38, 0x19, 0xC0, 0xDE, 0x07, 0xB8, 0x38, 0xE1, 0x83, 0x0C, 0x1C, 0x60, 0x73, 0x01, 0x98, 0x0E, 0xC0, 0x3E, 0x00, 0xC0,
/* 'L' 0x4C */ 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xFF, 0xFF, 0xF0,
/* 'M' 0x4D */ 0xE0, 0x07, 0xE0, 0x07, 0xF0, 0x0F, 0xF0, 0x0F, 0xD0, 0x0F, 0xD8, 0x1B, 0xD8, 0x1B, 0xD8, 0x1B, 0xCC, 0x33, 0xCC, 0x33, 0xCC, 0x33, 0xC6, 0x63, 0xC6, 0x63, 0xC6, 0x63, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC1, 0x83,
/* 'N' 0x4E */ 0xE0, 0x1F, 0x00, 0xFC, 0x07, 0xE0, 0x3D, 0x81, 0xEE, 0x0F, 0x30, 0x79, 0xC3, 0xC6, 0x1E, 0x18, 0xF0, 0xE7, 0x83, 0x3C, 0x1D, 0xE0, 0x6F, 0x01, 0xF8, 0x0F, 0xC0, 0x3E, 0x01, 0xC0,
/* 'O' 0x4F */ 0x07, 0xF0, 0x0F, 0xFE, 0x0F, 0x07, 0x86, 0x00, 0xC6, 0x00, 0x33, 0x00, 0x1B, 0x00, 0x07, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x36, 0x00, 0x33, 0x00, 0x18, 0xC0, 0x18, 0x78, 0x3C, 0x1F, 0xFC, 0x03, 0xF8, 0x00,
/* 'P' 0x50 */ 0xFF, 0x8F, 0xFE, 0xC0, 0x6C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x06, 0xFF, 0xEF, 0xFC, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00,
/* 'Q' 0x51 */ 0x07, 0xF0, 0x0F, 0xFE, 0x0F, 0x07, 0x86, 0x00, 0xC6, 0x00, 0x33, 0x00, 0x1B, 0x00, 0x07, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x36, 0x00, 0x33, 0x01, 0x98, 0xC0, 0xFC, 0x78, 0x3C, 0x1F, 0xFF, 0x03, 0xF9, 0x80, 0x00, 0x40,
/* 'R' 0x52 */ 0xFF, 0xE3, 0xFF, 0xCC, 0x03, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x6C, 0x01, 0xB0, 0x0C, 0xFF, 0xE3, 0xFF, 0xCC, 0x03, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x6C, 0x01, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x70,
/* 'S' 0x53 */ 0x0F, 0xE0, 0x7F, 0xC3, 0x83, 0x98, 0x07, 0x60, 0x0D, 0x80, 0x07, 0x00, 0x1E, 0x00, 0x3F, 0x80, 0x3F, 0xC0, 0x0F, 0x80, 0x07, 0xC0, 0x0F, 0x00, 0x3E, 0x00, 0xDE, 0x0E, 0x3F, 0xF0, 0x3F, 0x80,
/* 'T' 0x54 */ 0xFF, 0xFF, 0xFF, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60,
/* 'U' 0x55 */ 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x80, 0xEE, 0x0E, 0x3F, 0xE0, 0xFC, 0x00,
/* 'V' 0x56 */ 0xC0, 0x0F, 0x00, 0x7E, 0x01, 0x98, 0x06, 0x60, 0x39, 0xC0, 0xC3, 0x03, 0x0C, 0x1C, 0x38, 0x60, 0x61, 0x81, 0x8E, 0x07, 0x30, 0x0C, 0xC0, 0x37, 0x00, 0xF8, 0x01, 0xE0, 0x07, 0x80, 0x1C, 0x00,
/* 'W' 0x57 */ 0xE0, 0x30, 0x1D, 0x80, 0xE0, 0x76, 0x07, 0x81, 0xDC, 0x1E, 0x06, 0x70, 0x7C, 0x18, 0xC1, 0xB0, 0xE3, 0x0C, 0xC3, 0x8C, 0x33, 0x0C, 0x38, 0xC6, 0x30, 0x67, 0x18, 0xC1, 0x98, 0x67, 0x06, 0x61, 0xD8, 0x1D, 0x83, 0x60, 0x3C, 0x0D, 0x80, 0xF0, 0x3E, 0x03, 0xC0, 0x70, 0x0F, 0x01, 0xC0, 0x18, 0x07, 0x00,
/* 'X' 0x58 */ 0xE0, 0x1D, 0x80, 0xE7, 0x03, 0x0E, 0x1C, 0x18, 0x60, 0x73, 0x00, 0xFC, 0x01, 0xE0, 0x07, 0x00, 0x1E, 0x00, 0xF8, 0x03, 0x30, 0x1C, 0xE0, 0xE1, 0x83, 0x07, 0x1C, 0x0E, 0xE0, 0x1B, 0x00, 0x70,
/* 'Y' 0x59 */ 0xC0, 0x0F, 0x80, 0x76, 0x01, 0x9C, 0x0C, 0x38, 0x70, 0x61, 0x81, 0xCE, 0x03, 0x30, 0x0F, 0x80, 0x1E, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00,
/* 'Z' 0x5A */ 0xFF, 0xFF, 0xFF, 0xC0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0x60, 0x07, 0x00, 0x70, 0x07, 0x00, 0x30, 0x03, 0x80, 0x38, 0x03, 0x80, 0x18, 0x01, 0xC0, 0x1C, 0x00, 0xFF, 0xFF, 0xFF, 0xC0,
/* '[' 0x5B */ 0xFF, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCF, 0xF0,
/* '\' 0x5C */ 0x81, 0x81, 0x02, 0x06, 0x04, 0x08, 0x18, 0x10, 0x20, 0x60, 0x40, 0x81, 0x81, 0x02, 0x06, 0x04,
/* ']' 0x5D */ 0xFF, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0xF0,
/* '^' 0x5E */ 0x0C, 0x0E, 0x05, 0x86, 0xC3, 0x21, 0x19, 0x8C, 0x83, 0xC1, 0x80,
/* '_' 0x5F */ 0xFF, 0xFE,
/* '`' 0x60 */ 0xE3, 0x8C, 0x30,
/* 'a' 0x61 */ 0x3F, 0x07, 0xF8, 0xE1, 0xCC, 0x0C, 0x00, 0xC0, 0x1C, 0x3F, 0xCF, 0x8C, 0xC0, 0xCC, 0x0C, 0xE3, 0xC7, 0xEF, 0x3C, 0x70,
/* 'b' 0x62 */ 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0xF8, 0xDF, 0xCF, 0x0E, 0xE0, 0x7C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xE0, 0x6F, 0x0E, 0xDF, 0xCC, 0xF8,
/* 'c' 0x63 */ 0x1F, 0x0F, 0xE6, 0x1F, 0x83, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x38, 0x37, 0x1C, 0xFE, 0x1F, 0x00,
/* 'd' 0x64 */ 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x3C, 0xCF, 0xFB, 0x8F, 0xE0, 0xF8, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF8, 0x3B, 0x8F, 0x3F, 0x63, 0xCC,
/* 'e' 0x65 */ 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x3C, 0x07, 0xFF, 0xFF, 0xFE, 0x00, 0xC0, 0x1C, 0x0D, 0xC3, 0x1F, 0xC1, 0xF0,
/* 'f' 0x66 */ 0x3B, 0xD8, 0xC6, 0x7F, 0xEC, 0x63, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x00,
/* 'g' 0x67 */ 0x1E, 0x67, 0xFD, 0xC7, 0xF0, 0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x7C, 0x1D, 0xC7, 0x9F, 0xB1, 0xE6, 0x00, 0xC0, 0x3E, 0x0E, 0x7F, 0xC7, 0xE0,
/* 'h' 0x68 */ 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x33, 0xCD, 0xFB, 0xC7, 0xE0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x30,
/* 'i' 0x69 */ 0xF0, 0x3F, 0xFF, 0xFF, 0xF0,
/* 'j' 0x6A */ 0x33, 0x00, 0x03, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0xE0,
/* 'k' 0x6B */ 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x6C, 0x33, 0x18, 0xCC, 0x37, 0x0F, 0xC3, 0xB8, 0xC6, 0x31, 0xCC, 0x3B, 0x06, 0xC1, 0xF0, 0x30,
/* 'l' 0x6C */ 0xFF, 0xFF, 0xFF, 0xFF, 0xF0,
/* 'm' 0x6D */ 0xCF, 0x1F, 0x6F, 0xDF, 0xFC, 0x78, 0xFC, 0x18, 0x3C, 0x0C, 0x1E, 0x06, 0x0F, 0x03, 0x07, 0x81, 0x83, 0xC0, 0xC1, 0xE0, 0x60, 0xF0, 0x30, 0x78, 0x18, 0x3C, 0x0C, 0x18,
/* 'n' 0x6E */ 0xCF, 0x37, 0xEF, 0x1F, 0x83, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xC0,
/* 'o' 0x6F */ 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x7C, 0x1D, 0xC7, 0x1F, 0xC1, 0xF0,
/* 'p' 0x70 */ 0xCF, 0x8D, 0xFC, 0xF0, 0xEE, 0x06, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3E, 0x06, 0xF0, 0xEF, 0xFC, 0xCF, 0x8C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x00,
/* 'q' 0x71 */ 0x1E, 0x67, 0xFD, 0xC7, 0xF0, 0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x7C, 0x1D, 0xC7, 0x9F, 0xF1, 0xE6, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x60,
/* 'r' 0x72 */ 0xCF, 0x7F, 0x38, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC0,
/* 's' 0x73 */ 0x3E, 0x1F, 0xEE, 0x1B, 0x00, 0xC0, 0x3C, 0x07, 0xF0, 0x3F, 0x01, 0xF0, 0x3E, 0x1D, 0xFE, 0x3F, 0x00,
/* 't' 0x74 */ 0x63, 0x19, 0xFF, 0xB1, 0x8C, 0x63, 0x18, 0xC6, 0x31, 0xE7,
/* 'u' 0x75 */ 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x7E, 0x3D, 0xFB, 0x3C, 0xC0,
/* 'v' 0x76 */ 0xE0, 0x6C, 0x0D, 0x81, 0xB8, 0x63, 0x0C, 0x61, 0x8E, 0x60, 0xCC, 0x19, 0x83, 0xE0, 0x3C, 0x07, 0x00, 0xE0,
/* 'w' 0x77 */ 0xC1, 0xC1, 0xB0, 0xE1, 0xD8, 0x70, 0xCC, 0x2C, 0x66, 0x36, 0x31, 0x9B, 0x18, 0xCD, 0x98, 0x64, 0x6C, 0x16, 0x36, 0x0F, 0x1A, 0x07, 0x8F, 0x03, 0x83, 0x80, 0xC1, 0xC0,
/* 'x' 0x78 */ 0xC1, 0xF8, 0x66, 0x30, 0xCC, 0x3E, 0x07, 0x00, 0xC0, 0x78, 0x36, 0x0C, 0xC6, 0x3B, 0x06, 0xC0, 0xC0,
/* 'y' 0x79 */ 0xE0, 0x6C, 0x0D, 0x83, 0x38, 0x63, 0x0C, 0x63, 0x0C, 0x60, 0xCC, 0x1B, 0x03, 0x60, 0x3C, 0x07, 0x00, 0xE0, 0x18, 0x03, 0x00, 0xE0, 0x78, 0x0E, 0x00,
/* 'z' 0x7A */ 0xFF, 0xFF, 0xF0, 0x18, 0x0C, 0x07, 0x03, 0x81, 0xC0, 0x60, 0x30, 0x18, 0x0E, 0x03, 0xFF, 0xFF, 0xC0,
/* '{' 0x7B */ 0x19, 0xCC, 0x63, 0x18, 0xC6, 0x31, 0x99, 0x86, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x1C, 0x60,
/* '|' 0x7C */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC,
/* '}' 0x7D */ 0xC7, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x0C, 0x33, 0x31, 0x8C, 0x63, 0x18, 0xC6, 0x73, 0x00,
/* '~' 0x7E */ 0x70, 0x3E, 0x09, 0xE4, 0x1F, 0x03, 0x80,
/* 0x7F */
/* 0x80 */ 0x01, 0xF0, 0x1F, 0xF0, 0xE0, 0xC7, 0x00, 0x18, 0x00, 0xC0, 0x07, 0xFF, 0x3F, 0xFC, 0x30, 0x01, 0xFF, 0x8F, 0xFC, 0x0C, 0x00, 0x18, 0x00, 0x70, 0x00, 0xE0, 0x81, 0xFE, 0x03, 0xF0,
/* 0x81 */
/* 0x82 */ 0xF5, 0x80,
/* 0x83 */ 0x1C, 0xF3, 0x0C, 0x31, 0xF7, 0xCC, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x33, 0xCE, 0x00,
/* 0x84 */ 0xCF, 0x34, 0x51, 0x88,
/* 0x85 */ 0xC6, 0x3C, 0x63,
/* 0x86 */ 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x3F, 0xFF, 0xFC, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x00,
/* 0x87 */ 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x3F, 0xFF, 0xFC, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x0F, 0xFF, 0xFF, 0x0C, 0x03, 0x00, 0xC0, 0x30,
/* 0x88 */ 0x38, 0xD9, 0xB6, 0x30,
/* 0x89 */ 0x38, 0x18, 0x00, 0xF8, 0x30, 0x03, 0x18, 0xC0, 0x04, 0x11, 0x80, 0x0C, 0x66, 0x00, 0x0F, 0x8C, 0x00, 0x0E, 0x30, 0x00, 0x00, 0x40, 0x00, 0x01, 0x80, 0x00, 0x06, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x31, 0xC0, 0xE0, 0x67, 0xC3, 0xC1, 0x98, 0xCC, 0xC3, 0x20, 0x90, 0x8C, 0x63, 0x33, 0x10, 0x7C, 0x3C, 0x60, 0x70, 0x38,
/* 0x8A */ 0x0C, 0x40, 0x1F, 0x00, 0x38, 0x03, 0xF8, 0x1F, 0xF0, 0xE0, 0xE6, 0x01, 0xD8, 0x03, 0x60, 0x01, 0xC0, 0x07, 0x80, 0x0F, 0xE0, 0x0F, 0xF0, 0x03, 0xE0, 0x01, 0xF0, 0x03, 0xC0, 0x0F, 0x80, 0x37, 0x83, 0x8F, 0xFC, 0x0F, 0xE0,
/* 0x8B */ 0x2F, 0x49, 0x99,
/* 0x8C */ 0x07, 0xCF, 0xFC, 0x7F, 0xFF, 0xF3, 0x83, 0xC0, 0x18, 0x07, 0x00, 0x60, 0x0C, 0x03, 0x00, 0x30, 0x0C, 0x00, 0xC0, 0x30, 0x03, 0x00, 0xC0, 0x0F, 0xFF, 0x00, 0x3F, 0xFC, 0x00, 0xC0, 0x30, 0x03, 0x00, 0xC0, 0x0C, 0x01, 0x80, 0x30, 0x07, 0x01, 0xC0, 0x0E, 0x0F, 0x00, 0x1F, 0xEF, 0xFC, 0x1F, 0x3F, 0xF0,
/* 0x8D */
/* 0x8E */ 0x0C, 0xC0, 0x3C, 0x00, 0xE1, 0xFF, 0xFF, 0xFF, 0x80, 0x1C, 0x01, 0xC0, 0x1C, 0x00, 0xC0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0x60, 0x07, 0x00, 0x70, 0x07, 0x00, 0x30, 0x03, 0x80, 0x38, 0x01, 0xFF, 0xFF, 0xFF, 0x80,
/* 0x8F */
/* 0x90 */
/* 0x91 */ 0x6A, 0xF0,
/* 0x92 */ 0xF5, 0x60,
/* 0x93 */ 0x4E, 0x28, 0xA2, 0xCF, 0x30,
/* 0x94 */ 0xCF, 0x34, 0x51, 0x4E, 0x20,
/* 0x95 */ 0x7B, 0xFF, 0xFF, 0xFD, 0xE0,
/* 0x96 */ 0xFF, 0xFF, 0xF0,
/* 0x97 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0,
/* 0x98 */ 0x63, 0xFE, 0x70,
/* 0x99 */ 0xFF, 0x70, 0x1F, 0xFD, 0xC0, 0x71, 0x87, 0x83, 0xC6, 0x1E, 0x0F, 0x18, 0x68, 0x3C, 0x61, 0xB1, 0xB1, 0x86, 0xC6, 0xC6, 0x19, 0x1B, 0x18, 0x66, 0xCC, 0x61, 0x9B, 0x31, 0x86, 0x3C, 0xC6, 0x18, 0xE3, 0x18, 0x63, 0x8C,
/* 0x9A */ 0x63, 0x0D, 0x83, 0x60, 0x70, 0x00, 0x0F, 0x87, 0xFB, 0x86, 0xC0, 0x30, 0x0F, 0x01, 0xFC, 0x0F, 0xC0, 0x7C, 0x0F, 0x87, 0x7F, 0x8F, 0xC0,
/* 0x9B */ 0x99, 0x92, 0xF4,
/* 0x9C */ 0x1F, 0x0F, 0x83, 0xF9, 0xFC, 0x71, 0xF8, 0x6E, 0x0F, 0x03, 0xC0, 0x60, 0x3C, 0x07, 0xFF, 0xC0, 0x7F, 0xFC, 0x06, 0x00, 0xC0, 0x60, 0x0E, 0x0F, 0x03, 0x71, 0xF8, 0x63, 0xF9, 0xFC, 0x1F, 0x0F, 0x80,
/* 0x9D */
/* 0x9E */ 0x63, 0x0C, 0x83, 0x60, 0x70, 0x00, 0x3F, 0xFF, 0xFC, 0x06, 0x03, 0x01, 0xC0, 0xE0, 0x70, 0x18, 0x0C, 0x06, 0x03, 0x80, 0xFF, 0xFF, 0xF0,
/* 0x9F */ 0x0C, 0xC0, 0x33, 0x00, 0x00, 0x30, 0x03, 0xE0, 0x1D, 0x80, 0x67, 0x03, 0x0E, 0x1C, 0x18, 0x60, 0x73, 0x80, 0xCC, 0x03, 0xE0, 0x07, 0x80, 0x0C, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00,
/* 0xA0 */
/* 0xA1 */ 0xF0, 0xBF, 0xFF, 0xFF, 0xF0,
/* 0xA2 */ 0x04, 0x00, 0x80, 0x7C, 0x1F, 0xE7, 0x4C, 0xC8, 0xF1, 0x1E, 0x20, 0xC4, 0x18, 0x83, 0x10, 0x72, 0x37, 0x4E, 0x7F, 0x87, 0xC0, 0x20, 0x04, 0x00,
/* 0xA3 */ 0x0F, 0xC1, 0xFE, 0x38, 0x76, 0x03, 0x60, 0x36, 0x00, 0x70, 0x03, 0x80, 0xFF, 0x0F, 0xF0, 0x1C, 0x00, 0xC0, 0x0C, 0x01, 0x80, 0x10, 0x02, 0xF1, 0x7F, 0xF6, 0x1F,
/* 0xA4 */ 0xDD, 0xFF, 0xD8, 0xD8, 0x3C, 0x1E, 0x0F, 0x8D, 0xFF, 0xDD, 0x80,
/* 0xA5 */ 0xC0, 0x3E, 0x06, 0x60, 0x63, 0x0C, 0x30, 0xC1, 0x98, 0x19, 0x80, 0xF0, 0x0F, 0x07, 0xFE, 0x06, 0x00, 0x60, 0x7F, 0xE0, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00,
/* 0xA6 */ 0xFF, 0xFF, 0xF0, 0x3F, 0xFF, 0xFC,
/* 0xA7 */ 0x0F, 0x03, 0xF0, 0xE7, 0x18, 0x63, 0x0C, 0x70, 0x07, 0x03, 0xF8, 0xC3, 0x98, 0x3B, 0x03, 0xF0, 0x37, 0x06, 0x78, 0xC7, 0xB0, 0x7C, 0x03, 0x80, 0x39, 0x83, 0x30, 0x67, 0x1C, 0x7F, 0x07, 0xC0,
/* 0xA8 */ 0xCF, 0x30,
/* 0xA9 */ 0x03, 0xF0, 0x03, 0xFF, 0x01, 0xE0, 0xE0, 0xE3, 0x1C, 0x73, 0xF3, 0x99, 0x86, 0x6C, 0xC1, 0x8F, 0x30, 0x03, 0xCC, 0x00, 0xF3, 0x00, 0x3C, 0xC1, 0x8D, 0x98, 0x66, 0x77, 0xF3, 0x8E, 0x79, 0xC1, 0xC0, 0xE0, 0x3F, 0xF0, 0x03, 0xF0, 0x00,
/* 0xAA */ 0x79, 0x08, 0x11, 0xEE, 0x50, 0xA3, 0x3B, 0x00, 0x03, 0xF8,
/* 0xAB */ 0x21, 0x63, 0xE7, 0x84, 0x84, 0xE7, 0x63, 0x21,
/* 0xAC */ 0xFF, 0xFF, 0xFF, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03,
/* 0xAD */ 0xFF, 0xF0,
/* 0xAE */ 0x03, 0xF0, 0x03, 0xFF, 0x01, 0xE0, 0xE0, 0xFF, 0x1C, 0x7F, 0xF3, 0x9B, 0x04, 0x6C, 0xC1, 0x8F, 0x30, 0x43, 0xCF, 0xF0, 0xF3, 0xFC, 0x3C, 0xC1, 0x0D, 0xB0, 0x66, 0x7C, 0x1B, 0x8F, 0x07, 0xC1, 0xC0, 0xE0, 0x3F, 0xF0, 0x03, 0xF0, 0x00,
/* 0xAF */ 0xFF, 0xF0,
/* 0xB0 */ 0x38, 0xFB, 0x1C, 0x18, 0x38, 0xDF, 0x1C,
/* 0xB1 */ 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x7F, 0xE7, 0xFE, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xF0,
/* 0xB2 */ 0x7D, 0x8F, 0x18, 0x30, 0xC6, 0x18, 0x60, 0xFF, 0xFC,
/* 0xB3 */ 0x7D, 0x8F, 0x18, 0x31, 0x80, 0xC1, 0xE3, 0xC6, 0xF8,
/* 0xB4 */ 0x3B, 0x99, 0x80,
/* 0xB5 */ 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x0C, 0xC0, 0xCC, 0x1C, 0xE3, 0xCF, 0xEF, 0xFC, 0x7C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x00,
/* 0xB6 */ 0x1F, 0xE7, 0xFD, 0xF3, 0x7E, 0x6F, 0xCD, 0xF9, 0xBF, 0x37, 0xE6, 0x7C, 0xCF, 0x98, 0xF3, 0x06, 0x60, 0xCC, 0x19, 0x83, 0x30, 0x66, 0x0C, 0xC1, 0x98, 0x33, 0x06, 0x60, 0xCC,
/* 0xB7 */ 0xF0,
/* 0xB8 */ 0x10, 0xF0, 0xE3, 0x78,
/* 0xB9 */ 0x2F, 0xB6, 0xDB, 0x6C,
/* 0xBA */ 0x79, 0x38, 0x61, 0x86, 0x1C, 0xDE, 0x00, 0x0F, 0xC0,
/* 0xBB */ 0x88, 0xC6, 0xE7, 0x21, 0x21, 0xE7, 0xC6, 0x88,
/* 0xBC */ 0x20, 0x08, 0x30, 0x0C, 0x38, 0x04, 0x0C, 0x06, 0x06, 0x02, 0x03, 0x02, 0x01, 0x81, 0x00, 0xC1, 0x06, 0x61, 0x87, 0x30, 0x83, 0x80, 0xC2, 0xC0, 0x42, 0x60, 0x43, 0x30, 0x21, 0xFC, 0x20, 0x0C, 0x30, 0x06, 0x10, 0x03, 0x00,
/* 0xBD */ 0x20, 0x00, 0x08, 0x02, 0x06, 0x01, 0x83, 0x80, 0x40, 0x60, 0x20, 0x18, 0x18, 0x06, 0x04, 0x01, 0x83, 0x00, 0x61, 0x9F, 0x98, 0x4E, 0x76, 0x33, 0x0C, 0x08, 0x03, 0x04, 0x03, 0x83, 0x01, 0x80, 0x81, 0x80, 0x60, 0xC0, 0x30, 0x3F, 0xC8, 0x0F, 0xF0,
/* 0xBE */ 0x7C, 0x00, 0x18, 0xC0, 0x43, 0x18, 0x18, 0x03, 0x02, 0x00, 0x60, 0xC0, 0x30, 0x10, 0x01, 0x84, 0x00, 0x31, 0x80, 0xC6, 0x20, 0xD8, 0xC8, 0x39, 0xF1, 0x07, 0x00, 0x41, 0x60, 0x18, 0x4C, 0x02, 0x11, 0x80, 0x83, 0xF8, 0x10, 0x06, 0x04, 0x00, 0xC1, 0x00, 0x18,
/* 0xBF */ 0x0C, 0x06, 0x00, 0x00, 0x00, 0xC0, 0x60, 0x60, 0x30, 0x30, 0x38, 0x38, 0x18, 0x0C, 0x06, 0x0F, 0x07, 0xC7, 0x7F, 0x1F, 0x00,
/* 0xC0 */ 0x0C, 0x00, 0x18, 0x00, 0x30, 0x00, 0x00, 0x07, 0x80, 0x1E, 0x00, 0x78, 0x03, 0xF0, 0x0C, 0xC0, 0x33, 0x01, 0xCE, 0x06, 0x18, 0x18, 0x60, 0xE1, 0xC3, 0x03, 0x0F, 0xFC, 0x7F, 0xF9, 0x80, 0x66, 0x01, 0xB8, 0x07, 0xC0, 0x0F, 0x00, 0x30,
/* 0xC1 */ 0x01, 0xC0, 0x0C, 0x00, 0x20, 0x00, 0x00, 0x07, 0x80, 0x1E, 0x00, 0x78, 0x03, 0xF0, 0x0C, 0xC0, 0x33, 0x01, 0xCE, 0x06, 0x18, 0x18, 0x60, 0xE1, 0xC3, 0x03, 0x0F, 0xFC, 0x7F, 0xF9, 0x80, 0x66, 0x01, 0xB8, 0x07, 0xC0, 0x0F, 0x00, 0x30,
/* 0xC2 */ 0x07, 0x00, 0x3E, 0x01, 0x8C, 0x00, 0x00, 0x07, 0x80, 0x1E, 0x00, 0x78, 0x03, 0xF0, 0x0C, 0xC0, 0x33, 0x01, 0xCE, 0x06, 0x18, 0x18, 0x60, 0xE1, 0xC3, 0x03, 0x0F, 0xFC, 0x7F, 0xF9, 0x80, 0x66, 0x01, 0xB8, 0x07, 0xC0, 0x0F, 0x00, 0x30,
/* 0xC3 */ 0x0E, 0x40, 0x7F, 0x01, 0x98, 0x00, 0x00, 0x07, 0x80, 0x1E, 0x00, 0x78, 0x03, 0xF0, 0x0C, 0xC0, 0x33, 0x01, 0xCE, 0x06, 0x18, 0x18, 0x60, 0xE1, 0xC3, 0x03, 0x0F, 0xFC, 0x7F, 0xF9, 0x80, 0x66, 0x01, 0xB8, 0x07, 0xC0, 0x0F, 0x00, 0x30,
/* 0xC4 */ 0x0C, 0xC0, 0x33, 0x00, 0x00, 0x01, 0xE0, 0x07, 0x80, 0x1E, 0x00, 0xFC, 0x03, 0x30, 0x0C, 0xC0, 0x73, 0x81, 0x86, 0x06, 0x18, 0x38, 0x70, 0xC0, 0xC3, 0xFF, 0x1F, 0xFE, 0x60, 0x19, 0x80, 0x6E, 0x01, 0xF0, 0x03, 0xC0, 0x0C,
/* 0xC5 */ 0x03, 0x00, 0x1E, 0x00, 0xEC, 0x03, 0x30, 0x0F, 0xC0, 0x1E, 0x00, 0x78, 0x01, 0xE0, 0x07, 0x80, 0x3F, 0x00, 0xCC, 0x03, 0x30, 0x1C, 0xE0, 0x61, 0x81, 0x86, 0x0E, 0x1C, 0x30, 0x30, 0xFF, 0xC7, 0xFF, 0x98, 0x06, 0x60, 0x1B, 0x80, 0x7C, 0x00, 0xF0, 0x03,
/* 0xC6 */ 0x01, 0xFF, 0xFC, 0x07, 0xFF, 0xF0, 0x31, 0x80, 0x00, 0xC6, 0x00, 0x07, 0x18, 0x00, 0x18, 0x60, 0x00, 0x61, 0x80, 0x03, 0x86, 0x00, 0x0C, 0x1F, 0xF8, 0x70, 0x7F, 0xE1, 0x81, 0x80, 0x07, 0xFE, 0x00, 0x3F, 0xF8, 0x00, 0xC0, 0x60, 0x07, 0x01, 0x80, 0x1C, 0x06, 0x00, 0x60, 0x1F, 0xFF, 0x80, 0x7F, 0xF0,
/* 0xC7 */ 0x07, 0xE0, 0x3F, 0xF0, 0xE0, 0x73, 0x80, 0x66, 0x00, 0x7C, 0x00, 0x30, 0x00, 0x60, 0x00, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x06, 0x00, 0x0E, 0x00, 0x6C, 0x01, 0xDC, 0x03, 0x1C, 0x1E, 0x1F, 0xF8, 0x0F, 0xC0, 0x08, 0x00, 0x1E, 0x00, 0x0C, 0x01, 0x18, 0x01, 0xE0, 0x00,
/* 0xC8 */ 0x1C, 0x00, 0xC0, 0x02, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xEF, 0xFE, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xFF, 0xFF,
/* 0xC9 */ 0x07, 0x00, 0x60, 0x0C, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xEF, 0xFE, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xFF, 0xFF,
/* 0xCA */ 0x0E, 0x01, 0xF0, 0x31, 0x80, 0x00, 0xFF, 0xFF, 0xFF, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xEF, 0xFE, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xFF, 0xFF,
/* 0xCB */ 0x19, 0x81, 0x98, 0x00, 0x0F, 0xFF, 0xFF, 0xFC, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0F, 0xFE, 0xFF, 0xEC, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0F, 0xFF, 0xFF, 0xF0,
/* 0xCC */ 0xE7, 0x10, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
/* 0xCD */ 0x36, 0xC0, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
/* 0xCE */ 0x39, 0xFC, 0x40, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC0,
/* 0xCF */ 0xC7, 0x8C, 0x01, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x00,
/* 0xD0 */ 0x7F, 0xE0, 0xFF, 0xE1, 0x80, 0xE3, 0x00, 0xE6, 0x00, 0xCC, 0x01, 0xD8, 0x01, 0xB0, 0x03, 0xFE, 0x07, 0xFC, 0x0D, 0x80, 0x1B, 0x00, 0x36, 0x00, 0x6C, 0x01, 0x98, 0x07, 0x30, 0x1C, 0x7F, 0xF0, 0xFF, 0xC0,
/* 0xD1 */ 0x08, 0xC0, 0xFE, 0x05, 0xE0, 0x00, 0x0E, 0x01, 0xF0, 0x0F, 0xC0, 0x7E, 0x03, 0xD8, 0x1E, 0xE0, 0xF3, 0x07, 0x9C, 0x3C, 0x61, 0xE1, 0x8F, 0x0E, 0x78, 0x33, 0xC1, 0xDE, 0x06, 0xF0, 0x1F, 0x80, 0xFC, 0x03, 0xE0, 0x1C,
/* 0xD2 */ 0x07, 0x00, 0x01, 0x80, 0x00, 0x60, 0x00, 0x00, 0x00, 0x7F, 0x00, 0xFF, 0xE0, 0xF0, 0x78, 0x60, 0x0C, 0x60, 0x03, 0x30, 0x01, 0xB0, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x0F, 0x00, 0x07, 0x80, 0x03, 0x60, 0x03, 0x30, 0x01, 0x8C, 0x01, 0x87, 0x83, 0xC1, 0xFF, 0xC0, 0x3F, 0x80,
/* 0xD3 */ 0x00, 0xE0, 0x00, 0x60, 0x00, 0x40, 0x00, 0x00, 0x00, 0x7F, 0x00, 0xFF, 0xE0, 0xF0, 0x78, 0x60, 0x0C, 0x60, 0x03, 0x30, 0x01, 0xB0, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x0F, 0x00, 0x07, 0x80, 0x03, 0x60, 0x03, 0x30, 0x01, 0x8C, 0x01, 0x87, 0x83, 0xC1, 0xFF, 0xC0, 0x3F, 0x80,
/* 0xD4 */ 0x03, 0xC0, 0x01, 0xE0, 0x01, 0x98, 0x00, 0x00, 0x00, 0x7F, 0x00, 0xFF, 0xE0, 0xF0, 0x78, 0x60, 0x0C, 0x60, 0x03, 0x30, 0x01, 0xB0, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x0F, 0x00, 0x07, 0x80, 0x03, 0x60, 0x03, 0x30, 0x01, 0x8C, 0x01, 0x87, 0x83, 0xC1, 0xFF, 0xC0, 0x3F, 0x80,
/* 0xD5 */ 0x07, 0x20, 0x03, 0xF0, 0x01, 0x38, 0x00, 0x00, 0x00, 0x7F, 0x00, 0xFF, 0xE0, 0xF0, 0x78, 0x60, 0x0C, 0x60, 0x03, 0x30, 0x01, 0xB0, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x0F, 0x00, 0x07, 0x80, 0x03, 0x60, 0x03, 0x30, 0x01, 0x8C, 0x01, 0x87, 0x83, 0xC1, 0xFF, 0xC0, 0x3F, 0x80,
/* 0xD6 */ 0x06, 0x30, 0x03, 0x18, 0x00, 0x00, 0x00, 0xFE, 0x01, 0xFF, 0xC1, 0xE0, 0xF0, 0xC0, 0x18, 0xC0, 0x06, 0x60, 0x03, 0x60, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x0F, 0x00, 0x06, 0xC0, 0x06, 0x60, 0x03, 0x18, 0x03, 0x0F, 0x07, 0x83, 0xFF, 0x80, 0x7F, 0x00,
/* 0xD7 */ 0x81, 0xC3, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0xC3, 0x81,
/* 0xD8 */ 0x07, 0xF0, 0x8F, 0xFE, 0x8F, 0x07, 0xC6, 0x00, 0xE6, 0x00, 0xF3, 0x00, 0xDF, 0x00, 0xC7, 0x80, 0xC3, 0xC0, 0xC1, 0xE0, 0xC0, 0xF0, 0xC0, 0x78, 0xC0, 0x3E, 0xC0, 0x33, 0xC0, 0x19, 0xC0, 0x1C, 0xF8, 0x3C, 0xDF, 0xF8, 0x43, 0xF8, 0x00,
/* 0xD9 */ 0x0C, 0x00, 0x30, 0x00, 0xC0, 0x00, 0x0C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF8, 0x0E, 0xE0, 0xE3, 0xFE, 0x0F, 0xC0,
/* 0xDA */ 0x03, 0x80, 0x18, 0x01, 0x80, 0x00, 0x0C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF8, 0x0E, 0xE0, 0xE3, 0xFE, 0x0F, 0xC0,
/* 0xDB */ 0x07, 0x00, 0x7C, 0x06, 0x20, 0x00, 0x0C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF8, 0x0E, 0xE0, 0xE3, 0xFE, 0x0F, 0xC0,
/* 0xDC */ 0x0C, 0xC0, 0x66, 0x00, 0x01, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1F, 0x01, 0xDC, 0x1C, 0x7F, 0xC1, 0xF8, 0x00,
/* 0xDD */ 0x01, 0x80, 0x0C, 0x00, 0x60, 0x00, 0x00, 0xC0, 0x0F, 0x80, 0x76, 0x01, 0x9C, 0x0C, 0x38, 0x70, 0x61, 0x81, 0xCE, 0x03, 0x30, 0x0F, 0x80, 0x1E, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00,
/* 0xDE */ 0xC0, 0x0C, 0x00, 0xC0, 0x0F, 0xF8, 0xFF, 0xEC, 0x06, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x6F, 0xFE, 0xFF, 0x8C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00,
/* 0xDF */ 0x1F, 0x0F, 0xF3, 0x87, 0x60, 0x6C, 0x0D, 0x81, 0xB0, 0x66, 0x38, 0xC7, 0xD8, 0x1B, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x3E, 0x0E, 0xCF, 0x99, 0xE0,
/* 0xE0 */ 0x1C, 0x00, 0xC0, 0x06, 0x00, 0x20, 0x00, 0x03, 0xF0, 0x7F, 0x8E, 0x1C, 0xC0, 0xC0, 0x0C, 0x01, 0xC3, 0xFC, 0xF8, 0xCC, 0x0C, 0xC0, 0xCE, 0x3C, 0x7E, 0xF3, 0xC7,
/* 0xE1 */ 0x07, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x00, 0x03, 0xF0, 0x7F, 0x8E, 0x1C, 0xC0, 0xC0, 0x0C, 0x01, 0xC3, 0xFC, 0xF8, 0xCC, 0x0C, 0xC0, 0xCE, 0x3C, 0x7E, 0xF3, 0xC7,
/* 0xE2 */ 0x0C, 0x01, 0xE0, 0x1B, 0x03, 0x30, 0x00, 0x03, 0xF0, 0x7F, 0x8E, 0x1C, 0xC0, 0xC0, 0x0C, 0x01, 0xC3, 0xFC, 0xF8, 0xCC, 0x0C, 0xC0, 0xCE, 0x3C, 0x7E, 0xF3, 0xC7,
/* 0xE3 */ 0x19, 0x83, 0xF0, 0x27, 0x00, 0x00, 0x00, 0x03, 0xF0, 0x7F, 0x8E, 0x1C, 0xC0, 0xC0, 0x0C, 0x01, 0xC3, 0xFC, 0xF8, 0xCC, 0x0C, 0xC0, 0xCE, 0x3C, 0x7E, 0xF3, 0xC7,
/* 0xE4 */ 0x19, 0x81, 0x98, 0x00, 0x00, 0x00, 0x3F, 0x07, 0xF8, 0xE1, 0xCC, 0x0C, 0x00, 0xC0, 0x1C, 0x3F, 0xCF, 0x8C, 0xC0, 0xCC, 0x0C, 0xE3, 0xC7, 0xEF, 0x3C, 0x70,
/* 0xE5 */ 0x0E, 0x01, 0xF0, 0x1B, 0x81, 0xB8, 0x1F, 0x00, 0xE0, 0x3F, 0x07, 0xF8, 0xE1, 0xCC, 0x0C, 0x00, 0xC0, 0x1C, 0x3F, 0xCF, 0x8C, 0xC0, 0xCC, 0x0C, 0xE3, 0xC7, 0xEF, 0x3C, 0x70,
/* 0xE6 */ 0x3F, 0x1F, 0x0F, 0xF7, 0xF3, 0x87, 0xC3, 0x60, 0x70, 0x30, 0x0C, 0x06, 0x3F, 0xFF, 0xDF, 0xFF, 0xFF, 0x06, 0x00, 0xC0, 0xC0, 0x18, 0x3C, 0x0F, 0x8F, 0xC7, 0x3F, 0x9F, 0xE3, 0xC1, 0xF0,
/* 0xE7 */ 0x1F, 0x0F, 0xE7, 0x1D, 0x83, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x38, 0x37, 0x1C, 0xFE, 0x1F, 0x02, 0x00, 0xE0, 0x0C, 0x23, 0x07, 0x80,
/* 0xE8 */ 0x1C, 0x01, 0x80, 0x18, 0x01, 0x00, 0x00, 0x3E, 0x0F, 0xE3, 0x8E, 0xE0, 0x78, 0x0F, 0xFF, 0xFF, 0xFC, 0x01, 0x80, 0x38, 0x1B, 0x86, 0x3F, 0x83, 0xE0,
/* 0xE9 */ 0x03, 0x00, 0xC0, 0x30, 0x04, 0x00, 0x00, 0x3E, 0x0F, 0xE3, 0x8E, 0xE0, 0x78, 0x0F, 0xFF, 0xFF, 0xFC, 0x01, 0x80, 0x38, 0x1B, 0x86, 0x3F, 0x83, 0xE0,
/* 0xEA */ 0x0C, 0x03, 0xC0, 0x6C, 0x18, 0x80, 0x00, 0x3E, 0x0F, 0xE3, 0x8E, 0xE0, 0x78, 0x0F, 0xFF, 0xFF, 0xFC, 0x01, 0x80, 0x38, 0x1B, 0x86, 0x3F, 0x83, 0xE0,
/* 0xEB */ 0x31, 0x86, 0x30, 0x00, 0x00, 0x01, 0xF0, 0x7F, 0x1C, 0x77, 0x03, 0xC0, 0x7F, 0xFF, 0xFF, 0xE0, 0x0C, 0x01, 0xC0, 0xDC, 0x31, 0xFC, 0x1F, 0x00,
/* 0xEC */ 0xC6, 0x31, 0x06, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
/* 0xED */ 0x39, 0x99, 0x80, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x18, 0xC6, 0x31, 0x80,
/* 0xEE */ 0x71, 0xED, 0xA3, 0x00, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC0,
/* 0xEF */ 0xCF, 0x30, 0x00, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30,
/* 0xF0 */ 0x20, 0x07, 0xE0, 0x70, 0x3B, 0x00, 0x30, 0x3F, 0x0F, 0xF3, 0x8E, 0xE0, 0xF8, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF8, 0x3B, 0x8E, 0x3F, 0x83, 0xE0,
/* 0xF1 */ 0x19, 0x8F, 0xE2, 0x70, 0x00, 0x00, 0x33, 0xCD, 0xFB, 0xC7, 0xE0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x30,
/* 0xF2 */ 0x1C, 0x01, 0x80, 0x18, 0x01, 0x00, 0x00, 0x3E, 0x0F, 0xE3, 0x8E, 0xE0, 0xF8, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF8, 0x3B, 0x8E, 0x3F, 0x83, 0xE0,
/* 0xF3 */ 0x07, 0x00, 0xC0, 0x30, 0x04, 0x00, 0x00, 0x3E, 0x0F, 0xE3, 0x8E, 0xE0, 0xF8, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF8, 0x3B, 0x8E, 0x3F, 0x83, 0xE0,
/* 0xF4 */ 0x0C, 0x03, 0xC0, 0xD8, 0x19, 0x80, 0x00, 0x3E, 0x0F, 0xE3, 0x8E, 0xE0, 0xF8, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF8, 0x3B, 0x8E, 0x3F, 0x83, 0xE0,
/* 0xF5 */ 0x19, 0x87, 0xE0, 0x9C, 0x00, 0x00, 0x00, 0x3E, 0x0F, 0xE3, 0x8E, 0xE0, 0xF8, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF8, 0x3B, 0x8E, 0x3F, 0x83, 0xE0,
/* 0xF6 */ 0x31, 0x86, 0x30, 0x00, 0x00, 0x01, 0xF0, 0x7F, 0x1C, 0x77, 0x07, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0xC1, 0xDC, 0x71, 0xFC, 0x1F, 0x00,
/* 0xF7 */ 0x06, 0x00, 0x60, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x06, 0x00,
/* 0xF8 */ 0x1F, 0x27, 0xF5, 0xC7, 0x70, 0x7C, 0x17, 0x84, 0xF1, 0x1E, 0x43, 0xD0, 0x7C, 0x1D, 0xC7, 0x3F, 0xC9, 0xF0,
/* 0xF9 */ 0x18, 0x03, 0x00, 0x60, 0x08, 0x00, 0x30, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x1F, 0x8F, 0x7E, 0xCF, 0x30,
/* 0xFA */ 0x03, 0x01, 0x80, 0x60, 0x30, 0x00, 0x30, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x1F, 0x8F, 0x7E, 0xCF, 0x30,
/* 0xFB */ 0x0C, 0x07, 0x81, 0x20, 0xCC, 0x00, 0x30, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x1F, 0x8F, 0x7E, 0xCF, 0x30,
/* 0xFC */ 0x31, 0x8C, 0x60, 0x00, 0x00, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x7E, 0x3D, 0xFB, 0x3C, 0xC0,
/* 0xFD */ 0x03, 0x80, 0x60, 0x18, 0x06, 0x00, 0x01, 0xC0, 0xD8, 0x1B, 0x06, 0x70, 0xC6, 0x18, 0xC6, 0x18, 0xC1, 0x98, 0x36, 0x06, 0xC0, 0x78, 0x0E, 0x01, 0xC0, 0x30, 0x06, 0x01, 0xC0, 0xF0, 0x1C, 0x00,
/* 0xFE */ 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xCF, 0x8F, 0xFC, 0xF0, 0xEE, 0x06, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3E, 0x06, 0xF0, 0xEF, 0xFC, 0xCF, 0x8C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x00,
/* 0xFF */ 0x19, 0x83, 0x30, 0x00, 0x00, 0x0E, 0x06, 0xC0, 0xD8, 0x33, 0x86, 0x30, 0xC6, 0x30, 0xC6, 0x0C, 0xC1, 0xB0, 0x36, 0x03, 0xC0, 0x70, 0x0E, 0x01, 0x80, 0x30, 0x0E, 0x07, 0x80, 0xE0, 0x00,
};
const GFXglyph FreeSans12pt_Win1252Glyphs[] PROGMEM = {
/* 0x01 */ { 0, 19, 20, 21, 1, -17 },
/* 0x02 */ { 48, 19, 20, 21, 1, -17 },
/* 0x03 */ { 96, 21, 20, 23, 1, -17 },
/* 0x04 */ { 149, 21, 20, 23, 1, -17 },
/* 0x05 */ { 202, 20, 20, 22, 1, -17 },
/* 0x06 */ { 252, 20, 20, 22, 1, -17 },
/* 0x07 */ { 302, 0, 0, 8, 0, 0 },
/* 0x08 */ { 302, 23, 20, 25, 1, -17 },
/* 0x09 */ { 360, 23, 16, 25, 1, -16 },
/* 0x0A */ { 406, 0, 0, 8, 0, 0 },
/* 0x0B */ { 406, 21, 20, 23, 1, -17 },
/* 0x0C */ { 459, 19, 18, 21, 1, -15 },
/* 0x0D */ { 502, 0, 0, 8, 0, 0 },
/* 0x0E */ { 502, 20, 20, 22, 1, -17 },
/* 0x0F */ { 552, 21, 21, 23, 1, -18 },
/* 0x10 */ { 608, 19, 20, 21, 1, -17 },
/* 0x11 */ { 656, 21, 20, 23, 1, -17 },
/* 0x12 */ { 709, 20, 20, 22, 1, -17 },
/* 0x13 */ { 759, 21, 20, 23, 1, -17 },
/* 0x14 */ { 812, 21, 20, 23, 1, -17 },
/* 0x15 */ { 865, 22, 20, 24, 1, -17 },
/* 0x16 */ { 920, 16, 20, 18, 1, -17 },
/* 0x17 */ { 960, 21, 20, 23, 1, -17 },
/* 0x18 */ { 1013, 23, 20, 25, 1, -17 },
/* 0x19 */ { 1071, 21, 20, 23, 1, -17 },
/* 0x1A */ { 1124, 15, 19, 17, 1, -16 },
/* 0x1B */ { 1160, 24, 21, 26, 1, -18 },
/* 0x1C */ { 1223, 21, 20, 23, 1, -17 },
/* 0x1D */ { 1276, 21, 21, 23, 1, -18 },
/* 0x1E */ { 1332, 20, 20, 22, 1, -17 },
/* 0x1F */ { 1382, 15, 20, 17, 1, -17 },
/* ' ' 0x20 */ { 1420, 0, 0, 6, 0, 0 },
/* '!' 0x21 */ { 1420, 2, 18, 8, 3, -16 },
/* '"' 0x22 */ { 1425, 6, 6, 8, 1, -15 },
/* '#' 0x23 */ { 1430, 13, 16, 13, 0, -14 },
/* '$' 0x24 */ { 1456, 11, 20, 13, 1, -16 },
/* '%' 0x25 */ { 1484, 20, 17, 21, 1, -15 },
/* '&' 0x26 */ { 1527, 14, 17, 16, 1, -15 },
/* ''' 0x27 */ { 1557, 2, 6, 5, 1, -15 },
/* '(' 0x28 */ { 1559, 5, 23, 8, 2, -16 },
/* ')' 0x29 */ { 1574, 5, 23, 8, 1, -16 },
/* '*' 0x2A */ { 1589, 7, 7, 9, 1, -16 },
/* '+' 0x2B */ { 1596, 10, 11, 14, 2, -9 },
/* ',' 0x2C */ { 1610, 2, 6, 7, 2, 0 },
/* '-' 0x2D */ { 1612, 6, 2, 8, 1, -6 },
/* '.' 0x2E */ { 1614, 2, 2, 6, 2, 0 },
/* '/' 0x2F */ { 1615, 7, 18, 7, 0, -16 },
/* '0' 0x30 */ { 1631, 11, 17, 13, 1, -15 },
/* '1' 0x31 */ { 1655, 5, 17, 13, 3, -15 },
/* '2' 0x32 */ { 1666, 11, 17, 13, 1, -15 },
/* '3' 0x33 */ { 1690, 11, 17, 13, 1, -15 },
/* '4' 0x34 */ { 1714, 11, 17, 13, 1, -15 },
/* '5' 0x35 */ { 1738, 11, 17, 13, 1, -15 },
/* '6' 0x36 */ { 1762, 11, 17, 13, 1, -15 },
/* '7' 0x37 */ { 1786, 11, 17, 13, 1, -15 },
/* '8' 0x38 */ { 1810, 11, 17, 13, 1, -15 },
/* '9' 0x39 */ { 1834, 11, 17, 13, 1, -15 },
/* ':' 0x3A */ { 1858, 2, 13, 6, 2, -11 },
/* ';' 0x3B */ { 1862, 2, 16, 6, 2, -10 },
/* '<' 0x3C */ { 1866, 12, 11, 14, 1, -9 },
/* '=' 0x3D */ { 1883, 12, 6, 14, 1, -7 },
/* '>' 0x3E */ { 1892, 12, 11, 14, 1, -9 },
/* '?' 0x3F */ { 1909, 10, 18, 13, 2, -16 },
/* '@' 0x40 */ { 1932, 22, 21, 24, 1, -16 },
/* 'A' 0x41 */ { 1990, 14, 18, 16, 1, -16 },
/* 'B' 0x42 */ { 2022, 13, 18, 16, 2, -16 },
/* 'C' 0x43 */ { 2052, 15, 18, 17, 1, -16 },
/* 'D' 0x44 */ { 2086, 14, 18, 17, 2, -16 },
/* 'E' 0x45 */ { 2118, 12, 18, 15, 2, -16 },
/* 'F' 0x46 */ { 2145, 11, 18, 14, 2, -16 },
/* 'G' 0x47 */ { 2170, 16, 18, 18, 1, -16 },
/* 'H' 0x48 */ { 2206, 13, 18, 17, 2, -16 },
/* 'I' 0x49 */ { 2236, 2, 18, 7, 2, -16 },
/* 'J' 0x4A */ { 2241, 9, 18, 13, 1, -16 },
/* 'K' 0x4B */ { 2262, 13, 18, 16, 2, -16 },
/* 'L' 0x4C */ { 2292, 10, 18, 14, 2, -16 },
/* 'M' 0x4D */ { 2315, 16, 18, 20, 2, -16 },
/* 'N' 0x4E */ { 2351, 13, 18, 18, 2, -16 },
/* 'O' 0x4F */ { 2381, 17, 18, 19, 1, -16 },
/* 'P' 0x50 */ { 2420, 12, 18, 16, 2, -16 },
/* 'Q' 0x51 */ { 2447, 17, 19, 19, 1, -16 },
/* 'R' 0x52 */ { 2488, 14, 18, 17, 2, -16 },
/* 'S' 0x53 */ { 2520, 14, 18, 16, 1, -16 },
/* 'T' 0x54 */ { 2552, 12, 18, 15, 1, -16 },
/* 'U' 0x55 */ { 2579, 13, 18, 17, 2, -16 },
/* 'V' 0x56 */ { 2609, 14, 18, 15, 1, -16 },
/* 'W' 0x57 */ { 2641, 22, 18, 22, 0, -16 },
/* 'X' 0x58 */ { 2691, 14, 18, 16, 1, -16 },
/* 'Y' 0x59 */ { 2723, 14, 18, 16, 1, -16 },
/* 'Z' 0x5A */ { 2755, 13, 18, 15, 1, -16 },
/* '[' 0x5B */ { 2785, 4, 23, 7, 2, -16 },
/* '\' 0x5C */ { 2797, 7, 18, 7, 0, -16 },
/* ']' 0x5D */ { 2813, 4, 23, 7, 1, -16 },
/* '^' 0x5E */ { 2825, 9, 9, 11, 1, -15 },
/* '_' 0x5F */ { 2836, 15, 1, 13, -1, 5 },
/* '`' 0x60 */ { 2838, 5, 4, 6, 1, -16 },
/* 'a' 0x61 */ { 2841, 12, 13, 13, 1, -11 },
/* 'b' 0x62 */ { 2861, 12, 18, 13, 1, -16 },
/* 'c' 0x63 */ { 2888, 10, 13, 12, 1, -11 },
/* 'd' 0x64 */ { 2905, 11, 18, 13, 1, -16 },
/* 'e' 0x65 */ { 2930, 11, 13, 13, 1, -11 },
/* 'f' 0x66 */ { 2948, 5, 18, 7, 1, -16 },
/* 'g' 0x67 */ { 2960, 11, 18, 13, 1, -11 },
/* 'h' 0x68 */ { 2985, 10, 18, 13, 1, -16 },
/* 'i' 0x69 */ { 3008, 2, 18, 5, 2, -16 },
/* 'j' 0x6A */ { 3013, 4, 23, 6, 0, -16 },
/* 'k' 0x6B */ { 3025, 10, 18, 12, 1, -16 },
/* 'l' 0x6C */ { 3048, 2, 18, 5, 1, -16 },
/* 'm' 0x6D */ { 3053, 17, 13, 19, 1, -11 },
/* 'n' 0x6E */ { 3081, 10, 13, 13, 1, -11 },
/* 'o' 0x6F */ { 3098, 11, 13, 13, 1, -11 },
/* 'p' 0x70 */ { 3116, 12, 17, 13, 1, -11 },
/* 'q' 0x71 */ { 3142, 11, 17, 13, 1, -11 },
/* 'r' 0x72 */ { 3166, 6, 13, 8, 1, -11 },
/* 's' 0x73 */ { 3176, 10, 13, 12, 1, -11 },
/* 't' 0x74 */ { 3193, 5, 16, 7, 1, -14 },
/* 'u' 0x75 */ { 3203, 10, 13, 13, 1, -11 },
/* 'v' 0x76 */ { 3220, 11, 13, 12, 0, -11 },
/* 'w' 0x77 */ { 3238, 17, 13, 17, 0, -11 },
/* 'x' 0x78 */ { 3266, 10, 13, 11, 1, -11 },
/* 'y' 0x79 */ { 3283, 11, 18, 11, 0, -11 },
/* 'z' 0x7A */ { 3308, 10, 13, 12, 1, -11 },
/* '{' 0x7B */ { 3325, 5, 23, 8, 1, -16 },
/* '|' 0x7C */ { 3340, 2, 23, 6, 2, -16 },
/* '}' 0x7D */ { 3346, 5, 23, 8, 2, -16 },
/* '~' 0x7E */ { 3361, 10, 5, 12, 1, -9 },
/* 0x7F */ { 3368, 0, 0, 0, 0, 0 },
/* 0x80 */ { 3368, 14, 17, 16, 1, -15 },
/* 0x81 */ { 3398, 0, 0, 8, 0, 0 },
/* 0x82 */ { 3398, 2, 5, 6, 2, 0 },
/* 0x83 */ { 3400, 6, 23, 7, 0, -16 },
/* 0x84 */ { 3418, 6, 5, 10, 2, 0 },
/* 0x85 */ { 3422, 12, 2, 16, 2, 0 },
/* 0x86 */ { 3425, 10, 21, 13, 2, -15 },
/* 0x87 */ { 3452, 10, 20, 13, 2, -15 },
/* 0x88 */ { 3477, 7, 4, 8, 0, -16 },
/* 0x89 */ { 3481, 23, 18, 24, 0, -16 },
/* 0x8A */ { 3533, 14, 21, 16, 1, -19 },
/* 0x8B */ { 3570, 3, 8, 6, 1, -9 },
/* 0x8C */ { 3573, 22, 18, 24, 1, -16 },
/* 0x8D */ { 3623, 0, 0, 8, 0, 0 },
/* 0x8E */ { 3623, 13, 21, 15, 1, -19 },
/* 0x8F */ { 3658, 0, 0, 8, 0, 0 },
/* 0x90 */ { 3658, 0, 0, 8, 0, 0 },
/* 0x91 */ { 3658, 2, 6, 6, 2, -16 },
/* 0x92 */ { 3660, 2, 6, 6, 2, -16 },
/* 0x93 */ { 3662, 6, 6, 10, 2, -16 },
/* 0x94 */ { 3667, 6, 6, 10, 2, -16 },
/* 0x95 */ { 3672, 6, 6, 10, 2, -9 },
/* 0x96 */ { 3677, 10, 2, 12, 1, -6 },
/* 0x97 */ { 3680, 22, 2, 24, 1, -6 },
/* 0x98 */ { 3686, 7, 3, 8, 0, -16 },
/* 0x99 */ { 3689, 22, 13, 24, 2, -16 },
/* 0x9A */ { 3725, 10, 18, 12, 1, -16 },
/* 0x9B */ { 3748, 3, 8, 6, 2, -8 },
/* 0x9C */ { 3751, 20, 13, 22, 1, -11 },
/* 0x9D */ { 3784, 0, 0, 8, 0, 0 },
/* 0x9E */ { 3784, 10, 18, 12, 1, -16 },
/* 0x9F */ { 3807, 14, 21, 16, 1, -19 },
/* 0xA0 */ { 3844, 0, 0, 7, 0, 0 },
/* 0xA1 */ { 3844, 2, 18, 8, 3, -11 },
/* 0xA2 */ { 3849, 11, 17, 13, 1, -13 },
/* 0xA3 */ { 3873, 12, 18, 13, 0, -16 },
/* 0xA4 */ { 3900, 9, 9, 13, 2, -11 },
/* 0xA5 */ { 3911, 12, 17, 13, 1, -15 },
/* 0xA6 */ { 3937, 2, 23, 6, 2, -16 },
/* 0xA7 */ { 3943, 11, 23, 13, 1, -16 },
/* 0xA8 */ { 3975, 6, 2, 8, 1, -15 },
/* 0xA9 */ { 3977, 18, 17, 19, 1, -15 },
/* 0xAA */ { 4016, 7, 11, 9, 1, -16 },
/* 0xAB */ { 4026, 8, 8, 12, 2, -9 },
/* 0xAC */ { 4034, 12, 6, 14, 1, -7 },
/* 0xAD */ { 4043, 6, 2, 8, 1, -6 },
/* 0xAE */ { 4045, 18, 17, 19, 1, -15 },
/* 0xAF */ { 4084, 6, 2, 8, 1, -15 },
/* 0xB0 */ { 4086, 7, 8, 15, 4, -15 },
/* 0xB1 */ { 4093, 12, 15, 14, 1, -13 },
/* 0xB2 */ { 4116, 7, 10, 8, 1, -17 },
/* 0xB3 */ { 4125, 7, 10, 8, 1, -17 },
/* 0xB4 */ { 4134, 5, 4, 8, 2, -16 },
/* 0xB5 */ { 4137, 12, 17, 13, 2, -11 },
/* 0xB6 */ { 4163, 11, 21, 13, 2, -16 },
/* 0xB7 */ { 4192, 2, 2, 6, 2, -6 },
/* 0xB8 */ { 4193, 6, 5, 8, 1, 2 },
/* 0xB9 */ { 4197, 3, 10, 8, 3, -18 },
/* 0xBA */ { 4201, 6, 11, 9, 1, -16 },
/* 0xBB */ { 4210, 8, 8, 12, 2, -8 },
/* 0xBC */ { 4218, 17, 17, 21, 3, -15 },
/* 0xBD */ { 4255, 18, 18, 21, 3, -16 },
/* 0xBE */ { 4296, 19, 18, 21, 1, -16 },
/* 0xBF */ { 4339, 9, 18, 13, 3, -11 },
/* 0xC0 */ { 4360, 14, 22, 16, 1, -20 },
/* 0xC1 */ { 4399, 14, 22, 16, 1, -20 },
/* 0xC2 */ { 4438, 14, 22, 16, 1, -20 },
/* 0xC3 */ { 4477, 14, 22, 16, 1, -20 },
/* 0xC4 */ { 4516, 14, 21, 16, 1, -19 },
/* 0xC5 */ { 4553, 14, 24, 16, 1, -22 },
/* 0xC6 */ { 4595, 22, 18, 24, 0, -16 },
/* 0xC7 */ { 4645, 15, 23, 17, 1, -16 },
/* 0xC8 */ { 4689, 12, 22, 15, 2, -20 },
/* 0xC9 */ { 4722, 12, 22, 15, 2, -20 },
/* 0xCA */ { 4755, 12, 22, 15, 2, -20 },
/* 0xCB */ { 4788, 12, 21, 15, 2, -19 },
/* 0xCC */ { 4820, 4, 22, 7, 0, -20 },
/* 0xCD */ { 4831, 4, 22, 7, 1, -20 },
/* 0xCE */ { 4842, 6, 22, 7, 0, -20 },
/* 0xCF */ { 4859, 7, 21, 7, 0, -19 },
/* 0xD0 */ { 4878, 15, 18, 17, 1, -16 },
/* 0xD1 */ { 4912, 13, 22, 18, 2, -20 },
/* 0xD2 */ { 4948, 17, 22, 19, 1, -20 },
/* 0xD3 */ { 4995, 17, 22, 19, 1, -20 },
/* 0xD4 */ { 5042, 17, 22, 19, 1, -20 },
/* 0xD5 */ { 5089, 17, 22, 19, 1, -20 },
/* 0xD6 */ { 5136, 17, 21, 19, 1, -19 },
/* 0xD7 */ { 5181, 8, 9, 14, 3, -8 },
/* 0xD8 */ { 5190, 17, 18, 19, 1, -16 },
/* 0xD9 */ { 5229, 13, 22, 17, 2, -20 },
/* 0xDA */ { 5265, 13, 22, 17, 2, -20 },
/* 0xDB */ { 5301, 13, 22, 17, 2, -20 },
/* 0xDC */ { 5337, 13, 21, 17, 2, -19 },
/* 0xDD */ { 5372, 14, 22, 16, 1, -20 },
/* 0xDE */ { 5411, 12, 18, 15, 2, -16 },
/* 0xDF */ { 5438, 11, 18, 14, 2, -16 },
/* 0xE0 */ { 5463, 12, 18, 13, 1, -16 },
/* 0xE1 */ { 5490, 12, 18, 13, 1, -16 },
/* 0xE2 */ { 5517, 12, 18, 13, 1, -16 },
/* 0xE3 */ { 5544, 12, 18, 13, 1, -16 },
/* 0xE4 */ { 5571, 12, 17, 13, 1, -15 },
/* 0xE5 */ { 5597, 12, 19, 13, 1, -17 },
/* 0xE6 */ { 5626, 19, 13, 21, 1, -11 },
/* 0xE7 */ { 5657, 10, 18, 12, 1, -11 },
/* 0xE8 */ { 5680, 11, 18, 13, 1, -16 },
/* 0xE9 */ { 5705, 11, 18, 13, 1, -16 },
/* 0xEA */ { 5730, 11, 18, 13, 1, -16 },
/* 0xEB */ { 5755, 11, 17, 13, 1, -15 },
/* 0xEC */ { 5779, 4, 18, 5, 1, -16 },
/* 0xED */ { 5788, 5, 18, 5, 0, -16 },
/* 0xEE */ { 5800, 6, 18, 6, 0, -16 },
/* 0xEF */ { 5814, 6, 17, 6, 0, -15 },
/* 0xF0 */ { 5827, 11, 18, 13, 1, -16 },
/* 0xF1 */ { 5852, 10, 18, 13, 1, -16 },
/* 0xF2 */ { 5875, 11, 18, 13, 1, -16 },
/* 0xF3 */ { 5900, 11, 18, 13, 1, -16 },
/* 0xF4 */ { 5925, 11, 18, 13, 1, -16 },
/* 0xF5 */ { 5950, 11, 18, 13, 1, -16 },
/* 0xF6 */ { 5975, 11, 17, 13, 1, -15 },
/* 0xF7 */ { 5999, 12, 11, 14, 1, -9 },
/* 0xF8 */ { 6016, 11, 13, 13, 1, -11 },
/* 0xF9 */ { 6034, 10, 18, 13, 1, -16 },
/* 0xFA */ { 6057, 10, 18, 13, 1, -16 },
/* 0xFB */ { 6080, 10, 18, 13, 1, -16 },
/* 0xFC */ { 6103, 10, 17, 13, 1, -15 },
/* 0xFD */ { 6125, 11, 23, 11, 0, -16 },
/* 0xFE */ { 6157, 12, 21, 13, 1, -15 },
/* 0xFF */ { 6189, 11, 22, 11, 0, -15 },
};
const GFXfont FreeSans12pt_Win1252 PROGMEM = {
(uint8_t*)FreeSans12pt_Win1252Bitmaps,
(GFXglyph*)FreeSans12pt_Win1252Glyphs,
0x01, 0xFF, 19
};
@@ -0,0 +1,198 @@
#pragma once
const uint8_t FreeSans6pt7bBitmaps[] PROGMEM = {
/* ' ' 0x20 */
/* '!' 0x21 */ 0xFE, 0x80,
/* '"' 0x22 */ 0xB6, 0x80,
/* '#' 0x23 */ 0x24, 0x49, 0xF9, 0x42, 0x9F, 0x92, 0x28,
/* '$' 0x24 */ 0x23, 0xAB, 0x5A, 0x38, 0xB5, 0xAB, 0x88,
/* '%' 0x25 */ 0x71, 0x22, 0x88, 0xA2, 0x30, 0x74, 0x02, 0x60, 0xA4, 0x49, 0x11, 0x80,
/* '&' 0x26 */ 0x31, 0x24, 0x8C, 0x72, 0x58, 0xA3, 0x74,
/* ''' 0x27 */ 0xE0,
/* '(' 0x28 */ 0x5A, 0xAA, 0x94,
/* ')' 0x29 */ 0x89, 0x12, 0x49, 0x49, 0x00,
/* '*' 0x2A */ 0x5E, 0x80,
/* '+' 0x2B */ 0x21, 0x3E, 0x42, 0x00,
/* ',' 0x2C */ 0xE0,
/* '-' 0x2D */ 0xE0,
/* '.' 0x2E */ 0x80,
/* '/' 0x2F */ 0x25, 0x24, 0xA4, 0x80,
/* '0' 0x30 */ 0x76, 0xE3, 0x18, 0xC6, 0x3B, 0x70,
/* '1' 0x31 */ 0x5D, 0x55, 0x40,
/* '2' 0x32 */ 0x74, 0x42, 0x11, 0x11, 0x10, 0xF8,
/* '3' 0x33 */ 0x74, 0x42, 0x13, 0x04, 0x31, 0x70,
/* '4' 0x34 */ 0x11, 0x8C, 0xA9, 0x4B, 0xE2, 0x10,
/* '5' 0x35 */ 0x7D, 0x04, 0x1E, 0x4C, 0x10, 0x63, 0x78,
/* '6' 0x36 */ 0x72, 0x61, 0xE8, 0xC6, 0x39, 0x70,
/* '7' 0x37 */ 0xF8, 0x44, 0x22, 0x11, 0x08, 0x40,
/* '8' 0x38 */ 0x7A, 0x18, 0x61, 0x7A, 0x18, 0x61, 0x78,
/* '9' 0x39 */ 0x7B, 0x28, 0x61, 0xCD, 0xD0, 0x62, 0x70,
/* ':' 0x3A */ 0x82,
/* ';' 0x3B */ 0x87,
/* '<' 0x3C */ 0x3E, 0x30, 0x60, 0x80,
/* '=' 0x3D */ 0xF8, 0x3E,
/* '>' 0x3E */ 0xE0, 0xC6, 0xC8, 0x00,
/* '?' 0x3F */ 0x74, 0x42, 0x11, 0x10, 0x80, 0x20,
/* '@' 0x40 */ 0x0F, 0x06, 0x19, 0x3B, 0xC4, 0x99, 0x13, 0x22, 0x64, 0x96, 0x6E, 0x40, 0x04, 0x00, 0x7C, 0x00,
/* 'A' 0x41 */ 0x18, 0x18, 0x3C, 0x24, 0x24, 0x7E, 0x42, 0x42, 0xC3,
/* 'B' 0x42 */ 0xFA, 0x18, 0x61, 0xFA, 0x18, 0x61, 0xF8,
/* 'C' 0x43 */ 0x3E, 0x41, 0x80, 0x80, 0x80, 0x80, 0x81, 0x43, 0x3E,
/* 'D' 0x44 */ 0xF9, 0x0A, 0x0C, 0x18, 0x30, 0x60, 0xC2, 0xF8,
/* 'E' 0x45 */ 0xFE, 0x08, 0x20, 0xFA, 0x08, 0x20, 0xFC,
/* 'F' 0x46 */ 0xFC, 0x21, 0x0F, 0xC2, 0x10, 0x80,
/* 'G' 0x47 */ 0x3E, 0x41, 0x80, 0x80, 0x87, 0x81, 0xC1, 0x43, 0x3D,
/* 'H' 0x48 */ 0x83, 0x06, 0x0C, 0x1F, 0xF0, 0x60, 0xC1, 0x82,
/* 'I' 0x49 */ 0xFF, 0x80,
/* 'J' 0x4A */ 0x08, 0x42, 0x10, 0x86, 0x31, 0x70,
/* 'K' 0x4B */ 0x86, 0x29, 0x28, 0xD2, 0x48, 0xA1, 0x84,
/* 'L' 0x4C */ 0x84, 0x21, 0x08, 0x42, 0x10, 0xF8,
/* 'M' 0x4D */ 0xC3, 0xC3, 0xC3, 0xA5, 0xA5, 0xA5, 0x99, 0x99, 0x99,
/* 'N' 0x4E */ 0xC3, 0x86, 0x8D, 0x19, 0x33, 0x62, 0xC3, 0x86,
/* 'O' 0x4F */ 0x3E, 0x31, 0xB0, 0x70, 0x18, 0x0C, 0x07, 0x06, 0xC6, 0x3E, 0x00,
/* 'P' 0x50 */ 0xFA, 0x18, 0x61, 0xFA, 0x08, 0x20, 0x80,
/* 'Q' 0x51 */ 0x3E, 0x31, 0xB0, 0x70, 0x18, 0x0C, 0x07, 0x06, 0xC6, 0x3F, 0x00, 0x40,
/* 'R' 0x52 */ 0xF9, 0x0A, 0x14, 0x2F, 0x90, 0xA1, 0x42, 0x86,
/* 'S' 0x53 */ 0x7A, 0x18, 0x30, 0x78, 0x38, 0x71, 0x78,
/* 'T' 0x54 */ 0xFC, 0x41, 0x04, 0x10, 0x41, 0x04, 0x10,
/* 'U' 0x55 */ 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xE3, 0x7C,
/* 'V' 0x56 */ 0xC2, 0x85, 0x0B, 0x22, 0x44, 0x8E, 0x0C, 0x18,
/* 'W' 0x57 */ 0x84, 0x38, 0xCD, 0x29, 0x25, 0x24, 0xA4, 0xD2, 0x8C, 0x61, 0x8C, 0x31, 0x80,
/* 'X' 0x58 */ 0x87, 0x34, 0x8C, 0x30, 0xC4, 0xA3, 0x84,
/* 'Y' 0x59 */ 0x82, 0x89, 0x11, 0x43, 0x82, 0x04, 0x08, 0x10,
/* 'Z' 0x5A */ 0x7E, 0x0C, 0x30, 0x41, 0x06, 0x18, 0x20, 0xFE,
/* '[' 0x5B */ 0xEA, 0xAA, 0xAB,
/* '\' 0x5C */ 0x92, 0x24, 0x91, 0x20,
/* ']' 0x5D */ 0xD5, 0x55, 0x57,
/* '^' 0x5E */ 0x46, 0xA9, 0x10,
/* '_' 0x5F */ 0xFE,
/* '`' 0x60 */ 0x80,
/* 'a' 0x61 */ 0x79, 0x08, 0x11, 0xEC, 0x51, 0x9D, 0x80,
/* 'b' 0x62 */ 0x84, 0x3D, 0xB8, 0xC6, 0x3B, 0xF0,
/* 'c' 0x63 */ 0x7B, 0x18, 0x20, 0x83, 0x17, 0x80,
/* 'd' 0x64 */ 0x04, 0x17, 0xF3, 0x86, 0x18, 0x73, 0x74,
/* 'e' 0x65 */ 0x7B, 0x38, 0x7F, 0x83, 0x17, 0x80,
/* 'f' 0x66 */ 0x6B, 0xA4, 0x92, 0x40,
/* 'g' 0x67 */ 0x77, 0x38, 0x61, 0x87, 0x37, 0x41, 0x8D, 0xE0,
/* 'h' 0x68 */ 0x84, 0x2D, 0x98, 0xC6, 0x31, 0x88,
/* 'i' 0x69 */ 0xBF, 0x80,
/* 'j' 0x6A */ 0x45, 0x55, 0x57,
/* 'k' 0x6B */ 0x84, 0x25, 0x6E, 0x72, 0x52, 0x88,
/* 'l' 0x6C */ 0xFF, 0x80,
/* 'm' 0x6D */ 0xFF, 0x99, 0x91, 0x91, 0x91, 0x91, 0x91,
/* 'n' 0x6E */ 0xB6, 0x63, 0x18, 0xC6, 0x20,
/* 'o' 0x6F */ 0x7B, 0x38, 0x61, 0x87, 0x37, 0x80,
/* 'p' 0x70 */ 0xF6, 0xE3, 0x18, 0xEF, 0xD0, 0x80,
/* 'q' 0x71 */ 0x77, 0x38, 0x61, 0x87, 0x37, 0x41, 0x04,
/* 'r' 0x72 */ 0xBA, 0x49, 0x20,
/* 's' 0x73 */ 0x69, 0x8E, 0x19, 0x60,
/* 't' 0x74 */ 0x5D, 0x24, 0x93,
/* 'u' 0x75 */ 0x8C, 0x63, 0x18, 0xCD, 0xA0,
/* 'v' 0x76 */ 0x85, 0x24, 0x92, 0x30, 0xC3, 0x00,
/* 'w' 0x77 */ 0x89, 0x99, 0x59, 0x55, 0x56, 0x66, 0x26,
/* 'x' 0x78 */ 0x4A, 0x4C, 0x43, 0x27, 0x20,
/* 'y' 0x79 */ 0x85, 0x24, 0x92, 0x30, 0xC3, 0x08, 0x21, 0x80,
/* 'z' 0x7A */ 0x78, 0x44, 0x46, 0x23, 0xE0,
/* '{' 0x7B */ 0x69, 0x25, 0xB2, 0x49, 0x30,
/* '|' 0x7C */ 0xFF, 0xE0,
/* '}' 0x7D */ 0xC9, 0x24, 0xDA, 0x49, 0x60,
/* '~' 0x7E */ 0x66, 0x70,
};
const GFXglyph FreeSans6pt7bGlyphs[] PROGMEM = {
/* ' ' 0x20 */ {0, 0, 0, 3, 0, 0},
/* '!' 0x21 */ {0, 1, 9, 4, 2, -8},
/* '"' 0x22 */ {2, 3, 3, 4, 0, -8},
/* '#' 0x23 */ {4, 7, 8, 7, 0, -7},
/* '$' 0x24 */ {11, 5, 11, 7, 1, -9},
/* '%' 0x25 */ {18, 10, 9, 11, 0, -8},
/* '&' 0x26 */ {30, 6, 9, 8, 1, -8},
/* ''' 0x27 */ {37, 1, 3, 2, 1, -8},
/* '(' 0x28 */ {38, 2, 11, 4, 1, -8},
/* ')' 0x29 */ {41, 3, 11, 4, 0, -8},
/* '*' 0x2A */ {46, 3, 3, 5, 1, -8},
/* '+' 0x2B */ {48, 5, 5, 7, 1, -4},
/* ',' 0x2C */ {52, 1, 3, 3, 1, 0},
/* '-' 0x2D */ {53, 3, 1, 4, 1, -3},
/* '.' 0x2E */ {54, 1, 1, 3, 1, 0},
/* '/' 0x2F */ {55, 3, 9, 3, 0, -8},
/* '0' 0x30 */ {59, 5, 9, 7, 1, -8},
/* '1' 0x31 */ {65, 2, 9, 7, 2, -8},
/* '2' 0x32 */ {68, 5, 9, 7, 1, -8},
/* '3' 0x33 */ {74, 5, 9, 7, 1, -8},
/* '4' 0x34 */ {80, 5, 9, 7, 1, -8},
/* '5' 0x35 */ {86, 6, 9, 7, 0, -8},
/* '6' 0x36 */ {93, 5, 9, 7, 1, -8},
/* '7' 0x37 */ {99, 5, 9, 7, 1, -8},
/* '8' 0x38 */ {105, 6, 9, 7, 0, -8},
/* '9' 0x39 */ {112, 6, 9, 7, 0, -8},
/* ':' 0x3A */ {119, 1, 7, 3, 1, -6},
/* ';' 0x3B */ {120, 1, 8, 3, 1, -5},
/* '<' 0x3C */ {121, 5, 5, 7, 1, -4},
/* '=' 0x3D */ {125, 5, 3, 7, 1, -3},
/* '>' 0x3E */ {127, 5, 5, 7, 1, -4},
/* '?' 0x3F */ {131, 5, 9, 7, 1, -8},
/* '@' 0x40 */ {137, 11, 11, 12, 0, -8},
/* 'A' 0x41 */ {153, 8, 9, 8, 0, -8},
/* 'B' 0x42 */ {162, 6, 9, 8, 1, -8},
/* 'C' 0x43 */ {169, 8, 9, 9, 0, -8},
/* 'D' 0x44 */ {178, 7, 9, 8, 1, -8},
/* 'E' 0x45 */ {186, 6, 9, 8, 1, -8},
/* 'F' 0x46 */ {193, 5, 9, 7, 1, -8},
/* 'G' 0x47 */ {199, 8, 9, 9, 0, -8},
/* 'H' 0x48 */ {208, 7, 9, 9, 1, -8},
/* 'I' 0x49 */ {216, 1, 9, 3, 1, -8},
/* 'J' 0x4A */ {218, 5, 9, 6, 0, -8},
/* 'K' 0x4B */ {224, 6, 9, 8, 1, -8},
/* 'L' 0x4C */ {231, 5, 9, 7, 1, -8},
/* 'M' 0x4D */ {237, 8, 9, 10, 1, -8},
/* 'N' 0x4E */ {246, 7, 9, 9, 1, -8},
/* 'O' 0x4F */ {254, 9, 9, 9, 0, -8},
/* 'P' 0x50 */ {265, 6, 9, 8, 1, -8},
/* 'Q' 0x51 */ {272, 9, 10, 9, 0, -8},
/* 'R' 0x52 */ {284, 7, 9, 9, 1, -8},
/* 'S' 0x53 */ {292, 6, 9, 8, 1, -8},
/* 'T' 0x54 */ {299, 6, 9, 8, 0, -8},
/* 'U' 0x55 */ {306, 7, 9, 9, 1, -8},
/* 'V' 0x56 */ {314, 7, 9, 8, 0, -8},
/* 'W' 0x57 */ {322, 11, 9, 11, 0, -8},
/* 'X' 0x58 */ {335, 6, 9, 8, 1, -8},
/* 'Y' 0x59 */ {342, 7, 9, 8, 1, -8},
/* 'Z' 0x5A */ {350, 7, 9, 7, 0, -8},
/* '[' 0x5B */ {358, 2, 12, 3, 1, -8},
/* '\' 0x5C */ {361, 3, 9, 3, 0, -8},
/* ']' 0x5D */ {365, 2, 12, 3, 0, -8},
/* '^' 0x5E */ {368, 4, 5, 6, 1, -8},
/* '_' 0x5F */ {371, 7, 1, 7, 0, 2},
/* '`' 0x60 */ {372, 1, 1, 3, 1, -8},
/* 'a' 0x61 */ {373, 7, 7, 7, 0, -6},
/* 'b' 0x62 */ {380, 5, 9, 7, 1, -8},
/* 'c' 0x63 */ {386, 6, 7, 6, 0, -6},
/* 'd' 0x64 */ {392, 6, 9, 7, 0, -8},
/* 'e' 0x65 */ {399, 6, 7, 6, 0, -6},
/* 'f' 0x66 */ {405, 3, 9, 3, 0, -8},
/* 'g' 0x67 */ {409, 6, 10, 7, 0, -6},
/* 'h' 0x68 */ {417, 5, 9, 6, 1, -8},
/* 'i' 0x69 */ {423, 1, 9, 3, 1, -8},
/* 'j' 0x6A */ {425, 2, 12, 3, 0, -8},
/* 'k' 0x6B */ {428, 5, 9, 6, 1, -8},
/* 'l' 0x6C */ {434, 1, 9, 3, 1, -8},
/* 'm' 0x6D */ {436, 8, 7, 10, 1, -6},
/* 'n' 0x6E */ {443, 5, 7, 6, 1, -6},
/* 'o' 0x6F */ {448, 6, 7, 6, 0, -6},
/* 'p' 0x70 */ {454, 5, 9, 7, 1, -6},
/* 'q' 0x71 */ {460, 6, 9, 7, 0, -6},
/* 'r' 0x72 */ {467, 3, 7, 4, 1, -6},
/* 's' 0x73 */ {470, 4, 7, 6, 1, -6},
/* 't' 0x74 */ {474, 3, 8, 3, 0, -7},
/* 'u' 0x75 */ {477, 5, 7, 6, 1, -6},
/* 'v' 0x76 */ {482, 6, 7, 6, 0, -6},
/* 'w' 0x77 */ {488, 8, 7, 9, 0, -6},
/* 'x' 0x78 */ {495, 5, 7, 6, 0, -6},
/* 'y' 0x79 */ {500, 6, 10, 6, 0, -6},
/* 'z' 0x7A */ {508, 5, 7, 6, 0, -6},
/* '{' 0x7B */ {513, 3, 12, 4, 0, -8},
/* '|' 0x7C */ {518, 1, 11, 3, 1, -8},
/* '}' 0x7D */ {520, 3, 12, 4, 1, -8},
/* '~' 0x7E */ {525, 6, 2, 6, 0, -4},
};
const GFXfont FreeSans6pt7b PROGMEM = {(uint8_t *)FreeSans6pt7bBitmaps, (GFXglyph *)FreeSans6pt7bGlyphs, 0x20, 0x7E, 14};
@@ -0,0 +1,527 @@
// trunk-ignore-all(clang-format)
#pragma once
/* PROPERTIES
FONT_NAME FreeSans6pt_Win1250
*/
const uint8_t FreeSans6pt_Win1250Bitmaps[] PROGMEM = {
/* 0x01 */ 0x1C, 0x0A, 0x05, 0x04, 0xFE, 0x08, 0x1C, 0x02, 0x07, 0xE0, 0x9F, 0xC0,
/* 0x02 */ 0x3F, 0xF0, 0x40, 0xE0, 0x10, 0x3F, 0x04, 0x9E, 0x28, 0x14, 0x0E, 0x00,
/* 0x03 */ 0x3F, 0x10, 0x28, 0x06, 0x49, 0x80, 0x60, 0x19, 0x26, 0x31, 0x40, 0x8F, 0xC0,
/* 0x04 */ 0x3F, 0x10, 0x2A, 0x16, 0x49, 0xA1, 0x60, 0x19, 0xE6, 0x31, 0x40, 0x8F, 0xC0,
/* 0x05 */ 0x28, 0x15, 0x2A, 0xB5, 0x55, 0xA8, 0x54, 0x12, 0x04, 0x41, 0x08, 0x81, 0xC0,
/* 0x06 */ 0x04, 0x08, 0x88, 0x82, 0x07, 0x01, 0x11, 0xA2, 0xC4, 0x40, 0x70, 0x20, 0x88, 0x88, 0x10, 0x00,
/* 0x07 */
/* 0x08 */ 0x03, 0x83, 0x44, 0x48, 0x28, 0x01, 0x80, 0x17, 0xFE, 0x08, 0x45, 0x28, 0x84, 0x00,
/* 0x09 */ 0x01, 0xC0, 0x68, 0x82, 0x41, 0x10, 0x02, 0x80, 0x06, 0x00, 0x14, 0x00, 0x8F, 0xFC,
/* 0x0A */
/* 0x0B */ 0x22, 0x2A, 0xA2, 0x30, 0x18, 0x0A, 0x09, 0x04, 0x44, 0x14, 0x04, 0x00,
/* 0x0C */ 0x46, 0x00, 0x19, 0x03, 0x21, 0x20, 0x93, 0x04, 0x20, 0x11, 0x80, 0x50, 0x02, 0x7F, 0xE0,
/* 0x0D */
/* 0x0E */ 0x08, 0x0E, 0x08, 0x88, 0x24, 0x12, 0x09, 0x05, 0x01, 0xFF, 0x8A, 0x02, 0x00,
/* 0x0F */ 0x3F, 0x14, 0xAA, 0x16, 0x01, 0x92, 0x60, 0x18, 0xC6, 0x49, 0x40, 0x8F, 0xC0,
/* 0x10 */ 0x1B, 0x02, 0xA0, 0x54, 0x12, 0x42, 0x48, 0x49, 0x31, 0x1E, 0x23, 0xEA, 0xFE, 0x3C,
/* 0x11 */ 0x3F, 0x02, 0x00, 0x20, 0x6D, 0x27, 0xF8, 0x3F, 0xC1, 0xFE, 0x37, 0xD0, 0xBE, 0x40, 0xE1, 0xE2, 0x00,
/* 0x12 */ 0x12, 0x42, 0x20, 0x24, 0xC0, 0x29, 0x99, 0x05, 0x23, 0x30, 0xB0, 0x30, 0x00,
/* 0x13 */ 0x3F, 0x88, 0x0A, 0x44, 0xD5, 0x58, 0x03, 0x00, 0x67, 0xCC, 0x71, 0x40, 0x47, 0xF0,
/* 0x14 */ 0x3F, 0x18, 0x69, 0x26, 0x85, 0xA1, 0x6C, 0xD8, 0x06, 0x31, 0x40, 0x8F, 0xC0,
/* 0x15 */ 0x3F, 0x11, 0x00, 0xE8, 0x03, 0xA0, 0x1F, 0xB3, 0x7E, 0x00, 0xE9, 0xE0, 0x23, 0x00, 0x40, 0x40, 0xFE, 0x00,
/* 0x16 */ 0x30, 0x38, 0x3A, 0x3E, 0x6E, 0xEB, 0xC3, 0xC3, 0x66, 0x3C,
/* 0x17 */ 0x3F, 0x04, 0x00, 0x82, 0x88, 0x5C, 0xA4, 0x49, 0x22, 0x81, 0x98, 0xC4, 0x40, 0xA3, 0xF0,
/* 0x18 */ 0x07, 0x80, 0x42, 0x04, 0x08, 0x21, 0x41, 0x42, 0x60, 0x0E, 0x8C, 0xB2, 0x89, 0x50, 0x52, 0x82, 0x80,
/* 0x19 */ 0x3F, 0xC4, 0x02, 0x80, 0x18, 0x01, 0xB3, 0x1B, 0xB9, 0x80, 0x19, 0xE1, 0x40, 0x23, 0xFC,
/* 0x1A */ 0xFF, 0xC0, 0x67, 0x34, 0x58, 0x4C, 0x46, 0x03, 0x11, 0x80, 0xFF, 0xC0,
/* 0x1B */ 0x0F, 0xC0, 0x40, 0x82, 0x49, 0x08, 0x04, 0x00, 0x00, 0x12, 0x02, 0x31, 0x34, 0x0B, 0x88, 0x45, 0x00, 0x20,
/* 0x1C */ 0x3F, 0x88, 0x0A, 0x44, 0xC9, 0x19, 0x3B, 0x00, 0x60, 0x4C, 0x71, 0x40, 0x47, 0xF0,
/* 0x1D */ 0x3F, 0x8B, 0x0A, 0x00, 0xC8, 0x18, 0x13, 0x00, 0x48, 0xCA, 0xC1, 0x44, 0x53, 0x30,
/* 0x1E */ 0x19, 0xC2, 0x02, 0x50, 0x1E, 0x49, 0x80, 0x12, 0x01, 0x27, 0x92, 0x01, 0x10, 0x20, 0xFC,
/* 0x1F */ 0x30, 0x1C, 0x0C, 0x3E, 0x7E, 0xCF, 0x07, 0xC7, 0x7F, 0x3F,
/* ' ' 0x20 */
/* '!' 0x21 */ 0xFC, 0x80,
/* '"' 0x22 */ 0xB6, 0x80,
/* '#' 0x23 */ 0x24, 0x51, 0xF9, 0x42, 0x9F, 0x92, 0x28,
/* '$' 0x24 */ 0x10, 0xE5, 0x55, 0x50, 0xE1, 0x65, 0x55, 0xE1, 0x00,
/* '%' 0x25 */ 0x71, 0x24, 0x89, 0x22, 0x50, 0x74, 0x02, 0x70, 0xA4, 0x49, 0x11, 0xC0,
/* '&' 0x26 */ 0x71, 0x24, 0x9C, 0x62, 0x58, 0xA7, 0xF4,
/* ''' 0x27 */ 0xE0,
/* '(' 0x28 */ 0x5A, 0xAA, 0x94,
/* ')' 0x29 */ 0x89, 0x12, 0x49, 0x29, 0x00,
/* '*' 0x2A */ 0x5E, 0x80,
/* '+' 0x2B */ 0x21, 0x3E, 0x42, 0x00,
/* ',' 0x2C */ 0xE0,
/* '-' 0x2D */ 0xC0,
/* '.' 0x2E */ 0x80,
/* '/' 0x2F */ 0x24, 0xA4, 0xA4, 0x80,
/* '0' 0x30 */ 0x76, 0xE3, 0x18, 0xC6, 0x3B, 0x70,
/* '1' 0x31 */ 0x27, 0x92, 0x49, 0x20,
/* '2' 0x32 */ 0x79, 0x10, 0x41, 0x08, 0xC6, 0x10, 0xFC,
/* '3' 0x33 */ 0x79, 0x30, 0x43, 0x18, 0x10, 0x71, 0x78,
/* '4' 0x34 */ 0x08, 0x61, 0x8A, 0x49, 0x2F, 0xC2, 0x08,
/* '5' 0x35 */ 0xFC, 0x21, 0xE8, 0x84, 0x31, 0xF0,
/* '6' 0x36 */ 0x74, 0x61, 0xE8, 0xC6, 0x31, 0x70,
/* '7' 0x37 */ 0xF8, 0x44, 0x22, 0x11, 0x08, 0x40,
/* '8' 0x38 */ 0x39, 0x34, 0x53, 0x39, 0x1C, 0x51, 0x38,
/* '9' 0x39 */ 0x39, 0x3C, 0x71, 0x4C, 0xF0, 0x53, 0x78,
/* ':' 0x3A */ 0x82,
/* ';' 0x3B */ 0x87,
/* '<' 0x3C */ 0x3E, 0x30, 0x60, 0x80,
/* '=' 0x3D */ 0xF8, 0x3E,
/* '>' 0x3E */ 0xE0, 0xC6, 0xC8, 0x00,
/* '?' 0x3F */ 0x74, 0x42, 0x11, 0x10, 0x80, 0x20,
/* '@' 0x40 */ 0x0F, 0x86, 0x19, 0x9A, 0xA4, 0xD9, 0x13, 0x22, 0x56, 0xDA, 0x6E, 0x60, 0x06, 0x00, 0x3C, 0x00,
/* 'A' 0x41 */ 0x18, 0x18, 0x24, 0x24, 0x24, 0x7E, 0x42, 0x42, 0xC3,
/* 'B' 0x42 */ 0xFA, 0x18, 0x61, 0xFA, 0x18, 0x61, 0xFC,
/* 'C' 0x43 */ 0x3E, 0x63, 0x40, 0x40, 0xC0, 0x40, 0x41, 0x63, 0x3E,
/* 'D' 0x44 */ 0xF9, 0x0A, 0x1C, 0x18, 0x30, 0x61, 0xC2, 0xF8,
/* 'E' 0x45 */ 0xFE, 0x08, 0x20, 0xFE, 0x08, 0x20, 0xFC,
/* 'F' 0x46 */ 0xFE, 0x08, 0x20, 0xFA, 0x08, 0x20, 0x80,
/* 'G' 0x47 */ 0x1E, 0x61, 0x40, 0x40, 0xC7, 0x41, 0x41, 0x63, 0x1D,
/* 'H' 0x48 */ 0x83, 0x06, 0x0C, 0x1F, 0xF0, 0x60, 0xC1, 0x82,
/* 'I' 0x49 */ 0xFF, 0x80,
/* 'J' 0x4A */ 0x08, 0x42, 0x10, 0x87, 0x29, 0x70,
/* 'K' 0x4B */ 0x85, 0x12, 0x45, 0x0D, 0x13, 0x22, 0x42, 0x86,
/* 'L' 0x4C */ 0x84, 0x21, 0x08, 0x42, 0x10, 0xF8,
/* 'M' 0x4D */ 0xC3, 0xC3, 0xC3, 0xA5, 0xA5, 0xA5, 0x99, 0x99, 0x99,
/* 'N' 0x4E */ 0x83, 0x86, 0x8D, 0x19, 0x33, 0x62, 0xC3, 0x86,
/* 'O' 0x4F */ 0x1E, 0x31, 0x90, 0x68, 0x1C, 0x0A, 0x05, 0x06, 0xC6, 0x1E, 0x00,
/* 'P' 0x50 */ 0xFA, 0x18, 0x61, 0xFA, 0x08, 0x20, 0x80,
/* 'Q' 0x51 */ 0x1E, 0x31, 0x90, 0x68, 0x1C, 0x0A, 0x05, 0x16, 0xC6, 0x1F, 0x00, 0x40,
/* 'R' 0x52 */ 0xFD, 0x0E, 0x1C, 0x2F, 0x90, 0xA1, 0x42, 0x86,
/* 'S' 0x53 */ 0x7A, 0x18, 0x30, 0x78, 0x38, 0x61, 0x78,
/* 'T' 0x54 */ 0xFE, 0x20, 0x40, 0x81, 0x02, 0x04, 0x08, 0x10,
/* 'U' 0x55 */ 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xE2, 0x78,
/* 'V' 0x56 */ 0xC2, 0x85, 0x0B, 0x22, 0x44, 0x8E, 0x0C, 0x18,
/* 'W' 0x57 */ 0xC4, 0x28, 0xCD, 0x29, 0x25, 0x24, 0xA4, 0x52, 0x8C, 0x61, 0x8C, 0x31, 0x80,
/* 'X' 0x58 */ 0x87, 0x34, 0x8C, 0x30, 0xC4, 0xA3, 0x84,
/* 'Y' 0x59 */ 0xC3, 0x42, 0x24, 0x34, 0x18, 0x08, 0x08, 0x08, 0x08,
/* 'Z' 0x5A */ 0x7E, 0x0C, 0x30, 0x41, 0x06, 0x18, 0x20, 0xFE,
/* '[' 0x5B */ 0xEA, 0xAA, 0xAB,
/* '\' 0x5C */ 0x92, 0x24, 0x89, 0x20,
/* ']' 0x5D */ 0xD5, 0x55, 0x57,
/* '^' 0x5E */ 0x46, 0xA9,
/* '_' 0x5F */ 0xFE,
/* '`' 0x60 */ 0x80,
/* 'a' 0x61 */ 0x79, 0x20, 0x4F, 0xC6, 0x37, 0x40,
/* 'b' 0x62 */ 0x84, 0x3D, 0x18, 0xC6, 0x31, 0xF0,
/* 'c' 0x63 */ 0x39, 0x3C, 0x20, 0xC1, 0x33, 0x80,
/* 'd' 0x64 */ 0x04, 0x13, 0xD3, 0xC6, 0x1C, 0x53, 0x3C,
/* 'e' 0x65 */ 0x39, 0x38, 0x7F, 0x81, 0x13, 0x80,
/* 'f' 0x66 */ 0x6B, 0xA4, 0x92, 0x40,
/* 'g' 0x67 */ 0x35, 0x3C, 0x61, 0xC5, 0x33, 0x41, 0x4D, 0xE0,
/* 'h' 0x68 */ 0x84, 0x3D, 0x38, 0xC6, 0x31, 0x88,
/* 'i' 0x69 */ 0xBF, 0x80,
/* 'j' 0x6A */ 0x45, 0x55, 0x57,
/* 'k' 0x6B */ 0x84, 0x25, 0x4E, 0x52, 0xD2, 0x88,
/* 'l' 0x6C */ 0xFF, 0x80,
/* 'm' 0x6D */ 0xF7, 0x99, 0x91, 0x91, 0x91, 0x91, 0x91,
/* 'n' 0x6E */ 0xF4, 0x63, 0x18, 0xC6, 0x20,
/* 'o' 0x6F */ 0x39, 0x3C, 0x61, 0xC5, 0x33, 0x80,
/* 'p' 0x70 */ 0xF4, 0x63, 0x18, 0xC7, 0xD0, 0x80,
/* 'q' 0x71 */ 0x3D, 0x3C, 0x61, 0xC5, 0x37, 0x41, 0x04,
/* 'r' 0x72 */ 0xF2, 0x49, 0x20,
/* 's' 0x73 */ 0x7A, 0x50, 0xE0, 0xE5, 0xE0,
/* 't' 0x74 */ 0x5D, 0x24, 0x93,
/* 'u' 0x75 */ 0x8C, 0x63, 0x18, 0xCF, 0xA0,
/* 'v' 0x76 */ 0x85, 0x24, 0x92, 0x30, 0xC3, 0x00,
/* 'w' 0x77 */ 0x89, 0x59, 0x59, 0x55, 0x56, 0x26, 0x26,
/* 'x' 0x78 */ 0x4A, 0x4C, 0x43, 0x27, 0x20,
/* 'y' 0x79 */ 0x8A, 0x52, 0xA5, 0x18, 0x84, 0x22, 0x00,
/* 'z' 0x7A */ 0x78, 0x44, 0x46, 0x23, 0xE0,
/* '{' 0x7B */ 0x6A, 0xAA, 0xA9,
/* '|' 0x7C */ 0xFF, 0xE0,
/* '}' 0x7D */ 0x95, 0x55, 0x56,
/* '~' 0x7E */ 0x66, 0x60,
/* 0x7F */ 0xFF, 0xC0, 0x67, 0x34, 0x58, 0x4C, 0x46, 0x03, 0x11, 0x80, 0xFF, 0xC0,
/* 0x80 */ 0x1C, 0x45, 0x07, 0xE4, 0x1F, 0x10, 0x10, 0x1E,
/* 0x81 */
/* 0x82 */ 0xE0,
/* 0x83 */
/* 0x84 */ 0xB6, 0x80,
/* 0x85 */ 0xA8,
/* 0x86 */ 0x21, 0x09, 0xF2, 0x10, 0x84, 0x21, 0x08,
/* 0x87 */ 0x21, 0x09, 0xF2, 0x10, 0x84, 0xF9, 0x08,
/* 0x88 */
/* 0x89 */ 0x62, 0x09, 0x40, 0x98, 0x06, 0x80, 0x10, 0x01, 0x66, 0x29, 0x92, 0x99, 0x06, 0x60,
/* 0x8A */ 0x28, 0x47, 0xA1, 0x83, 0x07, 0x83, 0x87, 0x17, 0x80,
/* 0x8B */ 0x64,
/* 0x8C */ 0x10, 0x87, 0xA1, 0x83, 0x07, 0x83, 0x87, 0x17, 0x80,
/* 0x8D */ 0x28, 0x4F, 0xC4, 0x10, 0x41, 0x04, 0x10, 0x40,
/* 0x8E */ 0x14, 0x11, 0xF8, 0x30, 0xC1, 0x04, 0x18, 0x61, 0xFC,
/* 0x8F */ 0x08, 0x21, 0xF8, 0x30, 0xC1, 0x04, 0x18, 0x61, 0xFC,
/* 0x90 */
/* 0x91 */ 0xE0,
/* 0x92 */ 0xE0,
/* 0x93 */ 0xB6, 0x80,
/* 0x94 */ 0xB6, 0x80,
/* 0x95 */ 0xFF, 0x80,
/* 0x96 */ 0xFC,
/* 0x97 */ 0xFF, 0xF0,
/* 0x98 */
/* 0x99 */ 0xE6, 0x28, 0xCD, 0x19, 0xA3, 0x34, 0x6A, 0x8B, 0x51, 0x68,
/* 0x9A */ 0x52, 0x69, 0x8E, 0x19, 0x60,
/* 0x9B */ 0x98,
/* 0x9C */ 0x24, 0x06, 0x98, 0xE1, 0x96,
/* 0x9D */ 0x15, 0xE4, 0x44, 0x44, 0x60,
/* 0x9E */ 0x51, 0x00, 0xF0, 0x88, 0x8C, 0x47, 0xC0,
/* 0x9F */ 0x11, 0x00, 0xF0, 0x88, 0x8C, 0x47, 0xC0,
/* 0xA0 */
/* 0xA1 */ 0xA8,
/* 0xA2 */ 0x96,
/* 0xA3 */ 0x41, 0x05, 0x18, 0x43, 0x04, 0x10, 0x7C,
/* 0xA4 */ 0xFC, 0x63, 0xF0,
/* 0xA5 */ 0x30, 0x38, 0x28, 0x48, 0x4C, 0x7C, 0x84, 0x86, 0x82, 0x04, 0x07,
/* 0xA6 */ 0xF9, 0xF0,
/* 0xA7 */ 0x32, 0x91, 0xC9, 0x47, 0x26, 0x14, 0xA4, 0xC0,
/* 0xA8 */ 0xA0,
/* 0xA9 */ 0x3E, 0x3F, 0xB8, 0xF4, 0x1A, 0x0D, 0x17, 0x76, 0xC6, 0x3E, 0x00,
/* 0xAA */ 0x7A, 0x18, 0x30, 0x78, 0x38, 0x61, 0x78, 0xC1, 0x0C,
/* 0xAB */ 0x5A, 0xA5,
/* 0xAC */ 0xFC, 0x10, 0x40,
/* 0xAD */
/* 0xAE */ 0x3E, 0x31, 0xB7, 0x72, 0x99, 0xCC, 0xC7, 0x56, 0xC6, 0x3E, 0x00,
/* 0xAF */ 0x18, 0x31, 0xF8, 0x30, 0xC1, 0x04, 0x18, 0x61, 0xFC,
/* 0xB0 */ 0x69, 0x96,
/* 0xB1 */ 0x21, 0x3E, 0x42, 0x03, 0xE0,
/* 0xB2 */ 0x9C,
/* 0xB3 */ 0x49, 0x35, 0x92, 0x40,
/* 0xB4 */ 0x80,
/* 0xB5 */ 0x8A, 0x28, 0xA2, 0x8A, 0x6E, 0xE0, 0x80,
/* 0xB6 */ 0x7F, 0xAE, 0xBA, 0x68, 0xA2, 0x8A, 0x28, 0xA0,
/* 0xB7 */ 0x80,
/* 0xB8 */ 0x67, 0x80,
/* 0xB9 */ 0x78, 0x84, 0x04, 0x3C, 0xC4, 0x8C, 0x76, 0x04, 0x07,
/* 0xBA */ 0x69, 0x8E, 0x19, 0x66, 0x26,
/* 0xBB */ 0xA5, 0x5A,
/* 0xBC */ 0xA5, 0x21, 0x08, 0x42, 0x10, 0xF8,
/* 0xBD */ 0xA0,
/* 0xBE */ 0xBA, 0x49, 0x24, 0x90,
/* 0xBF */ 0x31, 0x9E, 0x11, 0x11, 0x88, 0xF8,
/* 0xC0 */ 0x10, 0x43, 0xE4, 0x28, 0x50, 0xBE, 0x42, 0x85, 0x0C,
/* 0xC1 */ 0x08, 0x10, 0x00, 0x18, 0x3C, 0x24, 0x24, 0x7E, 0x42, 0xC3,
/* 0xC2 */ 0x18, 0x24, 0x00, 0x18, 0x3C, 0x24, 0x24, 0x7E, 0x42, 0xC3,
/* 0xC3 */ 0x24, 0x18, 0x00, 0x18, 0x3C, 0x24, 0x24, 0x7E, 0x42, 0xC3,
/* 0xC4 */ 0x24, 0x00, 0x18, 0x3C, 0x24, 0x24, 0x7E, 0x42, 0x42, 0xC3,
/* 0xC5 */ 0x11, 0x21, 0x08, 0x42, 0x10, 0x87, 0xC0,
/* 0xC6 */ 0x08, 0x20, 0x01, 0xE4, 0x30, 0x20, 0x40, 0x82, 0x8C, 0xF0,
/* 0xC7 */ 0x3E, 0x61, 0xC0, 0x80, 0x80, 0x80, 0xC1, 0x63, 0x3E, 0x0C, 0x04, 0x1C,
/* 0xC8 */ 0x28, 0x20, 0x01, 0xE4, 0x30, 0x20, 0x40, 0x82, 0x8C, 0xF0,
/* 0xC9 */ 0x08, 0x40, 0x3F, 0x82, 0x0F, 0xA0, 0x83, 0xF0,
/* 0xCA */ 0xFD, 0x02, 0x04, 0x0F, 0xD0, 0x20, 0x40, 0xFC, 0x10, 0x38,
/* 0xCB */ 0x28, 0x0F, 0xE0, 0x83, 0xE8, 0x20, 0x83, 0xF0,
/* 0xCC */ 0x28, 0x40, 0x3F, 0x82, 0x0F, 0xA0, 0x82, 0x0F, 0xC0,
/* 0xCD */ 0x62, 0xAA, 0xA0,
/* 0xCE */ 0x54, 0x24, 0x92, 0x48,
/* 0xCF */ 0x50, 0x43, 0xE4, 0x28, 0x30, 0x60, 0xC1, 0x85, 0xF0,
/* 0xD0 */ 0x7C, 0x42, 0x41, 0x41, 0xF1, 0x41, 0x41, 0x42, 0x7C,
/* 0xD1 */ 0x08, 0x23, 0x0F, 0x1B, 0x32, 0x66, 0xC7, 0x87, 0x04,
/* 0xD2 */ 0x28, 0x23, 0x0F, 0x1B, 0x32, 0x66, 0xC7, 0x87, 0x04,
/* 0xD3 */ 0x04, 0x04, 0x0F, 0x8C, 0x6C, 0x1C, 0x06, 0x03, 0x83, 0x63, 0x1F, 0x00,
/* 0xD4 */ 0x08, 0x0A, 0x00, 0x07, 0xC6, 0x36, 0x0E, 0x03, 0x01, 0xC1, 0xB1, 0x8F, 0x80,
/* 0xD5 */ 0x0A, 0x0A, 0x00, 0x07, 0xC6, 0x36, 0x0E, 0x03, 0x01, 0xC1, 0xB1, 0x8F, 0x80,
/* 0xD6 */ 0x14, 0x00, 0x00, 0x07, 0xC6, 0x36, 0x0E, 0x03, 0x01, 0xC1, 0xB1, 0x8F, 0x80,
/* 0xD7 */ 0x8A, 0x88, 0xA8, 0x80,
/* 0xD8 */ 0x50, 0x43, 0xE4, 0x28, 0x50, 0xBE, 0x42, 0x85, 0x0C,
/* 0xD9 */ 0x10, 0x52, 0x4C, 0x18, 0x30, 0x60, 0xC1, 0xC6, 0xF8,
/* 0xDA */ 0x08, 0x22, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0xC6, 0xF8,
/* 0xDB */ 0x14, 0x52, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0xC6, 0xF8,
/* 0xDC */ 0x29, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0xC6, 0xF8,
/* 0xDD */ 0x09, 0x25, 0x12, 0x22, 0x87, 0x04, 0x08, 0x10, 0x20,
/* 0xDE */ 0xFC, 0x41, 0x04, 0x10, 0x41, 0x04, 0x10, 0x60, 0x8E,
/* 0xDF */ 0x7A, 0x18, 0x61, 0x8A, 0x18, 0x61, 0xB8,
/* 0xE0 */ 0x42, 0xE9, 0x24, 0x80,
/* 0xE1 */ 0x10, 0x40, 0x03, 0xC8, 0x40, 0x8F, 0x62, 0x8C, 0xEC,
/* 0xE2 */ 0x10, 0x50, 0x03, 0xC8, 0x40, 0x8F, 0x62, 0x8C, 0xEC,
/* 0xE3 */ 0x48, 0x60, 0x03, 0xC8, 0x40, 0x8F, 0x62, 0x8C, 0xEC,
/* 0xE4 */ 0x28, 0x01, 0xE4, 0x20, 0x47, 0xB1, 0x46, 0x76,
/* 0xE5 */ 0x62, 0xAA, 0xA0,
/* 0xE6 */ 0x10, 0x80, 0x1E, 0xC6, 0x08, 0x20, 0xC5, 0xE0,
/* 0xE7 */ 0x7B, 0x18, 0x20, 0x83, 0x17, 0x8C, 0x11, 0xC0,
/* 0xE8 */ 0x28, 0x40, 0x1E, 0xC6, 0x08, 0x20, 0xC5, 0xE0,
/* 0xE9 */ 0x10, 0x80, 0x1E, 0xCE, 0x1F, 0xE0, 0xC5, 0xE0,
/* 0xEA */ 0x7B, 0x38, 0x7F, 0x83, 0x37, 0x84, 0x1C,
/* 0xEB */ 0x28, 0x07, 0xB3, 0x87, 0xF8, 0x31, 0x78,
/* 0xEC */ 0x28, 0x40, 0x1E, 0xCE, 0x1F, 0xE0, 0xC5, 0xE0,
/* 0xED */ 0x62, 0xAA, 0xA0,
/* 0xEE */ 0x54, 0x24, 0x92, 0x48,
/* 0xEF */ 0x02, 0x0C, 0x13, 0xEC, 0xD0, 0xA1, 0x42, 0xCC, 0xE8,
/* 0xF0 */ 0x04, 0x1D, 0xD6, 0x68, 0x50, 0xA1, 0x66, 0x74,
/* 0xF1 */ 0x11, 0x01, 0x6C, 0xC6, 0x31, 0x8C, 0x40,
/* 0xF2 */ 0x20, 0x81, 0x6C, 0xC6, 0x31, 0x8C, 0x40,
/* 0xF3 */ 0x10, 0x80, 0x1E, 0xCE, 0x18, 0x61, 0xCD, 0xE0,
/* 0xF4 */ 0x10, 0xA0, 0x1E, 0xCE, 0x18, 0x61, 0xCD, 0xE0,
/* 0xF5 */ 0x29, 0x40, 0x1E, 0xCE, 0x18, 0x61, 0xCD, 0xE0,
/* 0xF6 */ 0x28, 0x07, 0xB3, 0x86, 0x18, 0x73, 0x78,
/* 0xF7 */ 0x20, 0x3E, 0x02, 0x00,
/* 0xF8 */ 0xA8, 0x5D, 0x24, 0x90,
/* 0xF9 */ 0x22, 0x89, 0x18, 0xC6, 0x31, 0x9B, 0x40,
/* 0xFA */ 0x11, 0x23, 0x18, 0xC6, 0x33, 0x68,
/* 0xFB */ 0x2A, 0x81, 0x18, 0xC6, 0x31, 0x9B, 0x40,
/* 0xFC */ 0x50, 0x23, 0x18, 0xC6, 0x33, 0x68,
/* 0xFD */ 0x10, 0x88, 0x52, 0x49, 0x23, 0x0C, 0x30, 0x82, 0x18,
/* 0xFE */ 0x4E, 0x44, 0x44, 0x46, 0x31, 0x70,
/* 0xFF */ 0x80,
};
const GFXglyph FreeSans6pt_Win1250Glyphs[] PROGMEM = {
/* 0x01 */ { 0, 9, 10, 11, 1, -9 },
/* 0x02 */ { 12, 9, 10, 11, 1, -8 },
/* 0x03 */ { 24, 10, 10, 12, 1, -8 },
/* 0x04 */ { 37, 10, 10, 12, 1, -8 },
/* 0x05 */ { 50, 10, 10, 12, 1, -9 },
/* 0x06 */ { 63, 11, 11, 13, 1, -9 },
/* 0x07 */ { 79, 0, 0, 8, 0, 0 },
/* 0x08 */ { 79, 12, 9, 14, 1, -8 },
/* 0x09 */ { 93, 14, 8, 16, 1, -7 },
/* 0x0A */ { 107, 0, 0, 8, 0, 0 },
/* 0x0B */ { 107, 9, 10, 11, 1, -9 },
/* 0x0C */ { 119, 13, 9, 15, 1, -8 },
/* 0x0D */ { 134, 0, 0, 8, 0, 0 },
/* 0x0E */ { 134, 9, 11, 11, 1, -9 },
/* 0x0F */ { 147, 10, 10, 12, 1, -9 },
/* 0x10 */ { 160, 11, 10, 13, 1, -9 },
/* 0x11 */ { 174, 13, 10, 15, 1, -9 },
/* 0x12 */ { 191, 10, 10, 12, 1, -9 },
/* 0x13 */ { 204, 11, 10, 13, 1, -9 },
/* 0x14 */ { 218, 10, 10, 12, 1, -9 },
/* 0x15 */ { 231, 14, 10, 16, 1, -9 },
/* 0x16 */ { 249, 8, 10, 10, 1, -9 },
/* 0x17 */ { 259, 12, 10, 14, 1, -9 },
/* 0x18 */ { 274, 13, 10, 15, 1, -9 },
/* 0x19 */ { 291, 12, 10, 14, 1, -9 },
/* 0x1A */ { 306, 9, 10, 11, 1, -8 },
/* 0x1B */ { 318, 14, 10, 16, 1, -9 },
/* 0x1C */ { 336, 11, 10, 13, 1, -9 },
/* 0x1D */ { 350, 11, 10, 13, 1, -9 },
/* 0x1E */ { 364, 12, 10, 14, 1, -9 },
/* 0x1F */ { 379, 8, 10, 11, 2, -9 },
/* ' ' 0x20 */ { 389, 0, 0, 3, 0, 0 },
/* '!' 0x21 */ { 389, 1, 9, 4, 2, -8 },
/* '"' 0x22 */ { 391, 3, 3, 4, 0, -8 },
/* '#' 0x23 */ { 393, 7, 8, 7, 0, -7 },
/* '$' 0x24 */ { 400, 6, 11, 7, 0, -9 },
/* '%' 0x25 */ { 409, 10, 9, 11, 0, -8 },
/* '&' 0x26 */ { 421, 6, 9, 8, 1, -8 },
/* ''' 0x27 */ { 428, 1, 3, 2, 1, -8 },
/* '(' 0x28 */ { 429, 2, 11, 4, 1, -8 },
/* ')' 0x29 */ { 432, 3, 11, 4, 0, -8 },
/* '*' 0x2A */ { 437, 3, 3, 5, 1, -8 },
/* '+' 0x2B */ { 439, 5, 5, 7, 1, -4 },
/* ',' 0x2C */ { 443, 1, 3, 3, 1, 0 },
/* '-' 0x2D */ { 444, 2, 1, 4, 1, -3 },
/* '.' 0x2E */ { 445, 1, 1, 3, 1, 0 },
/* '/' 0x2F */ { 446, 3, 9, 3, 0, -8 },
/* '0' 0x30 */ { 450, 5, 9, 7, 1, -8 },
/* '1' 0x31 */ { 456, 3, 9, 7, 1, -8 },
/* '2' 0x32 */ { 460, 6, 9, 7, 0, -8 },
/* '3' 0x33 */ { 467, 6, 9, 7, 0, -8 },
/* '4' 0x34 */ { 474, 6, 9, 7, 0, -8 },
/* '5' 0x35 */ { 481, 5, 9, 7, 1, -8 },
/* '6' 0x36 */ { 487, 5, 9, 7, 1, -8 },
/* '7' 0x37 */ { 493, 5, 9, 7, 1, -8 },
/* '8' 0x38 */ { 499, 6, 9, 7, 0, -8 },
/* '9' 0x39 */ { 506, 6, 9, 7, 0, -8 },
/* ':' 0x3A */ { 513, 1, 7, 3, 1, -6 },
/* ';' 0x3B */ { 514, 1, 8, 3, 1, -5 },
/* '<' 0x3C */ { 515, 5, 5, 7, 1, -4 },
/* '=' 0x3D */ { 519, 5, 3, 7, 1, -3 },
/* '>' 0x3E */ { 521, 5, 5, 7, 1, -4 },
/* '?' 0x3F */ { 525, 5, 9, 7, 1, -8 },
/* '@' 0x40 */ { 531, 11, 11, 12, 0, -8 },
/* 'A' 0x41 */ { 547, 8, 9, 8, 0, -8 },
/* 'B' 0x42 */ { 556, 6, 9, 8, 1, -8 },
/* 'C' 0x43 */ { 563, 8, 9, 9, 0, -8 },
/* 'D' 0x44 */ { 572, 7, 9, 8, 1, -8 },
/* 'E' 0x45 */ { 580, 6, 9, 8, 1, -8 },
/* 'F' 0x46 */ { 587, 6, 9, 7, 1, -8 },
/* 'G' 0x47 */ { 594, 8, 9, 9, 0, -8 },
/* 'H' 0x48 */ { 603, 7, 9, 9, 1, -8 },
/* 'I' 0x49 */ { 611, 1, 9, 3, 1, -8 },
/* 'J' 0x4A */ { 613, 5, 9, 6, 0, -8 },
/* 'K' 0x4B */ { 619, 7, 9, 8, 1, -8 },
/* 'L' 0x4C */ { 627, 5, 9, 7, 1, -8 },
/* 'M' 0x4D */ { 633, 8, 9, 10, 1, -8 },
/* 'N' 0x4E */ { 642, 7, 9, 9, 1, -8 },
/* 'O' 0x4F */ { 650, 9, 9, 9, 0, -8 },
/* 'P' 0x50 */ { 661, 6, 9, 8, 1, -8 },
/* 'Q' 0x51 */ { 668, 9, 10, 9, 0, -8 },
/* 'R' 0x52 */ { 680, 7, 9, 9, 1, -8 },
/* 'S' 0x53 */ { 688, 6, 9, 8, 1, -8 },
/* 'T' 0x54 */ { 695, 7, 9, 8, 0, -8 },
/* 'U' 0x55 */ { 703, 7, 9, 9, 1, -8 },
/* 'V' 0x56 */ { 711, 7, 9, 8, 0, -8 },
/* 'W' 0x57 */ { 719, 11, 9, 11, 0, -8 },
/* 'X' 0x58 */ { 732, 6, 9, 8, 1, -8 },
/* 'Y' 0x59 */ { 739, 8, 9, 8, 0, -8 },
/* 'Z' 0x5A */ { 748, 7, 9, 7, 0, -8 },
/* '[' 0x5B */ { 756, 2, 12, 3, 1, -8 },
/* '\' 0x5C */ { 759, 3, 9, 3, 0, -8 },
/* ']' 0x5D */ { 763, 2, 12, 3, 0, -8 },
/* '^' 0x5E */ { 766, 4, 4, 6, 1, -8 },
/* '_' 0x5F */ { 768, 7, 1, 7, 0, 2 },
/* '`' 0x60 */ { 769, 1, 1, 3, 1, -8 },
/* 'a' 0x61 */ { 770, 6, 7, 7, 0, -6 },
/* 'b' 0x62 */ { 776, 5, 9, 7, 1, -8 },
/* 'c' 0x63 */ { 782, 6, 7, 6, 0, -6 },
/* 'd' 0x64 */ { 788, 6, 9, 7, 0, -8 },
/* 'e' 0x65 */ { 795, 6, 7, 6, 0, -6 },
/* 'f' 0x66 */ { 801, 3, 9, 3, 0, -8 },
/* 'g' 0x67 */ { 805, 6, 10, 7, 0, -6 },
/* 'h' 0x68 */ { 813, 5, 9, 6, 1, -8 },
/* 'i' 0x69 */ { 819, 1, 9, 3, 1, -8 },
/* 'j' 0x6A */ { 821, 2, 12, 3, 0, -8 },
/* 'k' 0x6B */ { 824, 5, 9, 6, 1, -8 },
/* 'l' 0x6C */ { 830, 1, 9, 3, 1, -8 },
/* 'm' 0x6D */ { 832, 8, 7, 10, 1, -6 },
/* 'n' 0x6E */ { 839, 5, 7, 6, 1, -6 },
/* 'o' 0x6F */ { 844, 6, 7, 6, 0, -6 },
/* 'p' 0x70 */ { 850, 5, 9, 7, 1, -6 },
/* 'q' 0x71 */ { 856, 6, 9, 7, 0, -6 },
/* 'r' 0x72 */ { 863, 3, 7, 4, 1, -6 },
/* 's' 0x73 */ { 866, 5, 7, 6, 0, -6 },
/* 't' 0x74 */ { 871, 3, 8, 3, 0, -7 },
/* 'u' 0x75 */ { 874, 5, 7, 6, 1, -6 },
/* 'v' 0x76 */ { 879, 6, 7, 6, 0, -6 },
/* 'w' 0x77 */ { 885, 8, 7, 9, 0, -6 },
/* 'x' 0x78 */ { 892, 5, 7, 6, 0, -6 },
/* 'y' 0x79 */ { 897, 5, 10, 6, 0, -6 },
/* 'z' 0x7A */ { 904, 5, 7, 6, 0, -6 },
/* '{' 0x7B */ { 909, 2, 12, 4, 1, -8 },
/* '|' 0x7C */ { 912, 1, 11, 3, 1, -8 },
/* '}' 0x7D */ { 914, 2, 12, 4, 1, -8 },
/* '~' 0x7E */ { 917, 6, 2, 6, 0, -4 },
/* 0x7F */ { 919, 9, 10, 0, 1, -8 },
/* 0x80 */ { 931, 7, 9, 8, 0, -8 },
/* 0x81 */ { 939, 0, 0, 8, 0, 0 },
/* 0x82 */ { 939, 1, 3, 3, 1, 0 },
/* 0x83 */ { 940, 0, 0, 8, 0, 0 },
/* 0x84 */ { 940, 3, 3, 5, 1, 0 },
/* 0x85 */ { 942, 5, 1, 7, 1, 0 },
/* 0x86 */ { 943, 5, 11, 7, 1, -8 },
/* 0x87 */ { 950, 5, 11, 7, 1, -8 },
/* 0x88 */ { 957, 0, 0, 8, 0, 0 },
/* 0x89 */ { 957, 12, 9, 12, 0, -8 },
/* 0x8A */ { 971, 6, 11, 8, 1, -9 },
/* 0x8B */ { 980, 2, 3, 4, 1, -4 },
/* 0x8C */ { 981, 6, 11, 8, 1, -10 },
/* 0x8D */ { 990, 6, 10, 8, 0, -9 },
/* 0x8E */ { 998, 7, 10, 7, 0, -9 },
/* 0x8F */ { 1007, 7, 10, 7, 0, -9 },
/* 0x90 */ { 1016, 0, 0, 8, 0, 0 },
/* 0x91 */ { 1016, 1, 3, 3, 1, -8 },
/* 0x92 */ { 1017, 1, 3, 2, 1, -8 },
/* 0x93 */ { 1018, 3, 3, 5, 1, -8 },
/* 0x94 */ { 1020, 3, 3, 5, 1, -8 },
/* 0x95 */ { 1022, 3, 3, 5, 1, -5 },
/* 0x96 */ { 1024, 6, 1, 6, 0, -3 },
/* 0x97 */ { 1025, 12, 1, 12, 0, -3 },
/* 0x98 */ { 1027, 0, 0, 8, 0, 0 },
/* 0x99 */ { 1027, 11, 7, 12, 1, -8 },
/* 0x9A */ { 1037, 4, 9, 6, 1, -8 },
/* 0x9B */ { 1042, 2, 3, 3, 1, -4 },
/* 0x9C */ { 1043, 4, 10, 6, 1, -9 },
/* 0x9D */ { 1048, 4, 9, 5, 0, -8 },
/* 0x9E */ { 1053, 5, 10, 6, 0, -9 },
/* 0x9F */ { 1060, 5, 10, 6, 0, -9 },
/* 0xA0 */ { 1067, 0, 0, 3, 0, 0 },
/* 0xA1 */ { 1067, 3, 2, 4, 0, -8 },
/* 0xA2 */ { 1068, 4, 2, 4, 0, -8 },
/* 0xA3 */ { 1069, 6, 9, 7, 0, -8 },
/* 0xA4 */ { 1076, 5, 4, 7, 1, -5 },
/* 0xA5 */ { 1079, 8, 11, 8, 1, -8 },
/* 0xA6 */ { 1090, 1, 12, 3, 1, -8 },
/* 0xA7 */ { 1092, 5, 12, 7, 1, -8 },
/* 0xA8 */ { 1100, 3, 1, 4, 0, -7 },
/* 0xA9 */ { 1101, 9, 9, 10, 0, -8 },
/* 0xAA */ { 1112, 6, 12, 8, 1, -8 },
/* 0xAB */ { 1121, 4, 4, 6, 1, -4 },
/* 0xAC */ { 1123, 6, 3, 7, 1, -4 },
/* 0xAD */ { 1126, 0, 0, 0, 0, 0 },
/* 0xAE */ { 1126, 9, 9, 10, 0, -8 },
/* 0xAF */ { 1137, 7, 10, 7, 0, -9 },
/* 0xB0 */ { 1146, 4, 4, 7, 2, -8 },
/* 0xB1 */ { 1148, 5, 7, 7, 1, -6 },
/* 0xB2 */ { 1153, 3, 2, 4, 1, 1 },
/* 0xB3 */ { 1154, 3, 9, 3, 0, -8 },
/* 0xB4 */ { 1158, 1, 1, 4, 1, -8 },
/* 0xB5 */ { 1159, 6, 9, 7, 1, -6 },
/* 0xB6 */ { 1166, 6, 10, 6, 1, -8 },
/* 0xB7 */ { 1174, 1, 1, 3, 1, -2 },
/* 0xB8 */ { 1175, 3, 3, 4, 1, 1 },
/* 0xB9 */ { 1177, 8, 9, 7, 0, -6 },
/* 0xBA */ { 1186, 4, 10, 6, 1, -6 },
/* 0xBB */ { 1191, 4, 4, 6, 1, -5 },
/* 0xBC */ { 1193, 5, 9, 7, 1, -8 },
/* 0xBD */ { 1199, 3, 1, 4, 0, -8 },
/* 0xBE */ { 1200, 3, 10, 3, 1, -9 },
/* 0xBF */ { 1204, 5, 9, 6, 0, -8 },
/* 0xC0 */ { 1210, 7, 10, 9, 1, -9 },
/* 0xC1 */ { 1219, 8, 10, 8, 0, -9 },
/* 0xC2 */ { 1229, 8, 10, 8, 0, -9 },
/* 0xC3 */ { 1239, 8, 10, 8, 0, -9 },
/* 0xC4 */ { 1249, 8, 10, 8, 0, -9 },
/* 0xC5 */ { 1259, 5, 10, 7, 1, -9 },
/* 0xC6 */ { 1266, 7, 11, 9, 0, -10 },
/* 0xC7 */ { 1276, 8, 12, 9, 0, -8 },
/* 0xC8 */ { 1288, 7, 11, 9, 0, -10 },
/* 0xC9 */ { 1298, 6, 10, 8, 1, -9 },
/* 0xCA */ { 1306, 7, 11, 8, 1, -8 },
/* 0xCB */ { 1316, 6, 10, 8, 1, -9 },
/* 0xCC */ { 1324, 6, 11, 8, 1, -10 },
/* 0xCD */ { 1333, 2, 10, 3, 1, -9 },
/* 0xCE */ { 1336, 3, 10, 4, 0, -9 },
/* 0xCF */ { 1340, 7, 10, 8, 1, -9 },
/* 0xD0 */ { 1349, 8, 9, 8, 0, -8 },
/* 0xD1 */ { 1358, 7, 10, 9, 1, -9 },
/* 0xD2 */ { 1367, 7, 10, 9, 1, -9 },
/* 0xD3 */ { 1376, 9, 10, 9, 0, -9 },
/* 0xD4 */ { 1388, 9, 11, 9, 0, -10 },
/* 0xD5 */ { 1401, 9, 11, 9, 0, -10 },
/* 0xD6 */ { 1414, 9, 11, 9, 0, -10 },
/* 0xD7 */ { 1427, 5, 5, 7, 1, -5 },
/* 0xD8 */ { 1431, 7, 10, 9, 1, -9 },
/* 0xD9 */ { 1440, 7, 10, 9, 1, -9 },
/* 0xDA */ { 1449, 7, 10, 9, 1, -9 },
/* 0xDB */ { 1458, 7, 10, 9, 1, -9 },
/* 0xDC */ { 1467, 7, 10, 9, 1, -9 },
/* 0xDD */ { 1476, 7, 10, 8, 1, -9 },
/* 0xDE */ { 1485, 6, 12, 7, 0, -8 },
/* 0xDF */ { 1494, 6, 9, 7, 1, -8 },
/* 0xE0 */ { 1501, 3, 9, 4, 1, -8 },
/* 0xE1 */ { 1505, 7, 10, 7, 0, -9 },
/* 0xE2 */ { 1514, 7, 10, 7, 0, -9 },
/* 0xE3 */ { 1523, 7, 10, 7, 0, -9 },
/* 0xE4 */ { 1532, 7, 9, 7, 0, -8 },
/* 0xE5 */ { 1540, 2, 10, 3, 1, -9 },
/* 0xE6 */ { 1543, 6, 10, 6, 0, -9 },
/* 0xE7 */ { 1551, 6, 10, 6, 0, -6 },
/* 0xE8 */ { 1559, 6, 10, 6, 0, -9 },
/* 0xE9 */ { 1567, 6, 10, 6, 0, -9 },
/* 0xEA */ { 1575, 6, 9, 6, 0, -6 },
/* 0xEB */ { 1582, 6, 9, 6, 0, -8 },
/* 0xEC */ { 1589, 6, 10, 6, 0, -9 },
/* 0xED */ { 1597, 2, 10, 3, 1, -9 },
/* 0xEE */ { 1600, 3, 10, 3, 0, -9 },
/* 0xEF */ { 1604, 7, 10, 7, 0, -9 },
/* 0xF0 */ { 1613, 7, 9, 7, 0, -8 },
/* 0xF1 */ { 1621, 5, 10, 6, 1, -9 },
/* 0xF2 */ { 1628, 5, 10, 6, 1, -9 },
/* 0xF3 */ { 1635, 6, 10, 6, 0, -9 },
/* 0xF4 */ { 1643, 6, 10, 6, 0, -9 },
/* 0xF5 */ { 1651, 6, 10, 6, 0, -9 },
/* 0xF6 */ { 1659, 6, 9, 6, 0, -8 },
/* 0xF7 */ { 1666, 5, 5, 7, 1, -5 },
/* 0xF8 */ { 1670, 3, 10, 4, 1, -9 },
/* 0xF9 */ { 1674, 5, 10, 6, 1, -9 },
/* 0xFA */ { 1681, 5, 9, 6, 1, -8 },
/* 0xFB */ { 1687, 5, 10, 6, 1, -9 },
/* 0xFC */ { 1694, 5, 9, 6, 1, -8 },
/* 0xFD */ { 1700, 6, 12, 6, 0, -8 },
/* 0xFE */ { 1709, 4, 11, 3, 0, -7 },
/* 0xFF */ { 1715, 1, 1, 4, 1, -7 },
};
const GFXfont FreeSans6pt_Win1250 PROGMEM = {
(uint8_t*)FreeSans6pt_Win1250Bitmaps,
(GFXglyph*)FreeSans6pt_Win1250Glyphs,
0x01, 0xFF, 14
};
@@ -0,0 +1,527 @@
// trunk-ignore-all(clang-format)
#pragma once
/* PROPERTIES
FONT_NAME FreeSans6pt_Win1251
*/
const uint8_t FreeSans6pt_Win1251Bitmaps[] PROGMEM = {
/* 0x01 */ 0x1C, 0x0A, 0x05, 0x04, 0xFE, 0x08, 0x1C, 0x02, 0x07, 0xE0, 0x9F, 0xC0,
/* 0x02 */ 0x3F, 0xF0, 0x40, 0xE0, 0x10, 0x3F, 0x04, 0x9E, 0x28, 0x14, 0x0E, 0x00,
/* 0x03 */ 0x3F, 0x10, 0x28, 0x06, 0x49, 0x80, 0x60, 0x19, 0x26, 0x31, 0x40, 0x8F, 0xC0,
/* 0x04 */ 0x3F, 0x10, 0x2A, 0x16, 0x49, 0xA1, 0x60, 0x19, 0xE6, 0x31, 0x40, 0x8F, 0xC0,
/* 0x05 */ 0x28, 0x15, 0x2A, 0xB5, 0x55, 0xA8, 0x54, 0x12, 0x04, 0x41, 0x08, 0x81, 0xC0,
/* 0x06 */ 0x04, 0x08, 0x88, 0x82, 0x07, 0x01, 0x11, 0xA2, 0xC4, 0x40, 0x70, 0x20, 0x88, 0x88, 0x10, 0x00,
/* 0x07 */
/* 0x08 */ 0x03, 0x83, 0x44, 0x48, 0x28, 0x01, 0x80, 0x17, 0xFE, 0x08, 0x45, 0x28, 0x84, 0x00,
/* 0x09 */ 0x01, 0xC0, 0x68, 0x82, 0x41, 0x10, 0x02, 0x80, 0x06, 0x00, 0x14, 0x00, 0x8F, 0xFC,
/* 0x0A */
/* 0x0B */ 0x22, 0x2A, 0xA2, 0x30, 0x18, 0x0A, 0x09, 0x04, 0x44, 0x14, 0x04, 0x00,
/* 0x0C */ 0x46, 0x00, 0x19, 0x03, 0x21, 0x20, 0x93, 0x04, 0x20, 0x11, 0x80, 0x50, 0x02, 0x7F, 0xE0,
/* 0x0D */
/* 0x0E */ 0x08, 0x0E, 0x08, 0x88, 0x24, 0x12, 0x09, 0x05, 0x01, 0xFF, 0x8A, 0x02, 0x00,
/* 0x0F */ 0x3F, 0x14, 0xAA, 0x16, 0x01, 0x92, 0x60, 0x18, 0xC6, 0x49, 0x40, 0x8F, 0xC0,
/* 0x10 */ 0x1B, 0x02, 0xA0, 0x54, 0x12, 0x42, 0x48, 0x49, 0x31, 0x1E, 0x23, 0xEA, 0xFE, 0x3C,
/* 0x11 */ 0x3F, 0x02, 0x00, 0x20, 0x6D, 0x27, 0xF8, 0x3F, 0xC1, 0xFE, 0x37, 0xD0, 0xBE, 0x40, 0xE1, 0xE2, 0x00,
/* 0x12 */ 0x12, 0x42, 0x20, 0x24, 0xC0, 0x29, 0x99, 0x05, 0x23, 0x30, 0xB0, 0x30, 0x00,
/* 0x13 */ 0x3F, 0x88, 0x0A, 0x44, 0xD5, 0x58, 0x03, 0x00, 0x67, 0xCC, 0x71, 0x40, 0x47, 0xF0,
/* 0x14 */ 0x3F, 0x18, 0x69, 0x26, 0x85, 0xA1, 0x6C, 0xD8, 0x06, 0x31, 0x40, 0x8F, 0xC0,
/* 0x15 */ 0x3F, 0x11, 0x00, 0xE8, 0x03, 0xA0, 0x1F, 0xB3, 0x7E, 0x00, 0xE9, 0xE0, 0x23, 0x00, 0x40, 0x40, 0xFE, 0x00,
/* 0x16 */ 0x30, 0x38, 0x3A, 0x3E, 0x6E, 0xEB, 0xC3, 0xC3, 0x66, 0x3C,
/* 0x17 */ 0x3F, 0x04, 0x00, 0x82, 0x88, 0x5C, 0xA4, 0x49, 0x22, 0x81, 0x98, 0xC4, 0x40, 0xA3, 0xF0,
/* 0x18 */ 0x07, 0x80, 0x42, 0x04, 0x08, 0x21, 0x41, 0x42, 0x60, 0x0E, 0x8C, 0xB2, 0x89, 0x50, 0x52, 0x82, 0x80,
/* 0x19 */ 0x3F, 0xC4, 0x02, 0x80, 0x18, 0x01, 0xB3, 0x1B, 0xB9, 0x80, 0x19, 0xE1, 0x40, 0x23, 0xFC,
/* 0x1A */ 0xFF, 0xC0, 0x67, 0x34, 0x58, 0x4C, 0x46, 0x03, 0x11, 0x80, 0xFF, 0xC0,
/* 0x1B */ 0x0F, 0xC0, 0x40, 0x82, 0x49, 0x08, 0x04, 0x00, 0x00, 0x12, 0x02, 0x31, 0x34, 0x0B, 0x88, 0x45, 0x00, 0x20,
/* 0x1C */ 0x3F, 0x88, 0x0A, 0x44, 0xC9, 0x19, 0x3B, 0x00, 0x60, 0x4C, 0x71, 0x40, 0x47, 0xF0,
/* 0x1D */ 0x3F, 0x8B, 0x0A, 0x00, 0xC8, 0x18, 0x13, 0x00, 0x48, 0xCA, 0xC1, 0x44, 0x53, 0x30,
/* 0x1E */ 0x19, 0xC2, 0x02, 0x50, 0x1E, 0x49, 0x80, 0x12, 0x01, 0x27, 0x92, 0x01, 0x10, 0x20, 0xFC,
/* 0x1F */ 0x30, 0x1C, 0x0C, 0x3E, 0x7E, 0xCF, 0x07, 0xC7, 0x7F, 0x3F,
/* ' ' 0x20 */
/* '!' 0x21 */ 0xFC, 0x80,
/* '"' 0x22 */ 0xB6, 0x80,
/* '#' 0x23 */ 0x24, 0x51, 0xF9, 0x42, 0x9F, 0x92, 0x28,
/* '$' 0x24 */ 0x10, 0xE5, 0x55, 0x50, 0xE1, 0x65, 0x55, 0xE1, 0x00,
/* '%' 0x25 */ 0x71, 0x24, 0x89, 0x22, 0x50, 0x74, 0x02, 0x70, 0xA4, 0x49, 0x11, 0xC0,
/* '&' 0x26 */ 0x71, 0x24, 0x9C, 0x62, 0x58, 0xA7, 0xF4,
/* ''' 0x27 */ 0xE0,
/* '(' 0x28 */ 0x5A, 0xAA, 0x94,
/* ')' 0x29 */ 0x89, 0x12, 0x49, 0x29, 0x00,
/* '*' 0x2A */ 0x5E, 0x80,
/* '+' 0x2B */ 0x21, 0x3E, 0x42, 0x00,
/* ',' 0x2C */ 0xE0,
/* '-' 0x2D */ 0xC0,
/* '.' 0x2E */ 0x80,
/* '/' 0x2F */ 0x24, 0xA4, 0xA4, 0x80,
/* '0' 0x30 */ 0x76, 0xE3, 0x18, 0xC6, 0x3B, 0x70,
/* '1' 0x31 */ 0x27, 0x92, 0x49, 0x20,
/* '2' 0x32 */ 0x79, 0x10, 0x41, 0x08, 0xC6, 0x10, 0xFC,
/* '3' 0x33 */ 0x79, 0x30, 0x43, 0x18, 0x10, 0x71, 0x78,
/* '4' 0x34 */ 0x08, 0x61, 0x8A, 0x49, 0x2F, 0xC2, 0x08,
/* '5' 0x35 */ 0xFC, 0x21, 0xE8, 0x84, 0x31, 0xF0,
/* '6' 0x36 */ 0x74, 0x61, 0xE8, 0xC6, 0x31, 0x70,
/* '7' 0x37 */ 0xF8, 0x44, 0x22, 0x11, 0x08, 0x40,
/* '8' 0x38 */ 0x39, 0x34, 0x53, 0x39, 0x1C, 0x51, 0x38,
/* '9' 0x39 */ 0x39, 0x3C, 0x71, 0x4C, 0xF0, 0x53, 0x78,
/* ':' 0x3A */ 0x82,
/* ';' 0x3B */ 0x87,
/* '<' 0x3C */ 0x3E, 0x30, 0x60, 0x80,
/* '=' 0x3D */ 0xF8, 0x3E,
/* '>' 0x3E */ 0xE0, 0xC6, 0xC8, 0x00,
/* '?' 0x3F */ 0x74, 0x42, 0x11, 0x10, 0x80, 0x20,
/* '@' 0x40 */ 0x0F, 0x86, 0x19, 0x9A, 0xA4, 0xD9, 0x13, 0x22, 0x56, 0xDA, 0x6E, 0x60, 0x06, 0x00, 0x3C, 0x00,
/* 'A' 0x41 */ 0x18, 0x18, 0x24, 0x24, 0x24, 0x7E, 0x42, 0x42, 0xC3,
/* 'B' 0x42 */ 0xFA, 0x18, 0x61, 0xFA, 0x18, 0x61, 0xFC,
/* 'C' 0x43 */ 0x3E, 0x63, 0x40, 0x40, 0xC0, 0x40, 0x41, 0x63, 0x3E,
/* 'D' 0x44 */ 0xF9, 0x0A, 0x1C, 0x18, 0x30, 0x61, 0xC2, 0xF8,
/* 'E' 0x45 */ 0xFE, 0x08, 0x20, 0xFE, 0x08, 0x20, 0xFC,
/* 'F' 0x46 */ 0xFE, 0x08, 0x20, 0xFA, 0x08, 0x20, 0x80,
/* 'G' 0x47 */ 0x1E, 0x61, 0x40, 0x40, 0xC7, 0x41, 0x41, 0x63, 0x1D,
/* 'H' 0x48 */ 0x83, 0x06, 0x0C, 0x1F, 0xF0, 0x60, 0xC1, 0x82,
/* 'I' 0x49 */ 0xFF, 0x80,
/* 'J' 0x4A */ 0x08, 0x42, 0x10, 0x87, 0x29, 0x70,
/* 'K' 0x4B */ 0x85, 0x12, 0x45, 0x0D, 0x13, 0x22, 0x42, 0x86,
/* 'L' 0x4C */ 0x84, 0x21, 0x08, 0x42, 0x10, 0xF8,
/* 'M' 0x4D */ 0xC3, 0xC3, 0xC3, 0xA5, 0xA5, 0xA5, 0x99, 0x99, 0x99,
/* 'N' 0x4E */ 0x83, 0x86, 0x8D, 0x19, 0x33, 0x62, 0xC3, 0x86,
/* 'O' 0x4F */ 0x1E, 0x31, 0x90, 0x68, 0x1C, 0x0A, 0x05, 0x06, 0xC6, 0x1E, 0x00,
/* 'P' 0x50 */ 0xFA, 0x18, 0x61, 0xFA, 0x08, 0x20, 0x80,
/* 'Q' 0x51 */ 0x1E, 0x31, 0x90, 0x68, 0x1C, 0x0A, 0x05, 0x16, 0xC6, 0x1F, 0x00, 0x40,
/* 'R' 0x52 */ 0xFD, 0x0E, 0x1C, 0x2F, 0x90, 0xA1, 0x42, 0x86,
/* 'S' 0x53 */ 0x7A, 0x18, 0x30, 0x78, 0x38, 0x61, 0x78,
/* 'T' 0x54 */ 0xFE, 0x20, 0x40, 0x81, 0x02, 0x04, 0x08, 0x10,
/* 'U' 0x55 */ 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xE2, 0x78,
/* 'V' 0x56 */ 0xC2, 0x85, 0x0B, 0x22, 0x44, 0x8E, 0x0C, 0x18,
/* 'W' 0x57 */ 0xC4, 0x28, 0xCD, 0x29, 0x25, 0x24, 0xA4, 0x52, 0x8C, 0x61, 0x8C, 0x31, 0x80,
/* 'X' 0x58 */ 0x87, 0x34, 0x8C, 0x30, 0xC4, 0xA3, 0x84,
/* 'Y' 0x59 */ 0xC3, 0x42, 0x24, 0x34, 0x18, 0x08, 0x08, 0x08, 0x08,
/* 'Z' 0x5A */ 0x7E, 0x0C, 0x30, 0x41, 0x06, 0x18, 0x20, 0xFE,
/* '[' 0x5B */ 0xEA, 0xAA, 0xAB,
/* '\' 0x5C */ 0x92, 0x24, 0x89, 0x20,
/* ']' 0x5D */ 0xD5, 0x55, 0x57,
/* '^' 0x5E */ 0x46, 0xA9,
/* '_' 0x5F */ 0xFE,
/* '`' 0x60 */ 0x80,
/* 'a' 0x61 */ 0x79, 0x20, 0x4F, 0xC6, 0x37, 0x40,
/* 'b' 0x62 */ 0x84, 0x3D, 0x18, 0xC6, 0x31, 0xF0,
/* 'c' 0x63 */ 0x39, 0x3C, 0x20, 0xC1, 0x33, 0x80,
/* 'd' 0x64 */ 0x04, 0x13, 0xD3, 0xC6, 0x1C, 0x53, 0x3C,
/* 'e' 0x65 */ 0x39, 0x38, 0x7F, 0x81, 0x13, 0x80,
/* 'f' 0x66 */ 0x6B, 0xA4, 0x92, 0x40,
/* 'g' 0x67 */ 0x35, 0x3C, 0x61, 0xC5, 0x33, 0x41, 0x4D, 0xE0,
/* 'h' 0x68 */ 0x84, 0x3D, 0x38, 0xC6, 0x31, 0x88,
/* 'i' 0x69 */ 0xBF, 0x80,
/* 'j' 0x6A */ 0x45, 0x55, 0x57,
/* 'k' 0x6B */ 0x84, 0x25, 0x4E, 0x52, 0xD2, 0x88,
/* 'l' 0x6C */ 0xFF, 0x80,
/* 'm' 0x6D */ 0xF7, 0x99, 0x91, 0x91, 0x91, 0x91, 0x91,
/* 'n' 0x6E */ 0xF4, 0x63, 0x18, 0xC6, 0x20,
/* 'o' 0x6F */ 0x39, 0x3C, 0x61, 0xC5, 0x33, 0x80,
/* 'p' 0x70 */ 0xF4, 0x63, 0x18, 0xC7, 0xD0, 0x80,
/* 'q' 0x71 */ 0x3D, 0x3C, 0x61, 0xC5, 0x37, 0x41, 0x04,
/* 'r' 0x72 */ 0xF2, 0x49, 0x20,
/* 's' 0x73 */ 0x7A, 0x50, 0xE0, 0xE5, 0xE0,
/* 't' 0x74 */ 0x5D, 0x24, 0x93,
/* 'u' 0x75 */ 0x8C, 0x63, 0x18, 0xCF, 0xA0,
/* 'v' 0x76 */ 0x85, 0x24, 0x92, 0x30, 0xC3, 0x00,
/* 'w' 0x77 */ 0x89, 0x59, 0x59, 0x55, 0x56, 0x26, 0x26,
/* 'x' 0x78 */ 0x4A, 0x4C, 0x43, 0x27, 0x20,
/* 'y' 0x79 */ 0x8A, 0x52, 0xA5, 0x18, 0x84, 0x22, 0x00,
/* 'z' 0x7A */ 0x78, 0x44, 0x46, 0x23, 0xE0,
/* '{' 0x7B */ 0x6A, 0xAA, 0xA9,
/* '|' 0x7C */ 0xFF, 0xE0,
/* '}' 0x7D */ 0x95, 0x55, 0x56,
/* '~' 0x7E */ 0x66, 0x60,
/* 0x7F */
/* 0x80 */ 0xFC, 0x08, 0x04, 0x02, 0x01, 0xF0, 0x8C, 0x46, 0x23, 0x11, 0x80, 0xC0, 0xC0,
/* 0x81 */ 0x10, 0x8F, 0xE0, 0x82, 0x08, 0x20, 0x82, 0x00,
/* 0x82 */ 0xE0,
/* 0x83 */ 0x24, 0x0F, 0x88, 0x88, 0x80,
/* 0x84 */ 0xB6, 0x80,
/* 0x85 */ 0xA8,
/* 0x86 */ 0x21, 0x09, 0xF2, 0x10, 0x84, 0x21, 0x08,
/* 0x87 */ 0x21, 0x09, 0xF2, 0x10, 0x84, 0xF9, 0x08,
/* 0x88 */ 0x1C, 0x45, 0x07, 0xE4, 0x1F, 0x10, 0x10, 0x1E,
/* 0x89 */ 0x62, 0x09, 0x40, 0x98, 0x06, 0x80, 0x10, 0x01, 0x66, 0x29, 0x92, 0x99, 0x06, 0x60,
/* 0x8A */ 0x7C, 0x08, 0x81, 0x10, 0x22, 0x04, 0x7C, 0x88, 0x51, 0x0A, 0x21, 0x87, 0xC0,
/* 0x8B */ 0x64,
/* 0x8C */ 0x84, 0x10, 0x82, 0x10, 0x42, 0x0F, 0xFD, 0x08, 0xA1, 0x0C, 0x23, 0x87, 0xC0,
/* 0x8D */ 0x10, 0x88, 0xE6, 0xB3, 0x8C, 0x28, 0x92, 0x28, 0xC0,
/* 0x8E */ 0xFC, 0x08, 0x04, 0x02, 0x01, 0xF0, 0x8C, 0x46, 0x23, 0x11, 0x80,
/* 0x8F */ 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0xFE, 0x20, 0x40,
/* 0x90 */ 0x43, 0xC4, 0x1F, 0x45, 0x14, 0x51, 0x44, 0x11, 0x80,
/* 0x91 */ 0xE0,
/* 0x92 */ 0xE0,
/* 0x93 */ 0xB6, 0x80,
/* 0x94 */ 0xB6, 0x80,
/* 0x95 */ 0xFF, 0x80,
/* 0x96 */ 0xFC,
/* 0x97 */ 0xFF, 0xF0,
/* 0x98 */
/* 0x99 */ 0xE6, 0x28, 0xCD, 0x19, 0xA3, 0x34, 0x6A, 0x8B, 0x51, 0x68,
/* 0x9A */ 0x78, 0x24, 0x13, 0xC9, 0x14, 0x8E, 0x7C,
/* 0x9B */ 0x98,
/* 0x9C */ 0x88, 0x44, 0x3F, 0xD1, 0x38, 0x8C, 0x78,
/* 0x9D */ 0x24, 0x09, 0xAC, 0xCA, 0x90,
/* 0x9E */ 0x43, 0xC4, 0x1F, 0x45, 0x14, 0x51, 0x44,
/* 0x9F */ 0x8C, 0x63, 0x18, 0xFC, 0x80,
/* 0xA0 */
/* 0xA1 */ 0x24, 0x33, 0x0A, 0x36, 0x45, 0x8E, 0x0C, 0x10, 0x60, 0x80,
/* 0xA2 */ 0x51, 0x22, 0x95, 0xA8, 0xC4, 0x23, 0x10,
/* 0xA3 */ 0x08, 0x42, 0x10, 0x86, 0x31, 0x78,
/* 0xA4 */ 0xFC, 0x63, 0xF0,
/* 0xA5 */ 0x07, 0xF8, 0x20, 0x82, 0x08, 0x20, 0x82, 0x00,
/* 0xA6 */ 0xF9, 0xF0,
/* 0xA7 */ 0x32, 0x91, 0xC9, 0x47, 0x26, 0x14, 0xA4, 0xC0,
/* 0xA8 */ 0x28, 0x0F, 0xE0, 0x82, 0x0F, 0xE0, 0x82, 0x0F, 0xC0,
/* 0xA9 */ 0x3E, 0x3F, 0xB8, 0xF4, 0x1A, 0x0D, 0x17, 0x76, 0xC6, 0x3E, 0x00,
/* 0xAA */ 0x38, 0x8A, 0x0C, 0x0F, 0x90, 0x20, 0xE3, 0x7C,
/* 0xAB */ 0x5A, 0xA5,
/* 0xAC */ 0x51, 0x55, 0x56,
/* 0xAD */
/* 0xAE */ 0x3E, 0x31, 0xB7, 0x72, 0x99, 0xCC, 0xC7, 0x56, 0xC6, 0x3E, 0x00,
/* 0xAF */ 0xA1, 0x24, 0x92, 0x49, 0x00,
/* 0xB0 */ 0x69, 0x96,
/* 0xB1 */ 0x21, 0x3E, 0x42, 0x03, 0xE0,
/* 0xB2 */ 0xFF, 0x80,
/* 0xB3 */ 0xDF, 0x80,
/* 0xB4 */ 0x27, 0xC9, 0x24,
/* 0xB5 */ 0x8A, 0x28, 0xA2, 0x8A, 0x6E, 0xE0, 0x80,
/* 0xB6 */ 0x7F, 0xAE, 0xBA, 0x68, 0xA2, 0x8A, 0x28, 0xA0,
/* 0xB7 */ 0x80,
/* 0xB8 */ 0x28, 0xA0, 0x1E, 0x47, 0xFC, 0x11, 0x78,
/* 0xB9 */ 0x88, 0x44, 0x32, 0x59, 0xDA, 0xCD, 0x66, 0x6B, 0x32, 0x8B, 0x80,
/* 0xBA */ 0x79, 0x1F, 0x30, 0x45, 0xE0,
/* 0xBB */ 0xA5, 0x5A,
/* 0xBC */ 0x45, 0x55, 0x57,
/* 0xBD */ 0x7A, 0x18, 0x70, 0x78, 0x38, 0x61, 0x7C,
/* 0xBE */ 0x7A, 0x1C, 0x1C, 0xBC,
/* 0xBF */ 0xB4, 0x24, 0x92, 0x40,
/* 0xC0 */ 0x18, 0x18, 0x3C, 0x24, 0x24, 0x7E, 0x42, 0x42, 0xC3,
/* 0xC1 */ 0xFE, 0x08, 0x20, 0xFA, 0x18, 0x61, 0xF8,
/* 0xC2 */ 0xFA, 0x18, 0x61, 0xFA, 0x18, 0x61, 0xFC,
/* 0xC3 */ 0xFE, 0x08, 0x20, 0x82, 0x08, 0x20, 0x80,
/* 0xC4 */ 0x1F, 0x08, 0x84, 0x42, 0x21, 0x10, 0x88, 0x44, 0x42, 0xFF, 0xC0, 0x60, 0x20,
/* 0xC5 */ 0xFE, 0x08, 0x20, 0xFE, 0x08, 0x20, 0xFC,
/* 0xC6 */ 0x88, 0xA4, 0x9A, 0x87, 0xC1, 0xC1, 0xF1, 0xAD, 0x92, 0x88, 0x80,
/* 0xC7 */ 0x7A, 0x18, 0x41, 0x38, 0x18, 0x61, 0x7C,
/* 0xC8 */ 0x87, 0x0E, 0x2C, 0x59, 0x34, 0x68, 0xE1, 0xC2,
/* 0xC9 */ 0x28, 0x22, 0x1C, 0x38, 0xB1, 0x64, 0xD1, 0xA3, 0x87, 0x08,
/* 0xCA */ 0x8E, 0x6B, 0x38, 0xC2, 0x89, 0x22, 0x8C,
/* 0xCB */ 0x3E, 0x44, 0x89, 0x12, 0x24, 0x58, 0xA1, 0xC2,
/* 0xCC */ 0xC3, 0xC3, 0xC3, 0xA5, 0xA5, 0xA5, 0x99, 0x99, 0x99,
/* 0xCD */ 0x83, 0x06, 0x0C, 0x1F, 0xF0, 0x60, 0xC1, 0x82,
/* 0xCE */ 0x3C, 0x42, 0x81, 0x81, 0x81, 0x81, 0x81, 0xC2, 0x7C,
/* 0xCF */ 0xFF, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x82,
/* 0xD0 */ 0xFA, 0x18, 0x61, 0xFE, 0x08, 0x20, 0x80,
/* 0xD1 */ 0x38, 0x8A, 0x0C, 0x08, 0x10, 0x20, 0xE3, 0x7C,
/* 0xD2 */ 0xFE, 0x20, 0x40, 0x81, 0x02, 0x04, 0x08, 0x10,
/* 0xD3 */ 0xC2, 0x8D, 0x91, 0x63, 0x83, 0x04, 0x18, 0x20,
/* 0xD4 */ 0x08, 0x1F, 0x32, 0x71, 0x18, 0x8C, 0x47, 0x26, 0xFE, 0x08, 0x00,
/* 0xD5 */ 0x87, 0x34, 0x8C, 0x30, 0xC4, 0xB3, 0x84,
/* 0xD6 */ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0xFF, 0x01, 0x01,
/* 0xD7 */ 0x8E, 0x38, 0xE3, 0x8D, 0xF0, 0xC3, 0x0C,
/* 0xD8 */ 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0xFF,
/* 0xD9 */ 0x99, 0x4C, 0xA6, 0x53, 0x29, 0x94, 0xCA, 0x65, 0x32, 0xFF, 0x80, 0x40, 0x20,
/* 0xDA */ 0xF0, 0x04, 0x01, 0x00, 0x40, 0x1F, 0x84, 0x21, 0x0C, 0x42, 0x1F, 0x00,
/* 0xDB */ 0x81, 0xC0, 0xE0, 0x70, 0x3F, 0xDC, 0x2E, 0x17, 0x0B, 0xF9, 0x80,
/* 0xDC */ 0x82, 0x08, 0x20, 0xFE, 0x18, 0x61, 0xF8,
/* 0xDD */ 0x79, 0x8A, 0x18, 0x13, 0xE0, 0x60, 0xC2, 0x7C,
/* 0xDE */ 0x87, 0x26, 0x39, 0x06, 0x41, 0xF0, 0x64, 0x19, 0x06, 0x63, 0x8F, 0x80,
/* 0xDF */ 0x7E, 0x18, 0x61, 0x7C, 0xD6, 0x71, 0x84,
/* 0xE0 */ 0x79, 0x11, 0xD9, 0xCD, 0xD0,
/* 0xE1 */ 0x0D, 0xC4, 0x1E, 0x47, 0x1C, 0x51, 0x78,
/* 0xE2 */ 0xF4, 0xBD, 0x29, 0xF8,
/* 0xE3 */ 0xF8, 0x88, 0x88,
/* 0xE4 */ 0x3C, 0x48, 0x91, 0x22, 0x5F, 0xE0, 0x80,
/* 0xE5 */ 0x79, 0x1F, 0xF0, 0x45, 0xE0,
/* 0xE6 */ 0x92, 0x54, 0x38, 0x3C, 0x56, 0x93,
/* 0xE7 */ 0x78, 0x23, 0x82, 0xCD, 0xE0,
/* 0xE8 */ 0x9C, 0xEB, 0x5C, 0xC4,
/* 0xE9 */ 0x70, 0x27, 0x3A, 0xD7, 0x31,
/* 0xEA */ 0x9A, 0xCC, 0xA9,
/* 0xEB */ 0x7A, 0x52, 0x94, 0xE4,
/* 0xEC */ 0x8F, 0x3D, 0x6D, 0xA6, 0x90,
/* 0xED */ 0x8C, 0x7F, 0x18, 0xC4,
/* 0xEE */ 0x79, 0x1C, 0x71, 0x45, 0xE0,
/* 0xEF */ 0xFC, 0x63, 0x18, 0xC4,
/* 0xF0 */ 0xFC, 0x63, 0x18, 0xFA, 0x10, 0x80,
/* 0xF1 */ 0x79, 0x1C, 0x30, 0x45, 0xE0,
/* 0xF2 */ 0xF9, 0x08, 0x42, 0x10,
/* 0xF3 */ 0x8A, 0x56, 0xA3, 0x10, 0x8C, 0x40,
/* 0xF4 */ 0x04, 0x01, 0x07, 0xF9, 0x31, 0xC4, 0x71, 0x14, 0xC5, 0xFE, 0x04, 0x01, 0x00, 0x40,
/* 0xF5 */ 0x4B, 0x8C, 0x65, 0xE4,
/* 0xF6 */ 0x8A, 0x28, 0xA2, 0x8B, 0xF0, 0x40,
/* 0xF7 */ 0x99, 0x97, 0x11,
/* 0xF8 */ 0x96, 0x59, 0x65, 0x97, 0xF0,
/* 0xF9 */ 0x95, 0x2A, 0x54, 0xA9, 0x5F, 0xC0, 0x80,
/* 0xFA */ 0xF0, 0x20, 0x78, 0x91, 0x23, 0xC0,
/* 0xFB */ 0x86, 0x1F, 0x63, 0x8F, 0xD0,
/* 0xFC */ 0x84, 0x3D, 0x18, 0xF8,
/* 0xFD */ 0xF4, 0xDE, 0x19, 0xF8,
/* 0xFE */ 0x9E, 0xA2, 0xE1, 0xA1, 0xA2, 0x9E,
/* 0xFF */ 0xFC, 0x7E, 0xD4, 0xC4,
};
const GFXglyph FreeSans6pt_Win1251Glyphs[] PROGMEM = {
/* 0x01 */ { 0, 9, 10, 11, 1, -9 },
/* 0x02 */ { 12, 9, 10, 11, 1, -8 },
/* 0x03 */ { 24, 10, 10, 12, 1, -8 },
/* 0x04 */ { 37, 10, 10, 12, 1, -8 },
/* 0x05 */ { 50, 10, 10, 12, 1, -9 },
/* 0x06 */ { 63, 11, 11, 13, 1, -9 },
/* 0x07 */ { 79, 0, 0, 8, 0, 0 },
/* 0x08 */ { 79, 12, 9, 14, 1, -8 },
/* 0x09 */ { 93, 14, 8, 16, 1, -7 },
/* 0x0A */ { 107, 0, 0, 8, 0, 0 },
/* 0x0B */ { 107, 9, 10, 11, 1, -9 },
/* 0x0C */ { 119, 13, 9, 15, 1, -8 },
/* 0x0D */ { 134, 0, 0, 8, 0, 0 },
/* 0x0E */ { 134, 9, 11, 11, 1, -9 },
/* 0x0F */ { 147, 10, 10, 12, 1, -9 },
/* 0x10 */ { 160, 11, 10, 13, 1, -9 },
/* 0x11 */ { 174, 13, 10, 15, 1, -9 },
/* 0x12 */ { 191, 10, 10, 12, 1, -9 },
/* 0x13 */ { 204, 11, 10, 13, 1, -9 },
/* 0x14 */ { 218, 10, 10, 12, 1, -9 },
/* 0x15 */ { 231, 14, 10, 16, 1, -9 },
/* 0x16 */ { 249, 8, 10, 10, 1, -9 },
/* 0x17 */ { 259, 12, 10, 14, 1, -9 },
/* 0x18 */ { 274, 13, 10, 15, 1, -9 },
/* 0x19 */ { 291, 12, 10, 14, 1, -9 },
/* 0x1A */ { 306, 9, 10, 11, 1, -8 },
/* 0x1B */ { 318, 14, 10, 16, 1, -9 },
/* 0x1C */ { 336, 11, 10, 13, 1, -9 },
/* 0x1D */ { 350, 11, 10, 13, 1, -9 },
/* 0x1E */ { 364, 12, 10, 14, 1, -9 },
/* 0x1F */ { 379, 8, 10, 11, 2, -9 },
/* ' ' 0x20 */ { 389, 0, 0, 3, 0, 0 },
/* '!' 0x21 */ { 389, 1, 9, 4, 2, -8 },
/* '"' 0x22 */ { 391, 3, 3, 4, 0, -8 },
/* '#' 0x23 */ { 393, 7, 8, 7, 0, -7 },
/* '$' 0x24 */ { 400, 6, 11, 7, 0, -9 },
/* '%' 0x25 */ { 409, 10, 9, 11, 0, -8 },
/* '&' 0x26 */ { 421, 6, 9, 8, 1, -8 },
/* ''' 0x27 */ { 428, 1, 3, 2, 1, -8 },
/* '(' 0x28 */ { 429, 2, 11, 4, 1, -8 },
/* ')' 0x29 */ { 432, 3, 11, 4, 0, -8 },
/* '*' 0x2A */ { 437, 3, 3, 5, 1, -8 },
/* '+' 0x2B */ { 439, 5, 5, 7, 1, -4 },
/* ',' 0x2C */ { 443, 1, 3, 3, 1, 0 },
/* '-' 0x2D */ { 444, 2, 1, 4, 1, -3 },
/* '.' 0x2E */ { 445, 1, 1, 3, 1, 0 },
/* '/' 0x2F */ { 446, 3, 9, 3, 0, -8 },
/* '0' 0x30 */ { 450, 5, 9, 7, 1, -8 },
/* '1' 0x31 */ { 456, 3, 9, 7, 1, -8 },
/* '2' 0x32 */ { 460, 6, 9, 7, 0, -8 },
/* '3' 0x33 */ { 467, 6, 9, 7, 0, -8 },
/* '4' 0x34 */ { 474, 6, 9, 7, 0, -8 },
/* '5' 0x35 */ { 481, 5, 9, 7, 1, -8 },
/* '6' 0x36 */ { 487, 5, 9, 7, 1, -8 },
/* '7' 0x37 */ { 493, 5, 9, 7, 1, -8 },
/* '8' 0x38 */ { 499, 6, 9, 7, 0, -8 },
/* '9' 0x39 */ { 506, 6, 9, 7, 0, -8 },
/* ':' 0x3A */ { 513, 1, 7, 3, 1, -6 },
/* ';' 0x3B */ { 514, 1, 8, 3, 1, -5 },
/* '<' 0x3C */ { 515, 5, 5, 7, 1, -4 },
/* '=' 0x3D */ { 519, 5, 3, 7, 1, -3 },
/* '>' 0x3E */ { 521, 5, 5, 7, 1, -4 },
/* '?' 0x3F */ { 525, 5, 9, 7, 1, -8 },
/* '@' 0x40 */ { 531, 11, 11, 12, 0, -8 },
/* 'A' 0x41 */ { 547, 8, 9, 8, 0, -8 },
/* 'B' 0x42 */ { 556, 6, 9, 8, 1, -8 },
/* 'C' 0x43 */ { 563, 8, 9, 9, 0, -8 },
/* 'D' 0x44 */ { 572, 7, 9, 8, 1, -8 },
/* 'E' 0x45 */ { 580, 6, 9, 8, 1, -8 },
/* 'F' 0x46 */ { 587, 6, 9, 7, 1, -8 },
/* 'G' 0x47 */ { 594, 8, 9, 9, 0, -8 },
/* 'H' 0x48 */ { 603, 7, 9, 9, 1, -8 },
/* 'I' 0x49 */ { 611, 1, 9, 3, 1, -8 },
/* 'J' 0x4A */ { 613, 5, 9, 6, 0, -8 },
/* 'K' 0x4B */ { 619, 7, 9, 8, 1, -8 },
/* 'L' 0x4C */ { 627, 5, 9, 7, 1, -8 },
/* 'M' 0x4D */ { 633, 8, 9, 10, 1, -8 },
/* 'N' 0x4E */ { 642, 7, 9, 9, 1, -8 },
/* 'O' 0x4F */ { 650, 9, 9, 9, 0, -8 },
/* 'P' 0x50 */ { 661, 6, 9, 8, 1, -8 },
/* 'Q' 0x51 */ { 668, 9, 10, 9, 0, -8 },
/* 'R' 0x52 */ { 680, 7, 9, 9, 1, -8 },
/* 'S' 0x53 */ { 688, 6, 9, 8, 1, -8 },
/* 'T' 0x54 */ { 695, 7, 9, 8, 0, -8 },
/* 'U' 0x55 */ { 703, 7, 9, 9, 1, -8 },
/* 'V' 0x56 */ { 711, 7, 9, 8, 0, -8 },
/* 'W' 0x57 */ { 719, 11, 9, 11, 0, -8 },
/* 'X' 0x58 */ { 732, 6, 9, 8, 1, -8 },
/* 'Y' 0x59 */ { 739, 8, 9, 8, 0, -8 },
/* 'Z' 0x5A */ { 748, 7, 9, 7, 0, -8 },
/* '[' 0x5B */ { 756, 2, 12, 3, 1, -8 },
/* '\' 0x5C */ { 759, 3, 9, 3, 0, -8 },
/* ']' 0x5D */ { 763, 2, 12, 3, 0, -8 },
/* '^' 0x5E */ { 766, 4, 4, 6, 1, -8 },
/* '_' 0x5F */ { 768, 7, 1, 7, 0, 2 },
/* '`' 0x60 */ { 769, 1, 1, 3, 1, -8 },
/* 'a' 0x61 */ { 770, 6, 7, 7, 0, -6 },
/* 'b' 0x62 */ { 776, 5, 9, 7, 1, -8 },
/* 'c' 0x63 */ { 782, 6, 7, 6, 0, -6 },
/* 'd' 0x64 */ { 788, 6, 9, 7, 0, -8 },
/* 'e' 0x65 */ { 795, 6, 7, 6, 0, -6 },
/* 'f' 0x66 */ { 801, 3, 9, 3, 0, -8 },
/* 'g' 0x67 */ { 805, 6, 10, 7, 0, -6 },
/* 'h' 0x68 */ { 813, 5, 9, 6, 1, -8 },
/* 'i' 0x69 */ { 819, 1, 9, 3, 1, -8 },
/* 'j' 0x6A */ { 821, 2, 12, 3, 0, -8 },
/* 'k' 0x6B */ { 824, 5, 9, 6, 1, -8 },
/* 'l' 0x6C */ { 830, 1, 9, 3, 1, -8 },
/* 'm' 0x6D */ { 832, 8, 7, 10, 1, -6 },
/* 'n' 0x6E */ { 839, 5, 7, 6, 1, -6 },
/* 'o' 0x6F */ { 844, 6, 7, 6, 0, -6 },
/* 'p' 0x70 */ { 850, 5, 9, 7, 1, -6 },
/* 'q' 0x71 */ { 856, 6, 9, 7, 0, -6 },
/* 'r' 0x72 */ { 863, 3, 7, 4, 1, -6 },
/* 's' 0x73 */ { 866, 5, 7, 6, 0, -6 },
/* 't' 0x74 */ { 871, 3, 8, 3, 0, -7 },
/* 'u' 0x75 */ { 874, 5, 7, 6, 1, -6 },
/* 'v' 0x76 */ { 879, 6, 7, 6, 0, -6 },
/* 'w' 0x77 */ { 885, 8, 7, 9, 0, -6 },
/* 'x' 0x78 */ { 892, 5, 7, 6, 0, -6 },
/* 'y' 0x79 */ { 897, 5, 10, 6, 0, -6 },
/* 'z' 0x7A */ { 904, 5, 7, 6, 0, -6 },
/* '{' 0x7B */ { 909, 2, 12, 4, 1, -8 },
/* '|' 0x7C */ { 912, 1, 11, 3, 1, -8 },
/* '}' 0x7D */ { 914, 2, 12, 4, 1, -8 },
/* '~' 0x7E */ { 917, 6, 2, 6, 0, -4 },
/* 0x7F */ { 919, 0, 0, 0, 0, 0 },
/* 0x80 */ { 919, 9, 11, 9, 0, -8 },
/* 0x81 */ { 932, 6, 10, 7, 1, -9 },
/* 0x82 */ { 940, 1, 3, 3, 1, 0 },
/* 0x83 */ { 941, 4, 9, 5, 1, -8 },
/* 0x84 */ { 946, 3, 3, 5, 1, 0 },
/* 0x85 */ { 948, 5, 1, 7, 1, 0 },
/* 0x86 */ { 949, 5, 11, 7, 1, -8 },
/* 0x87 */ { 956, 5, 11, 7, 1, -8 },
/* 0x88 */ { 963, 7, 9, 8, 0, -8 },
/* 0x89 */ { 971, 12, 9, 12, 0, -8 },
/* 0x8A */ { 985, 11, 9, 13, 1, -8 },
/* 0x8B */ { 998, 2, 3, 4, 1, -4 },
/* 0x8C */ { 999, 11, 9, 12, 1, -8 },
/* 0x8D */ { 1012, 6, 11, 8, 1, -10 },
/* 0x8E */ { 1021, 9, 9, 9, 0, -8 },
/* 0x8F */ { 1032, 7, 11, 9, 1, -8 },
/* 0x90 */ { 1042, 6, 11, 7, 0, -8 },
/* 0x91 */ { 1051, 1, 3, 3, 1, -8 },
/* 0x92 */ { 1052, 1, 3, 2, 1, -8 },
/* 0x93 */ { 1053, 3, 3, 5, 1, -8 },
/* 0x94 */ { 1055, 3, 3, 5, 1, -8 },
/* 0x95 */ { 1057, 3, 3, 5, 1, -5 },
/* 0x96 */ { 1059, 6, 1, 6, 0, -3 },
/* 0x97 */ { 1060, 12, 1, 12, 0, -3 },
/* 0x98 */ { 1062, 0, 0, 8, 0, 0 },
/* 0x99 */ { 1062, 11, 7, 12, 1, -8 },
/* 0x9A */ { 1072, 9, 6, 10, 0, -5 },
/* 0x9B */ { 1079, 2, 3, 3, 1, -4 },
/* 0x9C */ { 1080, 9, 6, 10, 1, -5 },
/* 0x9D */ { 1087, 4, 9, 6, 1, -8 },
/* 0x9E */ { 1092, 6, 9, 7, 0, -8 },
/* 0x9F */ { 1099, 5, 7, 7, 1, -5 },
/* 0xA0 */ { 1104, 0, 0, 3, 0, 0 },
/* 0xA1 */ { 1104, 7, 11, 7, 0, -10 },
/* 0xA2 */ { 1114, 5, 11, 6, 0, -7 },
/* 0xA3 */ { 1121, 5, 9, 6, 0, -8 },
/* 0xA4 */ { 1127, 5, 4, 7, 1, -5 },
/* 0xA5 */ { 1130, 6, 10, 7, 1, -9 },
/* 0xA6 */ { 1138, 1, 12, 3, 1, -8 },
/* 0xA7 */ { 1140, 5, 12, 7, 1, -8 },
/* 0xA8 */ { 1148, 6, 11, 8, 1, -10 },
/* 0xA9 */ { 1157, 9, 9, 10, 0, -8 },
/* 0xAA */ { 1168, 7, 9, 9, 1, -8 },
/* 0xAB */ { 1176, 4, 4, 6, 1, -4 },
/* 0xAC */ { 1178, 2, 12, 3, 0, -8 },
/* 0xAD */ { 1181, 0, 0, 0, 0, 0 },
/* 0xAE */ { 1181, 9, 9, 10, 0, -8 },
/* 0xAF */ { 1192, 3, 11, 3, 0, -10 },
/* 0xB0 */ { 1197, 4, 4, 7, 2, -8 },
/* 0xB1 */ { 1199, 5, 7, 7, 1, -6 },
/* 0xB2 */ { 1204, 1, 9, 3, 1, -8 },
/* 0xB3 */ { 1206, 1, 9, 3, 1, -8 },
/* 0xB4 */ { 1208, 3, 8, 5, 1, -7 },
/* 0xB5 */ { 1211, 6, 9, 7, 1, -6 },
/* 0xB6 */ { 1218, 6, 10, 6, 1, -8 },
/* 0xB7 */ { 1226, 1, 1, 3, 1, -2 },
/* 0xB8 */ { 1227, 6, 9, 7, 0, -8 },
/* 0xB9 */ { 1234, 9, 9, 11, 1, -8 },
/* 0xBA */ { 1245, 6, 6, 6, 0, -5 },
/* 0xBB */ { 1250, 4, 4, 6, 1, -5 },
/* 0xBC */ { 1252, 2, 12, 3, 0, -8 },
/* 0xBD */ { 1255, 6, 9, 8, 1, -8 },
/* 0xBE */ { 1262, 5, 6, 6, 0, -5 },
/* 0xBF */ { 1266, 3, 9, 3, 0, -8 },
/* 0xC0 */ { 1270, 8, 9, 8, 0, -8 },
/* 0xC1 */ { 1279, 6, 9, 8, 1, -8 },
/* 0xC2 */ { 1286, 6, 9, 8, 1, -8 },
/* 0xC3 */ { 1293, 6, 9, 7, 1, -8 },
/* 0xC4 */ { 1300, 9, 11, 10, 0, -8 },
/* 0xC5 */ { 1313, 6, 9, 8, 1, -8 },
/* 0xC6 */ { 1320, 9, 9, 11, 1, -8 },
/* 0xC7 */ { 1331, 6, 9, 8, 1, -8 },
/* 0xC8 */ { 1338, 7, 9, 9, 1, -8 },
/* 0xC9 */ { 1346, 7, 11, 9, 1, -10 },
/* 0xCA */ { 1356, 6, 9, 8, 1, -8 },
/* 0xCB */ { 1363, 7, 9, 8, 0, -8 },
/* 0xCC */ { 1371, 8, 9, 10, 1, -8 },
/* 0xCD */ { 1380, 7, 9, 9, 1, -8 },
/* 0xCE */ { 1388, 8, 9, 10, 1, -8 },
/* 0xCF */ { 1397, 7, 9, 9, 1, -8 },
/* 0xD0 */ { 1405, 6, 9, 8, 1, -8 },
/* 0xD1 */ { 1412, 7, 9, 9, 1, -8 },
/* 0xD2 */ { 1420, 7, 9, 7, 0, -8 },
/* 0xD3 */ { 1428, 7, 9, 7, 0, -8 },
/* 0xD4 */ { 1436, 9, 9, 10, 1, -8 },
/* 0xD5 */ { 1447, 6, 9, 8, 1, -8 },
/* 0xD6 */ { 1454, 8, 11, 9, 1, -8 },
/* 0xD7 */ { 1465, 6, 9, 8, 1, -8 },
/* 0xD8 */ { 1472, 8, 9, 10, 1, -8 },
/* 0xD9 */ { 1481, 9, 11, 10, 1, -8 },
/* 0xDA */ { 1494, 10, 9, 10, 0, -8 },
/* 0xDB */ { 1506, 9, 9, 10, 1, -8 },
/* 0xDC */ { 1517, 6, 9, 8, 1, -8 },
/* 0xDD */ { 1524, 7, 9, 9, 1, -8 },
/* 0xDE */ { 1532, 10, 9, 12, 1, -8 },
/* 0xDF */ { 1544, 6, 9, 8, 1, -8 },
/* 0xE0 */ { 1551, 6, 6, 7, 0, -5 },
/* 0xE1 */ { 1556, 6, 9, 7, 0, -8 },
/* 0xE2 */ { 1563, 5, 6, 6, 1, -5 },
/* 0xE3 */ { 1567, 4, 6, 5, 1, -5 },
/* 0xE4 */ { 1570, 7, 7, 7, 0, -5 },
/* 0xE5 */ { 1577, 6, 6, 7, 0, -5 },
/* 0xE6 */ { 1582, 8, 6, 9, 1, -5 },
/* 0xE7 */ { 1588, 6, 6, 6, 0, -5 },
/* 0xE8 */ { 1593, 5, 6, 7, 1, -5 },
/* 0xE9 */ { 1597, 5, 8, 7, 1, -7 },
/* 0xEA */ { 1602, 4, 6, 6, 1, -5 },
/* 0xEB */ { 1605, 5, 6, 6, 0, -5 },
/* 0xEC */ { 1609, 6, 6, 7, 1, -5 },
/* 0xED */ { 1614, 5, 6, 7, 1, -5 },
/* 0xEE */ { 1618, 6, 6, 7, 0, -5 },
/* 0xEF */ { 1623, 5, 6, 7, 1, -5 },
/* 0xF0 */ { 1627, 5, 9, 7, 1, -5 },
/* 0xF1 */ { 1633, 6, 6, 6, 0, -5 },
/* 0xF2 */ { 1638, 5, 6, 5, 0, -5 },
/* 0xF3 */ { 1642, 5, 9, 6, 0, -5 },
/* 0xF4 */ { 1648, 10, 11, 10, 0, -7 },
/* 0xF5 */ { 1662, 5, 6, 6, 0, -5 },
/* 0xF6 */ { 1666, 6, 7, 7, 1, -5 },
/* 0xF7 */ { 1672, 4, 6, 6, 1, -5 },
/* 0xF8 */ { 1675, 6, 6, 8, 1, -5 },
/* 0xF9 */ { 1680, 7, 7, 9, 1, -5 },
/* 0xFA */ { 1687, 7, 6, 8, 0, -5 },
/* 0xFB */ { 1693, 6, 6, 8, 1, -5 },
/* 0xFC */ { 1698, 5, 6, 6, 1, -5 },
/* 0xFD */ { 1702, 5, 6, 6, 1, -5 },
/* 0xFE */ { 1706, 8, 6, 9, 1, -5 },
/* 0xFF */ { 1712, 5, 6, 7, 1, -5 },
};
const GFXfont FreeSans6pt_Win1251 PROGMEM = {
(uint8_t*)FreeSans6pt_Win1251Bitmaps,
(GFXglyph*)FreeSans6pt_Win1251Glyphs,
0x01, 0xFF, 14
};
@@ -0,0 +1,527 @@
// trunk-ignore-all(clang-format)
#pragma once
/* PROPERTIES
FONT_NAME FreeSans6pt_Win1252
*/
const uint8_t FreeSans6pt_Win1252Bitmaps[] PROGMEM = {
/* 0x01 */ 0x1C, 0x0A, 0x05, 0x04, 0xFE, 0x08, 0x1C, 0x02, 0x07, 0xE0, 0x9F, 0xC0,
/* 0x02 */ 0x3F, 0xF0, 0x40, 0xE0, 0x10, 0x3F, 0x04, 0x9E, 0x28, 0x14, 0x0E, 0x00,
/* 0x03 */ 0x3F, 0x10, 0x28, 0x06, 0x49, 0x80, 0x60, 0x19, 0x26, 0x31, 0x40, 0x8F, 0xC0,
/* 0x04 */ 0x3F, 0x10, 0x2A, 0x16, 0x49, 0xA1, 0x60, 0x19, 0xE6, 0x31, 0x40, 0x8F, 0xC0,
/* 0x05 */ 0x28, 0x15, 0x2A, 0xB5, 0x55, 0xA8, 0x54, 0x12, 0x04, 0x41, 0x08, 0x81, 0xC0,
/* 0x06 */ 0x04, 0x08, 0x88, 0x82, 0x07, 0x01, 0x11, 0xA2, 0xC4, 0x40, 0x70, 0x20, 0x88, 0x88, 0x10, 0x00,
/* 0x07 */
/* 0x08 */ 0x03, 0x83, 0x44, 0x48, 0x28, 0x01, 0x80, 0x17, 0xFE, 0x08, 0x45, 0x28, 0x84, 0x00,
/* 0x09 */ 0x01, 0xC0, 0x68, 0x82, 0x41, 0x10, 0x02, 0x80, 0x06, 0x00, 0x14, 0x00, 0x8F, 0xFC,
/* 0x0A */
/* 0x0B */ 0x22, 0x2A, 0xA2, 0x30, 0x18, 0x0A, 0x09, 0x04, 0x44, 0x14, 0x04, 0x00,
/* 0x0C */ 0x46, 0x00, 0x19, 0x03, 0x21, 0x20, 0x93, 0x04, 0x20, 0x11, 0x80, 0x50, 0x02, 0x7F, 0xE0,
/* 0x0D */
/* 0x0E */ 0x08, 0x0E, 0x08, 0x88, 0x24, 0x12, 0x09, 0x05, 0x01, 0xFF, 0x8A, 0x02, 0x00,
/* 0x0F */ 0x3F, 0x14, 0xAA, 0x16, 0x01, 0x92, 0x60, 0x18, 0xC6, 0x49, 0x40, 0x8F, 0xC0,
/* 0x10 */ 0x1B, 0x02, 0xA0, 0x54, 0x12, 0x42, 0x48, 0x49, 0x31, 0x1E, 0x23, 0xEA, 0xFE, 0x3C,
/* 0x11 */ 0x3F, 0x02, 0x00, 0x20, 0x6D, 0x27, 0xF8, 0x3F, 0xC1, 0xFE, 0x37, 0xD0, 0xBE, 0x40, 0xE1, 0xE2, 0x00,
/* 0x12 */ 0x12, 0x42, 0x20, 0x24, 0xC0, 0x29, 0x99, 0x05, 0x23, 0x30, 0xB0, 0x30, 0x00,
/* 0x13 */ 0x3F, 0x88, 0x0A, 0x44, 0xD5, 0x58, 0x03, 0x00, 0x67, 0xCC, 0x71, 0x40, 0x47, 0xF0,
/* 0x14 */ 0x3F, 0x18, 0x69, 0x26, 0x85, 0xA1, 0x6C, 0xD8, 0x06, 0x31, 0x40, 0x8F, 0xC0,
/* 0x15 */ 0x3F, 0x11, 0x00, 0xE8, 0x03, 0xA0, 0x1F, 0xB3, 0x7E, 0x00, 0xE9, 0xE0, 0x23, 0x00, 0x40, 0x40, 0xFE, 0x00,
/* 0x16 */ 0x30, 0x38, 0x3A, 0x3E, 0x6E, 0xEB, 0xC3, 0xC3, 0x66, 0x3C,
/* 0x17 */ 0x3F, 0x04, 0x00, 0x82, 0x88, 0x5C, 0xA4, 0x49, 0x22, 0x81, 0x98, 0xC4, 0x40, 0xA3, 0xF0,
/* 0x18 */ 0x07, 0x80, 0x42, 0x04, 0x08, 0x21, 0x41, 0x42, 0x60, 0x0E, 0x8C, 0xB2, 0x89, 0x50, 0x52, 0x82, 0x80,
/* 0x19 */ 0x3F, 0xC4, 0x02, 0x80, 0x18, 0x01, 0xB3, 0x1B, 0xB9, 0x80, 0x19, 0xE1, 0x40, 0x23, 0xFC,
/* 0x1A */ 0xFF, 0xC0, 0x67, 0x34, 0x58, 0x4C, 0x46, 0x03, 0x11, 0x80, 0xFF, 0xC0,
/* 0x1B */ 0x0F, 0xC0, 0x40, 0x82, 0x49, 0x08, 0x04, 0x00, 0x00, 0x12, 0x02, 0x31, 0x34, 0x0B, 0x88, 0x45, 0x00, 0x20,
/* 0x1C */ 0x3F, 0x88, 0x0A, 0x44, 0xC9, 0x19, 0x3B, 0x00, 0x60, 0x4C, 0x71, 0x40, 0x47, 0xF0,
/* 0x1D */ 0x3F, 0x8B, 0x0A, 0x00, 0xC8, 0x18, 0x13, 0x00, 0x48, 0xCA, 0xC1, 0x44, 0x53, 0x30,
/* 0x1E */ 0x19, 0xC2, 0x02, 0x50, 0x1E, 0x49, 0x80, 0x12, 0x01, 0x27, 0x92, 0x01, 0x10, 0x20, 0xFC,
/* 0x1F */ 0x30, 0x1C, 0x0C, 0x3E, 0x7E, 0xCF, 0x07, 0xC7, 0x7F, 0x3F,
/* ' ' 0x20 */
/* '!' 0x21 */ 0xFC, 0x80,
/* '"' 0x22 */ 0xB6, 0x80,
/* '#' 0x23 */ 0x24, 0x51, 0xF9, 0x42, 0x9F, 0x92, 0x28,
/* '$' 0x24 */ 0x10, 0xE5, 0x55, 0x50, 0xE1, 0x65, 0x55, 0xE1, 0x00,
/* '%' 0x25 */ 0x71, 0x24, 0x89, 0x22, 0x50, 0x74, 0x02, 0x70, 0xA4, 0x49, 0x11, 0xC0,
/* '&' 0x26 */ 0x71, 0x24, 0x9C, 0x62, 0x58, 0xA7, 0xF4,
/* ''' 0x27 */ 0xE0,
/* '(' 0x28 */ 0x5A, 0xAA, 0x94,
/* ')' 0x29 */ 0x89, 0x12, 0x49, 0x29, 0x00,
/* '*' 0x2A */ 0x5E, 0x80,
/* '+' 0x2B */ 0x21, 0x3E, 0x42, 0x00,
/* ',' 0x2C */ 0xE0,
/* '-' 0x2D */ 0xC0,
/* '.' 0x2E */ 0x80,
/* '/' 0x2F */ 0x24, 0xA4, 0xA4, 0x80,
/* '0' 0x30 */ 0x76, 0xE3, 0x18, 0xC6, 0x3B, 0x70,
/* '1' 0x31 */ 0x27, 0x92, 0x49, 0x20,
/* '2' 0x32 */ 0x79, 0x10, 0x41, 0x08, 0xC6, 0x10, 0xFC,
/* '3' 0x33 */ 0x79, 0x30, 0x43, 0x18, 0x10, 0x71, 0x78,
/* '4' 0x34 */ 0x08, 0x61, 0x8A, 0x49, 0x2F, 0xC2, 0x08,
/* '5' 0x35 */ 0xFC, 0x21, 0xE8, 0x84, 0x31, 0xF0,
/* '6' 0x36 */ 0x74, 0x61, 0xE8, 0xC6, 0x31, 0x70,
/* '7' 0x37 */ 0xF8, 0x44, 0x22, 0x11, 0x08, 0x40,
/* '8' 0x38 */ 0x39, 0x34, 0x53, 0x39, 0x1C, 0x51, 0x38,
/* '9' 0x39 */ 0x39, 0x3C, 0x71, 0x4C, 0xF0, 0x53, 0x78,
/* ':' 0x3A */ 0x82,
/* ';' 0x3B */ 0x87,
/* '<' 0x3C */ 0x3E, 0x30, 0x60, 0x80,
/* '=' 0x3D */ 0xF8, 0x3E,
/* '>' 0x3E */ 0xE0, 0xC6, 0xC8, 0x00,
/* '?' 0x3F */ 0x74, 0x42, 0x11, 0x10, 0x80, 0x20,
/* '@' 0x40 */ 0x0F, 0x86, 0x19, 0x9A, 0xA4, 0xD9, 0x13, 0x22, 0x56, 0xDA, 0x6E, 0x60, 0x06, 0x00, 0x3C, 0x00,
/* 'A' 0x41 */ 0x18, 0x18, 0x24, 0x24, 0x24, 0x7E, 0x42, 0x42, 0xC3,
/* 'B' 0x42 */ 0xFA, 0x18, 0x61, 0xFA, 0x18, 0x61, 0xFC,
/* 'C' 0x43 */ 0x3E, 0x63, 0x40, 0x40, 0xC0, 0x40, 0x41, 0x63, 0x3E,
/* 'D' 0x44 */ 0xF9, 0x0A, 0x1C, 0x18, 0x30, 0x61, 0xC2, 0xF8,
/* 'E' 0x45 */ 0xFE, 0x08, 0x20, 0xFE, 0x08, 0x20, 0xFC,
/* 'F' 0x46 */ 0xFE, 0x08, 0x20, 0xFA, 0x08, 0x20, 0x80,
/* 'G' 0x47 */ 0x1E, 0x61, 0x40, 0x40, 0xC7, 0x41, 0x41, 0x63, 0x1D,
/* 'H' 0x48 */ 0x83, 0x06, 0x0C, 0x1F, 0xF0, 0x60, 0xC1, 0x82,
/* 'I' 0x49 */ 0xFF, 0x80,
/* 'J' 0x4A */ 0x08, 0x42, 0x10, 0x87, 0x29, 0x70,
/* 'K' 0x4B */ 0x85, 0x12, 0x45, 0x0D, 0x13, 0x22, 0x42, 0x86,
/* 'L' 0x4C */ 0x84, 0x21, 0x08, 0x42, 0x10, 0xF8,
/* 'M' 0x4D */ 0xC3, 0xC3, 0xC3, 0xA5, 0xA5, 0xA5, 0x99, 0x99, 0x99,
/* 'N' 0x4E */ 0x83, 0x86, 0x8D, 0x19, 0x33, 0x62, 0xC3, 0x86,
/* 'O' 0x4F */ 0x1E, 0x31, 0x90, 0x68, 0x1C, 0x0A, 0x05, 0x06, 0xC6, 0x1E, 0x00,
/* 'P' 0x50 */ 0xFA, 0x18, 0x61, 0xFA, 0x08, 0x20, 0x80,
/* 'Q' 0x51 */ 0x1E, 0x31, 0x90, 0x68, 0x1C, 0x0A, 0x05, 0x16, 0xC6, 0x1F, 0x00, 0x40,
/* 'R' 0x52 */ 0xFD, 0x0E, 0x1C, 0x2F, 0x90, 0xA1, 0x42, 0x86,
/* 'S' 0x53 */ 0x7A, 0x18, 0x30, 0x78, 0x38, 0x61, 0x78,
/* 'T' 0x54 */ 0xFE, 0x20, 0x40, 0x81, 0x02, 0x04, 0x08, 0x10,
/* 'U' 0x55 */ 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xE2, 0x78,
/* 'V' 0x56 */ 0xC2, 0x85, 0x0B, 0x22, 0x44, 0x8E, 0x0C, 0x18,
/* 'W' 0x57 */ 0xC4, 0x28, 0xCD, 0x29, 0x25, 0x24, 0xA4, 0x52, 0x8C, 0x61, 0x8C, 0x31, 0x80,
/* 'X' 0x58 */ 0x87, 0x34, 0x8C, 0x30, 0xC4, 0xA3, 0x84,
/* 'Y' 0x59 */ 0xC3, 0x42, 0x24, 0x34, 0x18, 0x08, 0x08, 0x08, 0x08,
/* 'Z' 0x5A */ 0x7E, 0x0C, 0x30, 0x41, 0x06, 0x18, 0x20, 0xFE,
/* '[' 0x5B */ 0xEA, 0xAA, 0xAB,
/* '\' 0x5C */ 0x92, 0x24, 0x89, 0x20,
/* ']' 0x5D */ 0xD5, 0x55, 0x57,
/* '^' 0x5E */ 0x46, 0xA9,
/* '_' 0x5F */ 0xFE,
/* '`' 0x60 */ 0x80,
/* 'a' 0x61 */ 0x79, 0x20, 0x4F, 0xC6, 0x37, 0x40,
/* 'b' 0x62 */ 0x84, 0x3D, 0x18, 0xC6, 0x31, 0xF0,
/* 'c' 0x63 */ 0x39, 0x3C, 0x20, 0xC1, 0x33, 0x80,
/* 'd' 0x64 */ 0x04, 0x13, 0xD3, 0xC6, 0x1C, 0x53, 0x3C,
/* 'e' 0x65 */ 0x39, 0x38, 0x7F, 0x81, 0x13, 0x80,
/* 'f' 0x66 */ 0x6B, 0xA4, 0x92, 0x40,
/* 'g' 0x67 */ 0x35, 0x3C, 0x61, 0xC5, 0x33, 0x41, 0x4D, 0xE0,
/* 'h' 0x68 */ 0x84, 0x3D, 0x38, 0xC6, 0x31, 0x88,
/* 'i' 0x69 */ 0xBF, 0x80,
/* 'j' 0x6A */ 0x45, 0x55, 0x57,
/* 'k' 0x6B */ 0x84, 0x25, 0x4E, 0x52, 0xD2, 0x88,
/* 'l' 0x6C */ 0xFF, 0x80,
/* 'm' 0x6D */ 0xF7, 0x99, 0x91, 0x91, 0x91, 0x91, 0x91,
/* 'n' 0x6E */ 0xF4, 0x63, 0x18, 0xC6, 0x20,
/* 'o' 0x6F */ 0x39, 0x3C, 0x61, 0xC5, 0x33, 0x80,
/* 'p' 0x70 */ 0xF4, 0x63, 0x18, 0xC7, 0xD0, 0x80,
/* 'q' 0x71 */ 0x3D, 0x3C, 0x61, 0xC5, 0x37, 0x41, 0x04,
/* 'r' 0x72 */ 0xF2, 0x49, 0x20,
/* 's' 0x73 */ 0x7A, 0x50, 0xE0, 0xE5, 0xE0,
/* 't' 0x74 */ 0x5D, 0x24, 0x93,
/* 'u' 0x75 */ 0x8C, 0x63, 0x18, 0xCF, 0xA0,
/* 'v' 0x76 */ 0x85, 0x24, 0x92, 0x30, 0xC3, 0x00,
/* 'w' 0x77 */ 0x89, 0x59, 0x59, 0x55, 0x56, 0x26, 0x26,
/* 'x' 0x78 */ 0x4A, 0x4C, 0x43, 0x27, 0x20,
/* 'y' 0x79 */ 0x8A, 0x52, 0xA5, 0x18, 0x84, 0x22, 0x00,
/* 'z' 0x7A */ 0x78, 0x44, 0x46, 0x23, 0xE0,
/* '{' 0x7B */ 0x6A, 0xAA, 0xA9,
/* '|' 0x7C */ 0xFF, 0xE0,
/* '}' 0x7D */ 0x95, 0x55, 0x56,
/* '~' 0x7E */ 0x66, 0x60,
/* 0x7F */
/* 0x80 */ 0x1C, 0x45, 0x07, 0xE4, 0x1F, 0x10, 0x10, 0x1E,
/* 0x81 */
/* 0x82 */ 0xE0,
/* 0x83 */ 0x6B, 0xA4, 0x92, 0x49, 0x60,
/* 0x84 */ 0xB6, 0x80,
/* 0x85 */ 0xA8,
/* 0x86 */ 0x21, 0x09, 0xF2, 0x10, 0x84, 0x21, 0x08,
/* 0x87 */ 0x21, 0x09, 0xF2, 0x10, 0x84, 0xF9, 0x08,
/* 0x88 */ 0x54,
/* 0x89 */ 0x62, 0x09, 0x40, 0x98, 0x06, 0x80, 0x10, 0x01, 0x66, 0x29, 0x92, 0x99, 0x06, 0x60,
/* 0x8A */ 0x28, 0x47, 0xA1, 0x83, 0x07, 0x83, 0x87, 0x17, 0x80,
/* 0x8B */ 0x64,
/* 0x8C */ 0x3B, 0xE8, 0xC2, 0x08, 0x41, 0x08, 0x3F, 0x04, 0x20, 0x82, 0x30, 0x3B, 0xE0,
/* 0x8D */
/* 0x8E */ 0x14, 0x11, 0xF8, 0x30, 0xC1, 0x04, 0x18, 0x61, 0xFC,
/* 0x8F */
/* 0x90 */
/* 0x91 */ 0xE0,
/* 0x92 */ 0xE0,
/* 0x93 */ 0xB6, 0x80,
/* 0x94 */ 0xB6, 0x80,
/* 0x95 */ 0xFF, 0x80,
/* 0x96 */ 0xFC,
/* 0x97 */ 0xFF, 0xF0,
/* 0x98 */ 0xDB,
/* 0x99 */ 0xE6, 0x28, 0xCD, 0x19, 0xA3, 0x34, 0x6A, 0x8B, 0x51, 0x68,
/* 0x9A */ 0x52, 0x69, 0x8E, 0x19, 0x60,
/* 0x9B */ 0x98,
/* 0x9C */ 0x7B, 0xD9, 0xCE, 0x10, 0xC3, 0xF8, 0x41, 0x9C, 0x5E, 0xF0,
/* 0x9D */
/* 0x9E */ 0x51, 0x1E, 0x11, 0x11, 0x88, 0xF8,
/* 0x9F */ 0x29, 0x05, 0x12, 0x22, 0x87, 0x04, 0x08, 0x10, 0x20,
/* 0xA0 */
/* 0xA1 */ 0xBF, 0x80,
/* 0xA2 */ 0x23, 0xAB, 0x4A, 0x52, 0xAE, 0x20,
/* 0xA3 */ 0x39, 0x14, 0x10, 0xF0, 0x82, 0x1C, 0x4C,
/* 0xA4 */ 0xFC, 0x63, 0xF0,
/* 0xA5 */ 0x8C, 0x54, 0xAF, 0x93, 0xE4, 0x20,
/* 0xA6 */ 0xF9, 0xF0,
/* 0xA7 */ 0x32, 0x91, 0xC9, 0x47, 0x26, 0x14, 0xA4, 0xC0,
/* 0xA8 */ 0xA0,
/* 0xA9 */ 0x3E, 0x3F, 0xB8, 0xF4, 0x1A, 0x0D, 0x17, 0x76, 0xC6, 0x3E, 0x00,
/* 0xAA */ 0x61, 0x79, 0x60,
/* 0xAB */ 0x5A, 0xA5,
/* 0xAC */ 0xFC, 0x10, 0x40,
/* 0xAD */
/* 0xAE */ 0x3E, 0x31, 0xB7, 0x72, 0x99, 0xCC, 0xC7, 0x56, 0xC6, 0x3E, 0x00,
/* 0xAF */ 0xE0,
/* 0xB0 */ 0x69, 0x96,
/* 0xB1 */ 0x21, 0x3E, 0x42, 0x03, 0xE0,
/* 0xB2 */ 0x69, 0x3C, 0xF0,
/* 0xB3 */ 0x79, 0x29, 0x70,
/* 0xB4 */ 0x80,
/* 0xB5 */ 0x8A, 0x28, 0xA2, 0x8A, 0x6E, 0xE0, 0x80,
/* 0xB6 */ 0x7F, 0xAE, 0xBA, 0x68, 0xA2, 0x8A, 0x28, 0xA0,
/* 0xB7 */ 0x80,
/* 0xB8 */ 0x67, 0x80,
/* 0xB9 */ 0x75, 0x50,
/* 0xBA */ 0x69, 0x96, 0xF0,
/* 0xBB */ 0xA5, 0x5A,
/* 0xBC */ 0x42, 0x30, 0x84, 0x41, 0x10, 0x48, 0x82, 0x61, 0x28, 0x8F, 0x20, 0x80,
/* 0xBD */ 0x40, 0x63, 0x11, 0x09, 0x74, 0xA8, 0x84, 0x44, 0x44, 0x43, 0x80,
/* 0xBE */ 0x71, 0x24, 0x82, 0x20, 0x50, 0x98, 0x9A, 0x61, 0x28, 0x4F, 0x20, 0x80,
/* 0xBF */ 0x20, 0x08, 0x44, 0x42, 0x11, 0x70,
/* 0xC0 */ 0x10, 0x08, 0x00, 0x18, 0x3C, 0x24, 0x24, 0x7E, 0x42, 0xC3,
/* 0xC1 */ 0x08, 0x10, 0x00, 0x18, 0x3C, 0x24, 0x24, 0x7E, 0x42, 0xC3,
/* 0xC2 */ 0x18, 0x24, 0x00, 0x18, 0x3C, 0x24, 0x24, 0x7E, 0x42, 0xC3,
/* 0xC3 */ 0x34, 0x2C, 0x00, 0x18, 0x3C, 0x24, 0x24, 0x7E, 0x42, 0xC3,
/* 0xC4 */ 0x24, 0x00, 0x18, 0x3C, 0x24, 0x24, 0x7E, 0x42, 0x42, 0xC3,
/* 0xC5 */ 0x18, 0x24, 0x18, 0x18, 0x3C, 0x24, 0x24, 0x7E, 0x42, 0xC3,
/* 0xC6 */ 0x1F, 0xC5, 0x02, 0x40, 0x90, 0x47, 0xDF, 0x04, 0x42, 0x10, 0x87, 0xC0,
/* 0xC7 */ 0x3E, 0x61, 0xC0, 0x80, 0x80, 0x80, 0xC1, 0x63, 0x3E, 0x0C, 0x04, 0x1C,
/* 0xC8 */ 0x20, 0x40, 0x3F, 0x82, 0x0F, 0xA0, 0x83, 0xF0,
/* 0xC9 */ 0x08, 0x40, 0x3F, 0x82, 0x0F, 0xA0, 0x83, 0xF0,
/* 0xCA */ 0x10, 0xA0, 0x3F, 0x82, 0x0F, 0xA0, 0x83, 0xF0,
/* 0xCB */ 0x28, 0x0F, 0xE0, 0x83, 0xE8, 0x20, 0x83, 0xF0,
/* 0xCC */ 0x91, 0x55, 0x50,
/* 0xCD */ 0x62, 0xAA, 0xA0,
/* 0xCE */ 0x54, 0x24, 0x92, 0x48,
/* 0xCF */ 0xA1, 0x24, 0x92, 0x48,
/* 0xD0 */ 0x7C, 0x42, 0x41, 0x41, 0xF1, 0x41, 0x41, 0x42, 0x7C,
/* 0xD1 */ 0x14, 0x53, 0x0F, 0x1B, 0x32, 0x66, 0xC7, 0x87, 0x04,
/* 0xD2 */ 0x10, 0x04, 0x0F, 0x8C, 0x6C, 0x1C, 0x06, 0x03, 0x83, 0x63, 0x1F, 0x00,
/* 0xD3 */ 0x04, 0x04, 0x0F, 0x8C, 0x6C, 0x1C, 0x06, 0x03, 0x83, 0x63, 0x1F, 0x00,
/* 0xD4 */ 0x08, 0x0A, 0x00, 0x07, 0xC6, 0x36, 0x0E, 0x03, 0x01, 0xC1, 0xB1, 0x8F, 0x80,
/* 0xD5 */ 0x1A, 0x0B, 0x00, 0x07, 0xC6, 0x36, 0x0E, 0x03, 0x01, 0xC1, 0xB1, 0x8F, 0x80,
/* 0xD6 */ 0x14, 0x00, 0x00, 0x07, 0xC6, 0x36, 0x0E, 0x03, 0x01, 0xC1, 0xB1, 0x8F, 0x80,
/* 0xD7 */ 0x8A, 0x88, 0xA8, 0x80,
/* 0xD8 */ 0x3E, 0xB1, 0xB0, 0xF0, 0x98, 0x8C, 0x87, 0x86, 0xC6, 0xBE, 0x00,
/* 0xD9 */ 0x20, 0x22, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0xC6, 0xF8,
/* 0xDA */ 0x08, 0x22, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0xC6, 0xF8,
/* 0xDB */ 0x10, 0x52, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0xC6, 0xF8,
/* 0xDC */ 0x29, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0xC6, 0xF8,
/* 0xDD */ 0x09, 0x25, 0x12, 0x22, 0x87, 0x04, 0x08, 0x10, 0x20,
/* 0xDE */ 0x83, 0xE8, 0x61, 0x87, 0xE8, 0x20, 0x80,
/* 0xDF */ 0x7A, 0x18, 0x61, 0x8A, 0x18, 0x61, 0xB8,
/* 0xE0 */ 0x20, 0x20, 0x03, 0xC8, 0x40, 0x8F, 0x62, 0x8C, 0xEC,
/* 0xE1 */ 0x10, 0x40, 0x03, 0xC8, 0x40, 0x8F, 0x62, 0x8C, 0xEC,
/* 0xE2 */ 0x10, 0x50, 0x03, 0xC8, 0x40, 0x8F, 0x62, 0x8C, 0xEC,
/* 0xE3 */ 0x68, 0xB0, 0x03, 0xC8, 0x40, 0x8F, 0x62, 0x8C, 0xEC,
/* 0xE4 */ 0x28, 0x01, 0xE4, 0x20, 0x47, 0xB1, 0x46, 0x76,
/* 0xE5 */ 0x10, 0x50, 0x43, 0xC8, 0x40, 0x8F, 0x62, 0x8C, 0xEC,
/* 0xE6 */ 0x7B, 0xA1, 0x90, 0x45, 0xFF, 0x84, 0x23, 0x17, 0x38,
/* 0xE7 */ 0x7B, 0x18, 0x20, 0x83, 0x17, 0x8C, 0x11, 0xC0,
/* 0xE8 */ 0x20, 0x40, 0x1E, 0xCE, 0x1F, 0xE0, 0xC5, 0xE0,
/* 0xE9 */ 0x10, 0x80, 0x1E, 0xCE, 0x1F, 0xE0, 0xC5, 0xE0,
/* 0xEA */ 0x10, 0xA0, 0x1E, 0xCE, 0x1F, 0xE0, 0xC5, 0xE0,
/* 0xEB */ 0x28, 0x07, 0xB3, 0x87, 0xF8, 0x31, 0x78,
/* 0xEC */ 0x91, 0x55, 0x50,
/* 0xED */ 0x62, 0xAA, 0xA0,
/* 0xEE */ 0x54, 0x24, 0x92, 0x48,
/* 0xEF */ 0xA1, 0x24, 0x92, 0x40,
/* 0xF0 */ 0x28, 0x42, 0x8F, 0x46, 0x18, 0x52, 0x30,
/* 0xF1 */ 0x6A, 0xC1, 0x6C, 0xC6, 0x31, 0x8C, 0x40,
/* 0xF2 */ 0x20, 0x40, 0x1E, 0xCE, 0x18, 0x61, 0xCD, 0xE0,
/* 0xF3 */ 0x10, 0x80, 0x1E, 0xCE, 0x18, 0x61, 0xCD, 0xE0,
/* 0xF4 */ 0x10, 0xA0, 0x1E, 0xCE, 0x18, 0x61, 0xCD, 0xE0,
/* 0xF5 */ 0x69, 0x60, 0x1E, 0xCE, 0x18, 0x61, 0xCD, 0xE0,
/* 0xF6 */ 0x28, 0x07, 0xB3, 0x86, 0x18, 0x73, 0x78,
/* 0xF7 */ 0x20, 0x3E, 0x02, 0x00,
/* 0xF8 */ 0x7F, 0x39, 0x69, 0xC7, 0x3F, 0x80,
/* 0xF9 */ 0x41, 0x23, 0x18, 0xC6, 0x33, 0x68,
/* 0xFA */ 0x11, 0x23, 0x18, 0xC6, 0x33, 0x68,
/* 0xFB */ 0x22, 0x81, 0x18, 0xC6, 0x31, 0x9B, 0x40,
/* 0xFC */ 0x50, 0x23, 0x18, 0xC6, 0x33, 0x68,
/* 0xFD */ 0x10, 0x88, 0x52, 0x49, 0x23, 0x0C, 0x30, 0x82, 0x18,
/* 0xFE */ 0x84, 0x3D, 0xB8, 0xC6, 0x3B, 0xF4, 0x20,
/* 0xFF */ 0x28, 0x08, 0x52, 0x49, 0x23, 0x0C, 0x30, 0x82, 0x18,
};
const GFXglyph FreeSans6pt_Win1252Glyphs[] PROGMEM = {
/* 0x01 */ { 0, 9, 10, 11, 1, -9 },
/* 0x02 */ { 12, 9, 10, 11, 1, -8 },
/* 0x03 */ { 24, 10, 10, 12, 1, -8 },
/* 0x04 */ { 37, 10, 10, 12, 1, -8 },
/* 0x05 */ { 50, 10, 10, 12, 1, -9 },
/* 0x06 */ { 63, 11, 11, 13, 1, -9 },
/* 0x07 */ { 79, 0, 0, 8, 0, 0 },
/* 0x08 */ { 79, 12, 9, 14, 1, -8 },
/* 0x09 */ { 93, 14, 8, 16, 1, -7 },
/* 0x0A */ { 107, 0, 0, 8, 0, 0 },
/* 0x0B */ { 107, 9, 10, 11, 1, -9 },
/* 0x0C */ { 119, 13, 9, 15, 1, -8 },
/* 0x0D */ { 134, 0, 0, 8, 0, 0 },
/* 0x0E */ { 134, 9, 11, 11, 1, -9 },
/* 0x0F */ { 147, 10, 10, 12, 1, -9 },
/* 0x10 */ { 160, 11, 10, 13, 1, -9 },
/* 0x11 */ { 174, 13, 10, 15, 1, -9 },
/* 0x12 */ { 191, 10, 10, 12, 1, -9 },
/* 0x13 */ { 204, 11, 10, 13, 1, -9 },
/* 0x14 */ { 218, 10, 10, 12, 1, -9 },
/* 0x15 */ { 231, 14, 10, 16, 1, -9 },
/* 0x16 */ { 249, 8, 10, 10, 1, -9 },
/* 0x17 */ { 259, 12, 10, 14, 1, -9 },
/* 0x18 */ { 274, 13, 10, 15, 1, -9 },
/* 0x19 */ { 291, 12, 10, 14, 1, -9 },
/* 0x1A */ { 306, 9, 10, 11, 1, -8 },
/* 0x1B */ { 318, 14, 10, 16, 1, -9 },
/* 0x1C */ { 336, 11, 10, 13, 1, -9 },
/* 0x1D */ { 350, 11, 10, 13, 1, -9 },
/* 0x1E */ { 364, 12, 10, 14, 1, -9 },
/* 0x1F */ { 379, 8, 10, 11, 2, -9 },
/* ' ' 0x20 */ { 389, 0, 0, 3, 0, 0 },
/* '!' 0x21 */ { 389, 1, 9, 4, 2, -8 },
/* '"' 0x22 */ { 391, 3, 3, 4, 0, -8 },
/* '#' 0x23 */ { 393, 7, 8, 7, 0, -7 },
/* '$' 0x24 */ { 400, 6, 11, 7, 0, -9 },
/* '%' 0x25 */ { 409, 10, 9, 11, 0, -8 },
/* '&' 0x26 */ { 421, 6, 9, 8, 1, -8 },
/* ''' 0x27 */ { 428, 1, 3, 2, 1, -8 },
/* '(' 0x28 */ { 429, 2, 11, 4, 1, -8 },
/* ')' 0x29 */ { 432, 3, 11, 4, 0, -8 },
/* '*' 0x2A */ { 437, 3, 3, 5, 1, -8 },
/* '+' 0x2B */ { 439, 5, 5, 7, 1, -4 },
/* ',' 0x2C */ { 443, 1, 3, 3, 1, 0 },
/* '-' 0x2D */ { 444, 2, 1, 4, 1, -3 },
/* '.' 0x2E */ { 445, 1, 1, 3, 1, 0 },
/* '/' 0x2F */ { 446, 3, 9, 3, 0, -8 },
/* '0' 0x30 */ { 450, 5, 9, 7, 1, -8 },
/* '1' 0x31 */ { 456, 3, 9, 7, 1, -8 },
/* '2' 0x32 */ { 460, 6, 9, 7, 0, -8 },
/* '3' 0x33 */ { 467, 6, 9, 7, 0, -8 },
/* '4' 0x34 */ { 474, 6, 9, 7, 0, -8 },
/* '5' 0x35 */ { 481, 5, 9, 7, 1, -8 },
/* '6' 0x36 */ { 487, 5, 9, 7, 1, -8 },
/* '7' 0x37 */ { 493, 5, 9, 7, 1, -8 },
/* '8' 0x38 */ { 499, 6, 9, 7, 0, -8 },
/* '9' 0x39 */ { 506, 6, 9, 7, 0, -8 },
/* ':' 0x3A */ { 513, 1, 7, 3, 1, -6 },
/* ';' 0x3B */ { 514, 1, 8, 3, 1, -5 },
/* '<' 0x3C */ { 515, 5, 5, 7, 1, -4 },
/* '=' 0x3D */ { 519, 5, 3, 7, 1, -3 },
/* '>' 0x3E */ { 521, 5, 5, 7, 1, -4 },
/* '?' 0x3F */ { 525, 5, 9, 7, 1, -8 },
/* '@' 0x40 */ { 531, 11, 11, 12, 0, -8 },
/* 'A' 0x41 */ { 547, 8, 9, 8, 0, -8 },
/* 'B' 0x42 */ { 556, 6, 9, 8, 1, -8 },
/* 'C' 0x43 */ { 563, 8, 9, 9, 0, -8 },
/* 'D' 0x44 */ { 572, 7, 9, 8, 1, -8 },
/* 'E' 0x45 */ { 580, 6, 9, 8, 1, -8 },
/* 'F' 0x46 */ { 587, 6, 9, 7, 1, -8 },
/* 'G' 0x47 */ { 594, 8, 9, 9, 0, -8 },
/* 'H' 0x48 */ { 603, 7, 9, 9, 1, -8 },
/* 'I' 0x49 */ { 611, 1, 9, 3, 1, -8 },
/* 'J' 0x4A */ { 613, 5, 9, 6, 0, -8 },
/* 'K' 0x4B */ { 619, 7, 9, 8, 1, -8 },
/* 'L' 0x4C */ { 627, 5, 9, 7, 1, -8 },
/* 'M' 0x4D */ { 633, 8, 9, 10, 1, -8 },
/* 'N' 0x4E */ { 642, 7, 9, 9, 1, -8 },
/* 'O' 0x4F */ { 650, 9, 9, 9, 0, -8 },
/* 'P' 0x50 */ { 661, 6, 9, 8, 1, -8 },
/* 'Q' 0x51 */ { 668, 9, 10, 9, 0, -8 },
/* 'R' 0x52 */ { 680, 7, 9, 9, 1, -8 },
/* 'S' 0x53 */ { 688, 6, 9, 8, 1, -8 },
/* 'T' 0x54 */ { 695, 7, 9, 8, 0, -8 },
/* 'U' 0x55 */ { 703, 7, 9, 9, 1, -8 },
/* 'V' 0x56 */ { 711, 7, 9, 8, 0, -8 },
/* 'W' 0x57 */ { 719, 11, 9, 11, 0, -8 },
/* 'X' 0x58 */ { 732, 6, 9, 8, 1, -8 },
/* 'Y' 0x59 */ { 739, 8, 9, 8, 0, -8 },
/* 'Z' 0x5A */ { 748, 7, 9, 7, 0, -8 },
/* '[' 0x5B */ { 756, 2, 12, 3, 1, -8 },
/* '\' 0x5C */ { 759, 3, 9, 3, 0, -8 },
/* ']' 0x5D */ { 763, 2, 12, 3, 0, -8 },
/* '^' 0x5E */ { 766, 4, 4, 6, 1, -8 },
/* '_' 0x5F */ { 768, 7, 1, 7, 0, 2 },
/* '`' 0x60 */ { 769, 1, 1, 3, 1, -8 },
/* 'a' 0x61 */ { 770, 6, 7, 7, 0, -6 },
/* 'b' 0x62 */ { 776, 5, 9, 7, 1, -8 },
/* 'c' 0x63 */ { 782, 6, 7, 6, 0, -6 },
/* 'd' 0x64 */ { 788, 6, 9, 7, 0, -8 },
/* 'e' 0x65 */ { 795, 6, 7, 6, 0, -6 },
/* 'f' 0x66 */ { 801, 3, 9, 3, 0, -8 },
/* 'g' 0x67 */ { 805, 6, 10, 7, 0, -6 },
/* 'h' 0x68 */ { 813, 5, 9, 6, 1, -8 },
/* 'i' 0x69 */ { 819, 1, 9, 3, 1, -8 },
/* 'j' 0x6A */ { 821, 2, 12, 3, 0, -8 },
/* 'k' 0x6B */ { 824, 5, 9, 6, 1, -8 },
/* 'l' 0x6C */ { 830, 1, 9, 3, 1, -8 },
/* 'm' 0x6D */ { 832, 8, 7, 10, 1, -6 },
/* 'n' 0x6E */ { 839, 5, 7, 6, 1, -6 },
/* 'o' 0x6F */ { 844, 6, 7, 6, 0, -6 },
/* 'p' 0x70 */ { 850, 5, 9, 7, 1, -6 },
/* 'q' 0x71 */ { 856, 6, 9, 7, 0, -6 },
/* 'r' 0x72 */ { 863, 3, 7, 4, 1, -6 },
/* 's' 0x73 */ { 866, 5, 7, 6, 0, -6 },
/* 't' 0x74 */ { 871, 3, 8, 3, 0, -7 },
/* 'u' 0x75 */ { 874, 5, 7, 6, 1, -6 },
/* 'v' 0x76 */ { 879, 6, 7, 6, 0, -6 },
/* 'w' 0x77 */ { 885, 8, 7, 9, 0, -6 },
/* 'x' 0x78 */ { 892, 5, 7, 6, 0, -6 },
/* 'y' 0x79 */ { 897, 5, 10, 6, 0, -6 },
/* 'z' 0x7A */ { 904, 5, 7, 6, 0, -6 },
/* '{' 0x7B */ { 909, 2, 12, 4, 1, -8 },
/* '|' 0x7C */ { 912, 1, 11, 3, 1, -8 },
/* '}' 0x7D */ { 914, 2, 12, 4, 1, -8 },
/* '~' 0x7E */ { 917, 6, 2, 6, 0, -4 },
/* 0x7F */ { 919, 0, 0, 0, 0, 0 },
/* 0x80 */ { 919, 7, 9, 8, 0, -8 },
/* 0x81 */ { 927, 0, 0, 8, 0, 0 },
/* 0x82 */ { 927, 1, 3, 3, 1, 0 },
/* 0x83 */ { 928, 3, 12, 3, 0, -8 },
/* 0x84 */ { 933, 3, 3, 5, 1, 0 },
/* 0x85 */ { 935, 5, 1, 7, 1, 0 },
/* 0x86 */ { 936, 5, 11, 7, 1, -8 },
/* 0x87 */ { 943, 5, 11, 7, 1, -8 },
/* 0x88 */ { 950, 3, 2, 4, 0, -9 },
/* 0x89 */ { 951, 12, 9, 12, 0, -8 },
/* 0x8A */ { 965, 6, 11, 8, 1, -9 },
/* 0x8B */ { 974, 2, 3, 4, 1, -4 },
/* 0x8C */ { 975, 11, 9, 12, 0, -8 },
/* 0x8D */ { 988, 0, 0, 8, 0, 0 },
/* 0x8E */ { 988, 7, 10, 7, 0, -9 },
/* 0x8F */ { 997, 0, 0, 8, 0, 0 },
/* 0x90 */ { 997, 0, 0, 8, 0, 0 },
/* 0x91 */ { 997, 1, 3, 3, 1, -8 },
/* 0x92 */ { 998, 1, 3, 2, 1, -8 },
/* 0x93 */ { 999, 3, 3, 5, 1, -8 },
/* 0x94 */ { 1001, 3, 3, 5, 1, -8 },
/* 0x95 */ { 1003, 3, 3, 5, 1, -5 },
/* 0x96 */ { 1005, 6, 1, 6, 0, -3 },
/* 0x97 */ { 1006, 12, 1, 12, 0, -3 },
/* 0x98 */ { 1008, 4, 2, 4, 0, -8 },
/* 0x99 */ { 1009, 11, 7, 12, 1, -8 },
/* 0x9A */ { 1019, 4, 9, 6, 1, -8 },
/* 0x9B */ { 1024, 2, 3, 3, 1, -4 },
/* 0x9C */ { 1025, 11, 7, 11, 0, -6 },
/* 0x9D */ { 1035, 0, 0, 8, 0, 0 },
/* 0x9E */ { 1035, 5, 9, 6, 0, -8 },
/* 0x9F */ { 1041, 7, 10, 8, 1, -9 },
/* 0xA0 */ { 1050, 0, 0, 3, 0, 0 },
/* 0xA1 */ { 1050, 1, 9, 4, 1, -5 },
/* 0xA2 */ { 1052, 5, 9, 7, 1, -7 },
/* 0xA3 */ { 1058, 6, 9, 7, 0, -8 },
/* 0xA4 */ { 1065, 5, 4, 7, 1, -5 },
/* 0xA5 */ { 1068, 5, 9, 7, 1, -8 },
/* 0xA6 */ { 1074, 1, 12, 3, 1, -8 },
/* 0xA7 */ { 1076, 5, 12, 7, 1, -8 },
/* 0xA8 */ { 1084, 3, 1, 4, 0, -7 },
/* 0xA9 */ { 1085, 9, 9, 10, 0, -8 },
/* 0xAA */ { 1096, 4, 5, 4, 0, -8 },
/* 0xAB */ { 1099, 4, 4, 6, 1, -4 },
/* 0xAC */ { 1101, 6, 3, 7, 1, -4 },
/* 0xAD */ { 1104, 0, 0, 0, 0, 0 },
/* 0xAE */ { 1104, 9, 9, 10, 0, -8 },
/* 0xAF */ { 1115, 3, 1, 4, 0, -8 },
/* 0xB0 */ { 1116, 4, 4, 7, 2, -8 },
/* 0xB1 */ { 1118, 5, 7, 7, 1, -6 },
/* 0xB2 */ { 1123, 4, 5, 4, 0, -9 },
/* 0xB3 */ { 1126, 4, 5, 4, 0, -9 },
/* 0xB4 */ { 1129, 1, 1, 4, 1, -8 },
/* 0xB5 */ { 1130, 6, 9, 7, 1, -6 },
/* 0xB6 */ { 1137, 6, 10, 6, 1, -8 },
/* 0xB7 */ { 1145, 1, 1, 3, 1, -2 },
/* 0xB8 */ { 1146, 3, 3, 4, 1, 1 },
/* 0xB9 */ { 1148, 2, 6, 4, 1, -9 },
/* 0xBA */ { 1150, 4, 5, 4, 0, -8 },
/* 0xBB */ { 1153, 4, 4, 6, 1, -5 },
/* 0xBC */ { 1155, 10, 9, 10, 1, -8 },
/* 0xBD */ { 1167, 9, 9, 10, 1, -8 },
/* 0xBE */ { 1178, 10, 9, 11, 0, -8 },
/* 0xBF */ { 1190, 5, 9, 7, 1, -5 },
/* 0xC0 */ { 1196, 8, 10, 8, 0, -9 },
/* 0xC1 */ { 1206, 8, 10, 8, 0, -9 },
/* 0xC2 */ { 1216, 8, 10, 8, 0, -9 },
/* 0xC3 */ { 1226, 8, 10, 8, 0, -9 },
/* 0xC4 */ { 1236, 8, 10, 8, 0, -9 },
/* 0xC5 */ { 1246, 8, 10, 8, 0, -9 },
/* 0xC6 */ { 1256, 10, 9, 12, 1, -8 },
/* 0xC7 */ { 1268, 8, 12, 9, 0, -8 },
/* 0xC8 */ { 1280, 6, 10, 8, 1, -9 },
/* 0xC9 */ { 1288, 6, 10, 8, 1, -9 },
/* 0xCA */ { 1296, 6, 10, 8, 1, -9 },
/* 0xCB */ { 1304, 6, 10, 8, 1, -9 },
/* 0xCC */ { 1312, 2, 10, 3, 0, -9 },
/* 0xCD */ { 1315, 2, 10, 3, 1, -9 },
/* 0xCE */ { 1318, 3, 10, 4, 0, -9 },
/* 0xCF */ { 1322, 3, 10, 4, 0, -9 },
/* 0xD0 */ { 1326, 8, 9, 8, 0, -8 },
/* 0xD1 */ { 1335, 7, 10, 9, 1, -9 },
/* 0xD2 */ { 1344, 9, 10, 9, 0, -9 },
/* 0xD3 */ { 1356, 9, 10, 9, 0, -9 },
/* 0xD4 */ { 1368, 9, 11, 9, 0, -10 },
/* 0xD5 */ { 1381, 9, 11, 9, 0, -10 },
/* 0xD6 */ { 1394, 9, 11, 9, 0, -10 },
/* 0xD7 */ { 1407, 5, 5, 7, 1, -5 },
/* 0xD8 */ { 1411, 9, 9, 9, 0, -8 },
/* 0xD9 */ { 1422, 7, 10, 9, 1, -9 },
/* 0xDA */ { 1431, 7, 10, 9, 1, -9 },
/* 0xDB */ { 1440, 7, 10, 9, 1, -9 },
/* 0xDC */ { 1449, 7, 10, 9, 1, -9 },
/* 0xDD */ { 1458, 7, 10, 8, 1, -9 },
/* 0xDE */ { 1467, 6, 9, 8, 1, -8 },
/* 0xDF */ { 1474, 6, 9, 7, 1, -8 },
/* 0xE0 */ { 1481, 7, 10, 7, 0, -9 },
/* 0xE1 */ { 1490, 7, 10, 7, 0, -9 },
/* 0xE2 */ { 1499, 7, 10, 7, 0, -9 },
/* 0xE3 */ { 1508, 7, 10, 7, 0, -9 },
/* 0xE4 */ { 1517, 7, 9, 7, 0, -8 },
/* 0xE5 */ { 1525, 7, 10, 7, 0, -9 },
/* 0xE6 */ { 1534, 10, 7, 10, 0, -6 },
/* 0xE7 */ { 1543, 6, 10, 6, 0, -6 },
/* 0xE8 */ { 1551, 6, 10, 6, 0, -9 },
/* 0xE9 */ { 1559, 6, 10, 6, 0, -9 },
/* 0xEA */ { 1567, 6, 10, 6, 0, -9 },
/* 0xEB */ { 1575, 6, 9, 6, 0, -8 },
/* 0xEC */ { 1582, 2, 10, 3, 0, -9 },
/* 0xED */ { 1585, 2, 10, 3, 1, -9 },
/* 0xEE */ { 1588, 3, 10, 3, 0, -9 },
/* 0xEF */ { 1592, 3, 9, 3, 0, -8 },
/* 0xF0 */ { 1596, 6, 9, 6, 0, -8 },
/* 0xF1 */ { 1603, 5, 10, 6, 1, -9 },
/* 0xF2 */ { 1610, 6, 10, 6, 0, -9 },
/* 0xF3 */ { 1618, 6, 10, 6, 0, -9 },
/* 0xF4 */ { 1626, 6, 10, 6, 0, -9 },
/* 0xF5 */ { 1634, 6, 10, 6, 0, -9 },
/* 0xF6 */ { 1642, 6, 9, 6, 0, -8 },
/* 0xF7 */ { 1649, 5, 5, 7, 1, -5 },
/* 0xF8 */ { 1653, 6, 7, 6, 0, -6 },
/* 0xF9 */ { 1659, 5, 9, 6, 1, -8 },
/* 0xFA */ { 1665, 5, 9, 6, 1, -8 },
/* 0xFB */ { 1671, 5, 10, 6, 1, -9 },
/* 0xFC */ { 1678, 5, 9, 6, 1, -8 },
/* 0xFD */ { 1684, 6, 12, 6, 0, -8 },
/* 0xFE */ { 1693, 5, 11, 7, 1, -8 },
/* 0xFF */ { 1700, 6, 12, 6, 0, -8 },
};
const GFXfont FreeSans6pt_Win1252 PROGMEM = {
(uint8_t*)FreeSans6pt_Win1252Bitmaps,
(GFXglyph*)FreeSans6pt_Win1252Glyphs,
0x01, 0xFF, 10
};
@@ -0,0 +1,527 @@
// trunk-ignore-all(clang-format)
#pragma once
/* PROPERTIES
FONT_NAME FreeSans9pt_Win1250
*/
const uint8_t FreeSans9pt_Win1250Bitmaps[] PROGMEM = {
/* 0x01 */ 0x07, 0x00, 0x0A, 0x00, 0x24, 0x00, 0x48, 0x01, 0x10, 0x04, 0x40, 0x10, 0xFF, 0x20, 0x02, 0x81, 0xFD, 0x00, 0x06, 0x07, 0xF4, 0x08, 0x24, 0x0F, 0x88, 0x11, 0x0F, 0xDC, 0x00,
/* 0x02 */ 0x3F, 0x70, 0x81, 0x11, 0x03, 0xE4, 0x08, 0x28, 0x1F, 0xD0, 0x00, 0x60, 0x7F, 0x20, 0x02, 0x43, 0xFC, 0x44, 0x00, 0x44, 0x00, 0x48, 0x00, 0x90, 0x00, 0xA0, 0x01, 0xC0, 0x00,
/* 0x03 */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x00, 0x28, 0x00, 0x31, 0x8C, 0x63, 0x18, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x06, 0x20, 0x8C, 0x3E, 0x14, 0x00, 0x44, 0x01, 0x06, 0x0C, 0x03, 0xE0,
/* 0x04 */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x00, 0x28, 0x82, 0x30, 0x88, 0x62, 0x08, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x06, 0x3F, 0x8C, 0x3E, 0x14, 0x00, 0x44, 0x01, 0x06, 0x0C, 0x03, 0xE0,
/* 0x05 */ 0x0B, 0x10, 0x14, 0xA8, 0x12, 0x50, 0x29, 0x42, 0x24, 0xA5, 0x32, 0x95, 0x5A, 0x09, 0x48, 0x09, 0x24, 0x01, 0x10, 0x01, 0x48, 0x02, 0xA4, 0x02, 0x42, 0x04, 0x01, 0x98, 0x00, 0x60,
/* 0x06 */ 0x00, 0x80, 0x22, 0x80, 0x65, 0x00, 0xBE, 0xE1, 0x82, 0x4E, 0x03, 0x24, 0x04, 0x28, 0x06, 0x30, 0x12, 0x20, 0x3C, 0xA0, 0xC3, 0xFE, 0x80, 0x4D, 0x00, 0xA6, 0x01, 0x80, 0x00,
/* 0x07 */
/* 0x08 */ 0x00, 0xF8, 0x00, 0x82, 0x00, 0x80, 0x83, 0xE0, 0x41, 0x10, 0x21, 0x04, 0x1B, 0x00, 0x03, 0x00, 0x01, 0x80, 0x00, 0xE0, 0x00, 0x4F, 0xE1, 0xC0, 0x0F, 0x02, 0x00, 0x03, 0x01, 0x00, 0x09, 0x88, 0x0C, 0x0C,
/* 0x09 */ 0x00, 0xF8, 0x00, 0x82, 0x00, 0x80, 0x83, 0xE0, 0x41, 0x10, 0x21, 0x04, 0x1B, 0x00, 0x03, 0x00, 0x01, 0x80, 0x00, 0xE0, 0x00, 0x4F, 0xE1, 0xC0, 0x0F, 0x00,
/* 0x0A */
/* 0x0B */ 0x1C, 0x1C, 0x31, 0xB1, 0x90, 0x50, 0x50, 0x10, 0x18, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x02, 0x80, 0x02, 0x40, 0x01, 0x10, 0x01, 0x04, 0x01, 0x01, 0x01, 0x00, 0x41, 0x00, 0x11, 0x00, 0x07, 0x00, 0x01, 0x00,
/* 0x0C */ 0x06, 0x00, 0x0A, 0x00, 0x12, 0x00, 0x32, 0x01, 0x84, 0x04, 0x10, 0x08, 0x98, 0x1C, 0x18, 0x40, 0x48, 0x82, 0x11, 0xF0, 0x74, 0x02, 0x18, 0x70, 0x2F, 0x9F, 0x80,
/* 0x0D */
/* 0x0E */ 0x01, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x3E, 0x00, 0x82, 0x02, 0x82, 0x06, 0x04, 0x10, 0x04, 0x20, 0x08, 0x40, 0x10, 0xFF, 0x22, 0x00, 0x29, 0xFF, 0x3F, 0x8F, 0xDF, 0x9F, 0x01, 0xC0,
/* 0x0F */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x00, 0x28, 0x82, 0x36, 0x03, 0x60, 0x00, 0xCC, 0x19, 0xA4, 0x4B, 0x00, 0x06, 0x8E, 0x2B, 0x22, 0x66, 0x7C, 0xCC, 0x71, 0x98, 0x03, 0x00,
/* 0x10 */ 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1E, 0x00, 0x54, 0x00, 0xA8, 0x01, 0x50, 0x02, 0xA0, 0x05, 0x20, 0x32, 0x61, 0xC4, 0x74, 0x49, 0x10, 0x6C, 0x00, 0xD8, 0x01, 0x10, 0x00,
/* 0x11 */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x40, 0x29, 0x00, 0x31, 0x84, 0x63, 0x18, 0xC0, 0x00, 0x80, 0x15, 0x03, 0x7E, 0x02, 0xFA, 0x04, 0xE4, 0x18, 0x84, 0x00, 0x06, 0x0C, 0x03, 0xE0,
/* 0x12 */ 0x02, 0x08, 0x01, 0x08, 0x40, 0x10, 0xC0, 0x08, 0xC0, 0x60, 0x80, 0x28, 0x04, 0x12, 0x4C, 0x10, 0x80, 0x08, 0x23, 0x0E, 0x08, 0xC4, 0x82, 0x04, 0x20, 0x83, 0x09, 0x82, 0x47, 0x01, 0x1C, 0x01, 0x30, 0x00, 0xE0, 0x00, 0x00,
/* 0x13 */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x00, 0x28, 0x00, 0x31, 0x08, 0x65, 0x28, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x06, 0x3F, 0x8C, 0x3E, 0x14, 0x00, 0x44, 0x01, 0x06, 0x0C, 0x03, 0xE0,
/* 0x14 */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x22, 0x29, 0x83, 0x30, 0x00, 0x65, 0x14, 0xD3, 0x4D, 0xBA, 0xEB, 0x38, 0xE6, 0x00, 0x0A, 0x00, 0x24, 0x38, 0x44, 0x01, 0x07, 0x1C, 0x01, 0xC0,
/* 0x15 */ 0x07, 0xC0, 0x30, 0x18, 0x80, 0x32, 0x00, 0xF8, 0x01, 0xF1, 0x09, 0xA5, 0x28, 0x40, 0x01, 0x80, 0x03, 0x00, 0x06, 0x3F, 0x8C, 0x3E, 0x14, 0x00, 0x44, 0x01, 0x06, 0x0C, 0x03, 0xE0,
/* 0x16 */ 0x0C, 0x00, 0xC0, 0x1C, 0x03, 0x80, 0xF8, 0xBB, 0x36, 0xC7, 0x99, 0xF3, 0xFE, 0x3F, 0xC3, 0xF0, 0x7E, 0x0E, 0xC1, 0x8E, 0xE0, 0x20,
/* 0x17 */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x00, 0x28, 0x00, 0x10, 0x01, 0x20, 0x1D, 0x44, 0x42, 0x84, 0x85, 0x00, 0x86, 0x00, 0xC4, 0x00, 0x44, 0x7C, 0x44, 0x00, 0x06, 0x0C, 0x03, 0xE0,
/* 0x18 */ 0x01, 0xE0, 0x00, 0x84, 0x00, 0x40, 0x80, 0x20, 0x10, 0x08, 0x24, 0x02, 0x41, 0x00, 0x86, 0x03, 0x12, 0x03, 0xB4, 0x03, 0x52, 0x81, 0x23, 0x80, 0x70, 0xA0, 0x14, 0x28, 0x05, 0x0A, 0x01, 0x42, 0x80, 0x50,
/* 0x19 */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x00, 0x28, 0x00, 0x33, 0x18, 0x60, 0x00, 0xDC, 0xE1, 0xB9, 0xC3, 0x7B, 0xC6, 0x63, 0x0A, 0x00, 0x24, 0xF0, 0x44, 0x01, 0x06, 0x0C, 0x03, 0xE0,
/* 0x1A */ 0xFF, 0xFC, 0x00, 0x63, 0xE3, 0x31, 0x99, 0x04, 0xC8, 0x66, 0x06, 0x30, 0x61, 0x82, 0x0C, 0x10, 0x60, 0x03, 0x04, 0x18, 0x00, 0xFF, 0xFC,
/* 0x1B */ 0x07, 0xF0, 0x06, 0x0C, 0x04, 0x01, 0x04, 0x00, 0x44, 0x22, 0x12, 0x2A, 0x89, 0x00, 0x04, 0x80, 0x02, 0x44, 0x11, 0x01, 0xF0, 0x04, 0x01, 0x0D, 0x01, 0x6A, 0x41, 0x2C, 0x00, 0x05, 0xC0, 0x0E, 0x18, 0x18,
/* 0x1C */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0xC0, 0x2A, 0x00, 0x33, 0x00, 0x66, 0x00, 0xCC, 0x39, 0x80, 0x83, 0x00, 0x06, 0x00, 0x8C, 0x3E, 0x14, 0x00, 0x44, 0x01, 0x06, 0x0C, 0x03, 0xE0,
/* 0x1D */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x70, 0x28, 0x00, 0x31, 0x80, 0x63, 0x18, 0xC0, 0x31, 0x80, 0x03, 0x00, 0x06, 0x60, 0x0D, 0x33, 0x12, 0x10, 0x48, 0x21, 0x23, 0x8C, 0x00,
/* 0x1E */ 0x03, 0x00, 0x07, 0x9E, 0x07, 0x00, 0x86, 0x00, 0x27, 0xC0, 0x0F, 0xC0, 0x07, 0x8C, 0x62, 0x06, 0x31, 0x20, 0x00, 0x90, 0x00, 0x48, 0x00, 0x24, 0x3E, 0x11, 0x00, 0x10, 0x40, 0x10, 0x18, 0x30, 0x03, 0xE0,
/* 0x1F */ 0x18, 0x02, 0x80, 0x4C, 0x16, 0x41, 0x24, 0x3C, 0x88, 0x6E, 0x65, 0xF2, 0x78, 0x46, 0x88, 0xCF, 0x18, 0x02, 0x80, 0x8C, 0x60, 0x70,
/* ' ' 0x20 */
/* '!' 0x21 */ 0xFF, 0xFF, 0xF0, 0xC0,
/* '"' 0x22 */ 0xDE, 0xF7, 0x20,
/* '#' 0x23 */ 0x09, 0x86, 0x41, 0x91, 0xFF, 0x13, 0x04, 0xC3, 0x20, 0xC8, 0xFF, 0x89, 0x82, 0x61, 0x90,
/* '$' 0x24 */ 0x10, 0x1F, 0x14, 0xDA, 0x3D, 0x1E, 0x83, 0x40, 0x78, 0x17, 0x08, 0xF4, 0x7A, 0x35, 0x33, 0xF0, 0x40, 0x20,
/* '%' 0x25 */ 0x38, 0x10, 0xEC, 0x20, 0xC6, 0x20, 0xC6, 0x40, 0xC6, 0x40, 0x6C, 0x80, 0x39, 0x00, 0x01, 0x3C, 0x02, 0x77, 0x02, 0x63, 0x04, 0x63, 0x04, 0x77, 0x08, 0x3C,
/* '&' 0x26 */ 0x0E, 0x0C, 0xC3, 0x30, 0xCC, 0x1E, 0x03, 0x03, 0xC1, 0x9B, 0xC2, 0xF0, 0xEC, 0x19, 0x8F, 0x3C, 0x40,
/* ''' 0x27 */ 0xFE,
/* '(' 0x28 */ 0x13, 0x26, 0x6C, 0xCC, 0xCC, 0xC4, 0x66, 0x23, 0x10,
/* ')' 0x29 */ 0x8C, 0x46, 0x63, 0x33, 0x33, 0x32, 0x66, 0x4C, 0x80,
/* '*' 0x2A */ 0x25, 0x7E, 0xA5, 0x00,
/* '+' 0x2B */ 0x30, 0xC3, 0x3F, 0x30, 0xC3, 0x0C,
/* ',' 0x2C */ 0xD6,
/* '-' 0x2D */ 0xF0,
/* '.' 0x2E */ 0xC0,
/* '/' 0x2F */ 0x08, 0x44, 0x21, 0x10, 0x84, 0x42, 0x11, 0x08, 0x00,
/* '0' 0x30 */ 0x3C, 0x66, 0x42, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x42, 0x66, 0x3C,
/* '1' 0x31 */ 0x11, 0x3F, 0x33, 0x33, 0x33, 0x33, 0x30,
/* '2' 0x32 */ 0x3E, 0x31, 0xB0, 0x78, 0x30, 0x18, 0x1C, 0x1C, 0x1C, 0x18, 0x18, 0x10, 0x08, 0x07, 0xF8,
/* '3' 0x33 */ 0x3C, 0x66, 0xC3, 0xC3, 0x03, 0x06, 0x1C, 0x07, 0x03, 0xC3, 0xC3, 0x66, 0x3C,
/* '4' 0x34 */ 0x0C, 0x18, 0x71, 0x62, 0xC9, 0xA3, 0x46, 0xFE, 0x18, 0x30, 0x60, 0xC0,
/* '5' 0x35 */ 0x7F, 0x20, 0x10, 0x08, 0x08, 0x07, 0xF3, 0x8C, 0x03, 0x01, 0x80, 0xF0, 0x6C, 0x63, 0xE0,
/* '6' 0x36 */ 0x1E, 0x31, 0x98, 0x78, 0x0C, 0x06, 0xF3, 0x8D, 0x83, 0xC1, 0xE0, 0xD0, 0x6C, 0x63, 0xE0,
/* '7' 0x37 */ 0xFF, 0x03, 0x02, 0x06, 0x04, 0x0C, 0x08, 0x18, 0x18, 0x18, 0x10, 0x30, 0x30,
/* '8' 0x38 */ 0x3E, 0x31, 0xB0, 0x78, 0x3C, 0x1B, 0x18, 0xF8, 0xC6, 0xC1, 0xE0, 0xF0, 0x6C, 0x63, 0xE0,
/* '9' 0x39 */ 0x3C, 0x66, 0xC2, 0xC3, 0xC3, 0xC3, 0x67, 0x3B, 0x03, 0x03, 0xC2, 0x66, 0x3C,
/* ':' 0x3A */ 0xC0, 0x00, 0x30,
/* ';' 0x3B */ 0xC0, 0x00, 0x00, 0x64, 0xA0,
/* '<' 0x3C */ 0x00, 0x81, 0xC7, 0x8E, 0x0C, 0x07, 0x80, 0x70, 0x0E, 0x01, 0x80,
/* '=' 0x3D */ 0xFF, 0x80, 0x00, 0x1F, 0xF0,
/* '>' 0x3E */ 0xE0, 0x1C, 0x03, 0x80, 0x30, 0x70, 0xE3, 0x81, 0x00,
/* '?' 0x3F */ 0x3E, 0x31, 0xB0, 0x78, 0x30, 0x18, 0x18, 0x38, 0x18, 0x18, 0x0C, 0x00, 0x00, 0x01, 0x80,
/* '@' 0x40 */ 0x03, 0xF0, 0x06, 0x0E, 0x06, 0x01, 0x86, 0x00, 0x66, 0x1D, 0xBB, 0x31, 0xCF, 0x18, 0xC7, 0x98, 0x63, 0xCC, 0x31, 0xE6, 0x11, 0xB3, 0x99, 0xCC, 0xF7, 0x86, 0x00, 0x01, 0x80, 0x00, 0x70, 0x40, 0x0F, 0xE0,
/* 'A' 0x41 */ 0x06, 0x00, 0xF0, 0x0F, 0x00, 0x90, 0x19, 0x81, 0x98, 0x10, 0x83, 0x0C, 0x3F, 0xC2, 0x04, 0x60, 0x66, 0x06, 0xC0, 0x30,
/* 'B' 0x42 */ 0xFF, 0x18, 0x33, 0x03, 0x60, 0x6C, 0x0D, 0x83, 0x3F, 0xC6, 0x06, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x6F, 0xF8,
/* 'C' 0x43 */ 0x1F, 0x86, 0x19, 0x81, 0xA0, 0x3C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x68, 0x0D, 0x83, 0x18, 0x61, 0xF0,
/* 'D' 0x44 */ 0xFF, 0x18, 0x33, 0x03, 0x60, 0x3C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x03, 0x60, 0xCF, 0xF0,
/* 'E' 0x45 */ 0xFF, 0xE0, 0x30, 0x18, 0x0C, 0x06, 0x03, 0xFD, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0F, 0xF8,
/* 'F' 0x46 */ 0xFF, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xFE, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0,
/* 'G' 0x47 */ 0x0F, 0x83, 0x0E, 0x60, 0x66, 0x03, 0xC0, 0x0C, 0x00, 0xC1, 0xFC, 0x03, 0xC0, 0x36, 0x03, 0x60, 0x73, 0x0F, 0x0F, 0x10,
/* 'H' 0x48 */ 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xFF, 0xFE, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x06,
/* 'I' 0x49 */ 0xFF, 0xFF, 0xFF, 0xC0,
/* 'J' 0x4A */ 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x07, 0x8F, 0x1E, 0x27, 0x80,
/* 'K' 0x4B */ 0xC0, 0xF0, 0x6C, 0x33, 0x18, 0xCC, 0x37, 0x0F, 0xC3, 0x98, 0xC3, 0x30, 0xCC, 0x1B, 0x03, 0xC0, 0xC0,
/* 'L' 0x4C */ 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xFF,
/* 'M' 0x4D */ 0xE0, 0x3F, 0x01, 0xFC, 0x1F, 0xE0, 0xFD, 0x05, 0xEC, 0x6F, 0x63, 0x79, 0x13, 0xCD, 0x9E, 0x6C, 0xF1, 0x47, 0x8E, 0x3C, 0x71, 0x80,
/* 'N' 0x4E */ 0xE0, 0x7C, 0x0F, 0xC1, 0xE8, 0x3D, 0x87, 0x98, 0xF1, 0x1E, 0x33, 0xC3, 0x78, 0x6F, 0x07, 0xE0, 0x7C, 0x0E,
/* 'O' 0x4F */ 0x0F, 0x81, 0x83, 0x18, 0x0C, 0xC0, 0x6C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1B, 0x01, 0x98, 0x0C, 0x60, 0xC0, 0xF8, 0x00,
/* 'P' 0x50 */ 0xFF, 0x30, 0x6C, 0x0F, 0x03, 0xC0, 0xF0, 0x6F, 0xF3, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x00,
/* 'Q' 0x51 */ 0x0F, 0x81, 0x83, 0x18, 0x0C, 0xC0, 0x6C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1B, 0x01, 0x98, 0x6C, 0x60, 0xC0, 0xFB, 0x00, 0x08,
/* 'R' 0x52 */ 0xFF, 0x8C, 0x0E, 0xC0, 0x6C, 0x06, 0xC0, 0x6C, 0x0C, 0xFF, 0x8C, 0x0E, 0xC0, 0x6C, 0x06, 0xC0, 0x6C, 0x06, 0xC0, 0x70,
/* 'S' 0x53 */ 0x3F, 0x18, 0x6C, 0x0F, 0x03, 0xC0, 0x1E, 0x01, 0xF0, 0x0E, 0x00, 0xF0, 0x3C, 0x0D, 0x86, 0x3F, 0x00,
/* 'T' 0x54 */ 0xFF, 0x86, 0x03, 0x01, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x80, 0xC0,
/* 'U' 0x55 */ 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xB0, 0x61, 0xF0,
/* 'V' 0x56 */ 0xC0, 0x6C, 0x0D, 0x81, 0x10, 0x63, 0x0C, 0x61, 0x04, 0x60, 0xCC, 0x19, 0x01, 0x60, 0x3C, 0x07, 0x00, 0x60,
/* 'W' 0x57 */ 0xC1, 0x81, 0x61, 0xC3, 0x61, 0xC3, 0x61, 0x43, 0x62, 0x62, 0x22, 0x66, 0x32, 0x26, 0x36, 0x26, 0x14, 0x34, 0x14, 0x34, 0x1C, 0x1C, 0x18, 0x1C, 0x08, 0x18,
/* 'X' 0x58 */ 0xC0, 0xD8, 0x66, 0x18, 0xCC, 0x1E, 0x07, 0x00, 0xC0, 0x78, 0x32, 0x0C, 0xC6, 0x1B, 0x07, 0xC0, 0xC0,
/* 'Y' 0x59 */ 0xC0, 0x36, 0x06, 0x30, 0xC3, 0x0C, 0x19, 0x81, 0xD8, 0x0F, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00,
/* 'Z' 0x5A */ 0xFF, 0xC0, 0x60, 0x30, 0x0C, 0x06, 0x03, 0x01, 0xC0, 0x60, 0x30, 0x18, 0x06, 0x03, 0x00, 0xFF, 0xC0,
/* '[' 0x5B */ 0xFB, 0x6D, 0xB6, 0xDB, 0x6D, 0xB6, 0xE0,
/* '\' 0x5C */ 0x84, 0x10, 0x84, 0x10, 0x84, 0x10, 0x84, 0x10, 0x80,
/* ']' 0x5D */ 0xED, 0xB6, 0xDB, 0x6D, 0xB6, 0xDB, 0xE0,
/* '^' 0x5E */ 0x30, 0x60, 0xA2, 0x44, 0xD8, 0xA1, 0x80,
/* '_' 0x5F */ 0xFF, 0xC0,
/* '`' 0x60 */ 0xC6, 0x30,
/* 'a' 0x61 */ 0x7E, 0x71, 0xB0, 0xC0, 0x60, 0xF3, 0xDB, 0x0D, 0x86, 0xC7, 0x3D, 0xC0,
/* 'b' 0x62 */ 0xC0, 0x60, 0x30, 0x1B, 0xCE, 0x36, 0x0F, 0x07, 0x83, 0xC1, 0xE0, 0xF0, 0x7C, 0x6D, 0xE0,
/* 'c' 0x63 */ 0x3C, 0x66, 0xC3, 0xC0, 0xC0, 0xC0, 0xC0, 0xC3, 0x66, 0x3C,
/* 'd' 0x64 */ 0x03, 0x03, 0x03, 0x3B, 0x67, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x67, 0x3B,
/* 'e' 0x65 */ 0x3C, 0x66, 0xC3, 0xC3, 0xFF, 0xC0, 0xC0, 0xC3, 0x66, 0x3C,
/* 'f' 0x66 */ 0x36, 0x6F, 0x66, 0x66, 0x66, 0x66, 0x60,
/* 'g' 0x67 */ 0x3B, 0x67, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x67, 0x3B, 0x03, 0x03, 0xC6, 0x7C,
/* 'h' 0x68 */ 0xC0, 0xC0, 0xC0, 0xDE, 0xE3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,
/* 'i' 0x69 */ 0xC3, 0xFF, 0xFF, 0xC0,
/* 'j' 0x6A */ 0x30, 0x03, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xE0,
/* 'k' 0x6B */ 0xC0, 0xC0, 0xC0, 0xC2, 0xC4, 0xCC, 0xD8, 0xF8, 0xEC, 0xC4, 0xC6, 0xC3, 0xC3,
/* 'l' 0x6C */ 0xFF, 0xFF, 0xFF, 0xC0,
/* 'm' 0x6D */ 0xDE, 0xF7, 0x1C, 0xF0, 0xC7, 0x86, 0x3C, 0x31, 0xE1, 0x8F, 0x0C, 0x78, 0x63, 0xC3, 0x1E, 0x18, 0xC0,
/* 'n' 0x6E */ 0xDE, 0xE3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,
/* 'o' 0x6F */ 0x3C, 0x66, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x66, 0x3C,
/* 'p' 0x70 */ 0xDE, 0x71, 0xB0, 0x78, 0x3C, 0x1E, 0x0F, 0x07, 0x83, 0xE3, 0x6F, 0x30, 0x18, 0x0C, 0x00,
/* 'q' 0x71 */ 0x3B, 0x67, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x67, 0x3B, 0x03, 0x03, 0x03,
/* 'r' 0x72 */ 0xDF, 0x31, 0x8C, 0x63, 0x18, 0xC6, 0x00,
/* 's' 0x73 */ 0x3E, 0xE3, 0xC0, 0xC0, 0xE0, 0x3C, 0x07, 0xC3, 0xE3, 0x7E,
/* 't' 0x74 */ 0x66, 0xF6, 0x66, 0x66, 0x66, 0x67,
/* 'u' 0x75 */ 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0x7B,
/* 'v' 0x76 */ 0xC1, 0xA0, 0x98, 0xCC, 0x42, 0x21, 0xB0, 0xD0, 0x28, 0x1C, 0x0C, 0x00,
/* 'w' 0x77 */ 0xC6, 0x1E, 0x38, 0x91, 0xC4, 0xCA, 0x66, 0xD3, 0x16, 0xD0, 0xA6, 0x87, 0x1C, 0x38, 0xC0, 0xC6, 0x00,
/* 'x' 0x78 */ 0x87, 0x89, 0xB1, 0xC3, 0x07, 0x1E, 0x26, 0xC5, 0x0C,
/* 'y' 0x79 */ 0xC1, 0x43, 0x63, 0x62, 0x26, 0x36, 0x34, 0x1C, 0x1C, 0x18, 0x18, 0x18, 0x10, 0x60,
/* 'z' 0x7A */ 0xFE, 0x0C, 0x30, 0xC1, 0x86, 0x18, 0x20, 0xC1, 0xFC,
/* '{' 0x7B */ 0x36, 0x66, 0x66, 0x6E, 0xCE, 0x66, 0x66, 0x66, 0x30,
/* '|' 0x7C */ 0xFF, 0xFF, 0xFF, 0xFF, 0xC0,
/* '}' 0x7D */ 0xC6, 0x66, 0x66, 0x67, 0x37, 0x66, 0x66, 0x66, 0xC0,
/* '~' 0x7E */ 0x61, 0x24, 0x38,
/* 0x7F */ 0xFF, 0xFC, 0x00, 0x63, 0xE3, 0x31, 0x99, 0x04, 0xC8, 0x66, 0x06, 0x30, 0x61, 0x83, 0x0C, 0x18, 0x60, 0x03, 0x06, 0x18, 0x00, 0xFF, 0xFC,
/* 0x80 */ 0x07, 0xC6, 0x13, 0x00, 0xC0, 0x60, 0x3F, 0xE6, 0x03, 0xFC, 0x60, 0x0C, 0x03, 0x00, 0x61, 0x07, 0xC0,
/* 0x81 */
/* 0x82 */ 0xDC,
/* 0x83 */
/* 0x84 */ 0xDA, 0x76,
/* 0x85 */ 0xCC, 0xC0,
/* 0x86 */ 0x18, 0x18, 0x18, 0x18, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
/* 0x87 */ 0x18, 0x18, 0x18, 0x18, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xFF, 0x18, 0x18, 0x18, 0x18,
/* 0x88 */
/* 0x89 */ 0x70, 0x80, 0x22, 0x20, 0x08, 0x90, 0x02, 0x24, 0x00, 0x72, 0x00, 0x00, 0x80, 0x00, 0x40, 0x00, 0x10, 0x00, 0x09, 0xC7, 0x84, 0x8B, 0x31, 0x22, 0x84, 0x88, 0xB3, 0x21, 0xC7, 0x80,
/* 0x8A */ 0x1B, 0x03, 0x83, 0xF1, 0x86, 0xC0, 0xF0, 0x3C, 0x01, 0xE0, 0x1F, 0x00, 0xE0, 0x0F, 0x03, 0xC0, 0xD8, 0x63, 0xF0,
/* 0x8B */ 0x69,
/* 0x8C */ 0x06, 0x03, 0x03, 0xF1, 0x86, 0xC0, 0xF0, 0x3C, 0x01, 0xE0, 0x1F, 0x00, 0xE0, 0x0F, 0x03, 0xC0, 0xD8, 0x63, 0xF0,
/* 0x8D */ 0x33, 0x0F, 0x3F, 0xE1, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x80, 0xC0, 0x60, 0x30,
/* 0x8E */ 0x1B, 0x03, 0x8F, 0xFC, 0x06, 0x03, 0x00, 0xC0, 0x60, 0x30, 0x1C, 0x06, 0x03, 0x01, 0x80, 0x60, 0x30, 0x0F, 0xFC,
/* 0x8F */ 0x0C, 0x06, 0x0F, 0xFC, 0x06, 0x03, 0x00, 0xC0, 0x60, 0x30, 0x1C, 0x06, 0x03, 0x01, 0x80, 0x60, 0x30, 0x0F, 0xFC,
/* 0x90 */
/* 0x91 */ 0x6B,
/* 0x92 */ 0xD6,
/* 0x93 */ 0x4C, 0xA5, 0xB0,
/* 0x94 */ 0xDA, 0x53, 0x20,
/* 0x95 */ 0x6F, 0xFF, 0x60,
/* 0x96 */ 0xFE,
/* 0x97 */ 0xFF, 0xFF,
/* 0x98 */
/* 0x99 */ 0xFC, 0xE1, 0xCC, 0x38, 0x73, 0x0E, 0x1C, 0xC3, 0x8F, 0x30, 0xD2, 0xCC, 0x34, 0xB3, 0x0D, 0x6C, 0xC3, 0x53, 0x30, 0xCC, 0xCC, 0x33, 0x30,
/* 0x9A */ 0x24, 0x3C, 0x18, 0x7E, 0xE3, 0xC0, 0xC0, 0x60, 0x3C, 0x07, 0xC3, 0xE3, 0x7E,
/* 0x9B */ 0x96,
/* 0x9C */ 0x0C, 0x18, 0x10, 0x3E, 0xE3, 0xC0, 0xC0, 0xE0, 0x3C, 0x07, 0xC3, 0xE3, 0x7E,
/* 0x9D */ 0x0D, 0xA7, 0x3C, 0x61, 0x86, 0x18, 0x61, 0x86, 0x18, 0x70,
/* 0x9E */ 0x48, 0xF0, 0xC7, 0xF0, 0x61, 0x86, 0x0C, 0x30, 0xC1, 0x06, 0x0F, 0xE0,
/* 0x9F */ 0x0C, 0x10, 0x47, 0xF0, 0x61, 0x86, 0x0C, 0x30, 0xC1, 0x06, 0x0F, 0xE0,
/* 0xA0 */
/* 0xA1 */ 0x8A, 0x9C,
/* 0xA2 */ 0x85, 0xE0,
/* 0xA3 */ 0x60, 0x30, 0x18, 0x0C, 0x86, 0xC3, 0xC1, 0xC1, 0xC0, 0xE0, 0x30, 0x18, 0x0C, 0x07, 0xF8,
/* 0xA4 */ 0xFF, 0xDF, 0x1E, 0x3E, 0xFF, 0xC0,
/* 0xA5 */ 0x06, 0x00, 0xF0, 0x0F, 0x01, 0x30, 0x13, 0x81, 0x38, 0x21, 0x82, 0x1C, 0x3F, 0xC6, 0x04, 0x60, 0x66, 0x06, 0xC0, 0x30, 0x06, 0x00, 0xC0, 0x0C, 0x00, 0x70,
/* 0xA6 */ 0xFF, 0xFC, 0x0F, 0xFF, 0xC0,
/* 0xA7 */ 0x0C, 0x09, 0x0C, 0xC6, 0x63, 0x81, 0xE3, 0x19, 0x87, 0xE1, 0xB8, 0xC6, 0x41, 0xC0, 0x73, 0x19, 0x8C, 0x66, 0x1E, 0x00,
/* 0xA8 */ 0xCC,
/* 0xA9 */ 0x0F, 0xC0, 0x61, 0x87, 0x03, 0x9B, 0xC6, 0xD9, 0x8F, 0x60, 0x3D, 0x00, 0xF4, 0x03, 0xD8, 0x0D, 0xE6, 0x67, 0xF3, 0x86, 0x18, 0x0F, 0xC0,
/* 0xAA */ 0x3F, 0x18, 0x6C, 0x0F, 0x03, 0xC0, 0x1E, 0x01, 0xF0, 0x0E, 0x00, 0xF0, 0x3C, 0x0D, 0x86, 0x3F, 0x02, 0x00, 0xE0, 0x18, 0x1C, 0x00,
/* 0xAB */ 0x22, 0xCF, 0x26, 0x46, 0x64, 0x40,
/* 0xAC */ 0xFF, 0x80, 0xC0, 0x60, 0x30, 0x18,
/* 0xAD */
/* 0xAE */ 0x0F, 0xC0, 0x61, 0x87, 0x03, 0x9F, 0xE6, 0xD0, 0x8F, 0x42, 0x3D, 0xF0, 0xF4, 0x23, 0xD0, 0x8D, 0xC2, 0x67, 0x0B, 0x86, 0x18, 0x0F, 0xC0,
/* 0xAF */ 0x0C, 0x00, 0x0F, 0xFC, 0x06, 0x03, 0x00, 0xC0, 0x60, 0x30, 0x1C, 0x06, 0x03, 0x01, 0x80, 0x60, 0x30, 0x0F, 0xFC,
/* 0xB0 */ 0x74, 0x63, 0x17, 0x00,
/* 0xB1 */ 0x0C, 0x06, 0x03, 0x07, 0xE0, 0xC0, 0x60, 0x30, 0x18, 0x00, 0x00, 0x3F, 0xE0,
/* 0xB2 */ 0x6C, 0xC7,
/* 0xB3 */ 0x66, 0x66, 0x67, 0x6E, 0x66, 0x66, 0x60,
/* 0xB4 */ 0x36, 0xC0,
/* 0xB5 */ 0xC3, 0x61, 0xB0, 0xD8, 0x6C, 0x36, 0x1B, 0x0D, 0x86, 0xE7, 0x7D, 0xF0, 0x18, 0x0C, 0x00,
/* 0xB6 */ 0x3F, 0x7E, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0x72, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
/* 0xB7 */ 0xE0,
/* 0xB8 */ 0x21, 0xC7, 0xE0,
/* 0xB9 */ 0x7E, 0x38, 0xCC, 0x30, 0x0C, 0x0F, 0x1E, 0xCC, 0x33, 0x0C, 0xC7, 0x1E, 0xE0, 0x10, 0x0C, 0x03, 0x00, 0x70,
/* 0xBA */ 0x3E, 0xE3, 0xC0, 0xC0, 0xE0, 0x3C, 0x07, 0xC3, 0xC3, 0x7E, 0x10, 0x1C, 0x0C, 0x38,
/* 0xBB */ 0x89, 0x98, 0x99, 0x3C, 0xD1, 0x00,
/* 0xBC */ 0xC6, 0xC4, 0xC8, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xFF,
/* 0xBD */ 0x6F, 0x69, 0x00,
/* 0xBE */ 0xDE, 0xB9, 0x8C, 0x63, 0x18, 0xC6, 0x31, 0x8C, 0x00,
/* 0xBF */ 0x30, 0x03, 0xF8, 0x30, 0xC3, 0x06, 0x18, 0x60, 0x83, 0x07, 0xF0,
/* 0xC0 */ 0x06, 0x00, 0xC0, 0xFF, 0x8C, 0x0E, 0xC0, 0x6C, 0x06, 0xC0, 0x6C, 0x0C, 0xFF, 0x8C, 0x0E, 0xC0, 0x6C, 0x06, 0xC0, 0x6C, 0x06, 0xC0, 0x70,
/* 0xC1 */ 0x06, 0x03, 0x00, 0x00, 0x30, 0x1E, 0x07, 0x81, 0x20, 0xCC, 0x33, 0x0F, 0xC6, 0x19, 0x86, 0x40, 0xB0, 0x30,
/* 0xC2 */ 0x0C, 0x04, 0x80, 0x00, 0x30, 0x1E, 0x07, 0x81, 0x20, 0xCC, 0x33, 0x0F, 0xC6, 0x19, 0x86, 0x40, 0xB0, 0x30,
/* 0xC3 */ 0x21, 0x07, 0x80, 0x00, 0x30, 0x1E, 0x07, 0x81, 0x20, 0xCC, 0x33, 0x0F, 0xC6, 0x19, 0x86, 0x40, 0xB0, 0x30,
/* 0xC4 */ 0x33, 0x00, 0x00, 0xC0, 0x78, 0x1E, 0x04, 0x83, 0x30, 0xCC, 0x33, 0x1F, 0xE6, 0x19, 0x02, 0xC0, 0xF0, 0x30,
/* 0xC5 */ 0x30, 0x60, 0x00, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xFF,
/* 0xC6 */ 0x06, 0x01, 0x80, 0x00, 0x0F, 0xC3, 0x0C, 0xC0, 0xD0, 0x1E, 0x00, 0xC0, 0x18, 0x03, 0x01, 0xA0, 0x36, 0x0C, 0x61, 0x87, 0xC0,
/* 0xC7 */ 0x1F, 0x06, 0x19, 0x83, 0xA0, 0x3C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x68, 0x0D, 0x83, 0x18, 0xE1, 0xF0, 0x08, 0x01, 0xC0, 0x18, 0x0E, 0x00,
/* 0xC8 */ 0x19, 0x81, 0xE0, 0x00, 0x0F, 0xC3, 0x0C, 0xC0, 0xF0, 0x1E, 0x00, 0xC0, 0x18, 0x03, 0x01, 0xA0, 0x36, 0x0C, 0x61, 0x87, 0xC0,
/* 0xC9 */ 0x0C, 0x0C, 0x00, 0x1F, 0xFC, 0x06, 0x03, 0x01, 0x80, 0xFF, 0x60, 0x30, 0x18, 0x0C, 0x07, 0xFC,
/* 0xCA */ 0xFF, 0xD8, 0x03, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x3F, 0xF6, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x60, 0x0F, 0xFC, 0x01, 0x80, 0x60, 0x0C, 0x00, 0xE0,
/* 0xCB */ 0x33, 0x00, 0x3F, 0xF8, 0x0C, 0x06, 0x03, 0x01, 0xFE, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x07, 0xFC,
/* 0xCC */ 0x33, 0x0F, 0x00, 0x1F, 0xFC, 0x06, 0x03, 0x01, 0x80, 0xFF, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0xFE,
/* 0xCD */ 0x78, 0x36, 0xDB, 0x6D, 0xB6, 0xC0,
/* 0xCE */ 0x76, 0xC0, 0x63, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x18,
/* 0xCF */ 0x66, 0x0F, 0x00, 0x03, 0xF8, 0xC3, 0x30, 0x6C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC1, 0xB0, 0xEF, 0xE0,
/* 0xD0 */ 0x7F, 0x0C, 0x31, 0x83, 0x30, 0x36, 0x06, 0xC0, 0xFE, 0x1B, 0x03, 0x60, 0x6C, 0x0D, 0x83, 0x30, 0xE7, 0xF0,
/* 0xD1 */ 0x03, 0x01, 0x83, 0x81, 0xF0, 0x3F, 0x07, 0xA0, 0xF6, 0x1E, 0x63, 0xC4, 0x78, 0xCF, 0x0D, 0xE1, 0xBC, 0x1F, 0x81, 0xC0,
/* 0xD2 */ 0x19, 0x81, 0xE3, 0x81, 0xF0, 0x3F, 0x07, 0xA0, 0xF6, 0x1E, 0x63, 0xC4, 0x78, 0xCF, 0x0D, 0xE1, 0xBC, 0x1F, 0x81, 0xC0,
/* 0xD3 */ 0x03, 0x00, 0x60, 0x00, 0x00, 0xF0, 0x39, 0xC6, 0x06, 0x60, 0x6C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x36, 0x06, 0x60, 0x63, 0x9C, 0x0F, 0x00,
/* 0xD4 */ 0x0F, 0x01, 0x98, 0x00, 0x00, 0xF0, 0x39, 0xC6, 0x06, 0x60, 0x6C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x36, 0x06, 0x60, 0x63, 0x9C, 0x0F, 0x00,
/* 0xD5 */ 0x0D, 0x81, 0xB0, 0x00, 0x00, 0xF0, 0x39, 0xC6, 0x06, 0x60, 0x6C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x36, 0x06, 0x60, 0x63, 0x9C, 0x0F, 0x00,
/* 0xD6 */ 0x19, 0x81, 0x98, 0x00, 0x00, 0xF0, 0x39, 0xC6, 0x06, 0x60, 0x6C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x36, 0x06, 0x60, 0x63, 0x9C, 0x0F, 0x00,
/* 0xD7 */ 0x83, 0x89, 0xA1, 0x83, 0x89, 0xA1, 0x80,
/* 0xD8 */ 0x33, 0x01, 0xE0, 0xFF, 0x8C, 0x0E, 0xC0, 0x6C, 0x06, 0xC0, 0x6C, 0x0C, 0xFF, 0x8C, 0x0E, 0xC0, 0x6C, 0x06, 0xC0, 0x6C, 0x06, 0xC0, 0x70,
/* 0xD9 */ 0x04, 0x01, 0x43, 0x11, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x36, 0x0C, 0x3E, 0x00,
/* 0xDA */ 0x06, 0x01, 0x83, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x36, 0x0C, 0x3E, 0x00,
/* 0xDB */ 0x0D, 0x83, 0x63, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x36, 0x0C, 0x3E, 0x00,
/* 0xDC */ 0x1B, 0x00, 0x03, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x36, 0x0C, 0x3E, 0x00,
/* 0xDD */ 0x03, 0x0C, 0x63, 0x60, 0x63, 0x0C, 0x30, 0xC1, 0x98, 0x1D, 0x80, 0xF0, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60,
/* 0xDE */ 0xFF, 0x86, 0x03, 0x01, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x80, 0xC0, 0x40, 0x3C, 0x06, 0x1E, 0x00,
/* 0xDF */ 0x3C, 0x33, 0x30, 0xD8, 0x6C, 0x36, 0x33, 0x39, 0x86, 0xC1, 0xE0, 0xF0, 0x78, 0x6D, 0xE0,
/* 0xE0 */ 0x19, 0x89, 0xBE, 0x63, 0x18, 0xC6, 0x31, 0x8C, 0x00,
/* 0xE1 */ 0x0C, 0x04, 0x04, 0x0F, 0xCE, 0x36, 0x18, 0x0C, 0x1E, 0x7B, 0x61, 0xB0, 0xD8, 0xE7, 0xB8,
/* 0xE2 */ 0x10, 0x14, 0x1B, 0x0F, 0xCE, 0x36, 0x18, 0x0C, 0x1E, 0x7B, 0x61, 0xB0, 0xD8, 0xE7, 0xB8,
/* 0xE3 */ 0x66, 0x1E, 0x00, 0x0F, 0xCE, 0x36, 0x18, 0x0C, 0x1E, 0x7B, 0x61, 0xB0, 0xD8, 0xE7, 0xB8,
/* 0xE4 */ 0x66, 0x00, 0x1F, 0x9C, 0x6C, 0x30, 0x18, 0x3C, 0xF6, 0xC3, 0x61, 0xB1, 0xCF, 0x70,
/* 0xE5 */ 0x78, 0x36, 0xDB, 0x6D, 0xB6, 0xD8,
/* 0xE6 */ 0x0C, 0x08, 0x10, 0x3C, 0x66, 0xC3, 0xC0, 0xC0, 0xC0, 0xC0, 0xC3, 0x66, 0x3C,
/* 0xE7 */ 0x3C, 0x66, 0xC3, 0xC0, 0xC0, 0xC0, 0xC0, 0xC3, 0x66, 0x3C, 0x10, 0x1C, 0x0C, 0x38,
/* 0xE8 */ 0x44, 0x28, 0x38, 0x3C, 0x66, 0xC3, 0xC0, 0xC0, 0xC0, 0xC0, 0xC3, 0x66, 0x3C,
/* 0xE9 */ 0x0C, 0x08, 0x18, 0x3C, 0x66, 0xC3, 0xC3, 0xFF, 0xC0, 0xC0, 0xC3, 0x66, 0x3C,
/* 0xEA */ 0x3C, 0x62, 0xC3, 0xC3, 0xFF, 0xC0, 0xC0, 0xC3, 0x66, 0x3E, 0x04, 0x0C, 0x0C, 0x06,
/* 0xEB */ 0x66, 0x00, 0x3C, 0x66, 0xC3, 0xC3, 0xFF, 0xC0, 0xC0, 0xC3, 0x66, 0x3C,
/* 0xEC */ 0x64, 0x2C, 0x18, 0x3C, 0x66, 0xC3, 0xC3, 0xFF, 0xC0, 0xC0, 0xC3, 0x66, 0x3C,
/* 0xED */ 0x7A, 0x6D, 0xB6, 0xDB, 0x6C,
/* 0xEE */ 0x69, 0x06, 0x66, 0x66, 0x66, 0x66, 0x60,
/* 0xEF */ 0x03, 0x30, 0x32, 0x03, 0x43, 0xB0, 0x67, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x06, 0x70, 0x3B, 0x00,
/* 0xF0 */ 0x03, 0x07, 0xC0, 0xC7, 0x66, 0x76, 0x1B, 0x0D, 0x86, 0xC3, 0x61, 0xB0, 0xCC, 0xE3, 0xB0,
/* 0xF1 */ 0x0C, 0x18, 0x00, 0xDE, 0xE3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,
/* 0xF2 */ 0x66, 0x3C, 0x00, 0xDE, 0xE3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,
/* 0xF3 */ 0x0C, 0x18, 0x00, 0x3C, 0x66, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x66, 0x3C,
/* 0xF4 */ 0x18, 0x24, 0x00, 0x3C, 0x66, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x66, 0x3C,
/* 0xF5 */ 0x36, 0x6C, 0x00, 0x3C, 0x66, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x66, 0x3C,
/* 0xF6 */ 0x66, 0x00, 0x3C, 0x66, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x66, 0x3C,
/* 0xF7 */ 0x18, 0x00, 0x00, 0x1F, 0xF0, 0x00, 0x00, 0x00, 0x30,
/* 0xF8 */ 0xDB, 0x81, 0xBE, 0x63, 0x18, 0xC6, 0x31, 0x8C, 0x00,
/* 0xF9 */ 0x10, 0x28, 0x10, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0x7B,
/* 0xFA */ 0x06, 0x0C, 0x18, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0x7B,
/* 0xFB */ 0x36, 0x6C, 0x00, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0x7B,
/* 0xFC */ 0x66, 0x00, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0x7B,
/* 0xFD */ 0x06, 0x04, 0x08, 0xC1, 0x43, 0x63, 0x62, 0x26, 0x36, 0x34, 0x1C, 0x1C, 0x18, 0x18, 0x18, 0x10, 0x60,
/* 0xFE */ 0x63, 0x3C, 0xC6, 0x31, 0x8C, 0x63, 0x18, 0xE2, 0x1C, 0x6F,
/* 0xFF */ 0xC0,
};
const GFXglyph FreeSans9pt_Win1250Glyphs[] PROGMEM = {
/* 0x01 */ { 0, 15, 15, 17, 1, -13 },
/* 0x02 */ { 29, 15, 15, 17, 1, -13 },
/* 0x03 */ { 58, 15, 16, 17, 1, -14 },
/* 0x04 */ { 88, 15, 16, 17, 1, -14 },
/* 0x05 */ { 118, 16, 15, 18, 1, -13 },
/* 0x06 */ { 148, 15, 15, 17, 1, -13 },
/* 0x07 */ { 177, 0, 0, 8, 0, 0 },
/* 0x08 */ { 177, 17, 16, 19, 1, -14 },
/* 0x09 */ { 211, 17, 12, 19, 1, -12 },
/* 0x0A */ { 237, 0, 0, 8, 0, 0 },
/* 0x0B */ { 237, 17, 16, 19, 1, -14 },
/* 0x0C */ { 271, 15, 14, 17, 1, -12 },
/* 0x0D */ { 298, 0, 0, 8, 0, 0 },
/* 0x0E */ { 298, 15, 16, 17, 1, -14 },
/* 0x0F */ { 328, 15, 15, 17, 1, -13 },
/* 0x10 */ { 357, 15, 15, 17, 1, -13 },
/* 0x11 */ { 386, 15, 16, 17, 1, -14 },
/* 0x12 */ { 416, 17, 17, 19, 1, -15 },
/* 0x13 */ { 453, 15, 16, 17, 1, -14 },
/* 0x14 */ { 483, 15, 16, 17, 1, -14 },
/* 0x15 */ { 513, 15, 16, 17, 1, -14 },
/* 0x16 */ { 543, 11, 16, 13, 1, -14 },
/* 0x17 */ { 565, 15, 16, 17, 1, -14 },
/* 0x18 */ { 595, 18, 15, 20, 1, -13 },
/* 0x19 */ { 629, 15, 16, 17, 1, -14 },
/* 0x1A */ { 659, 13, 14, 15, 1, -12 },
/* 0x1B */ { 682, 17, 16, 19, 1, -14 },
/* 0x1C */ { 716, 15, 16, 17, 1, -14 },
/* 0x1D */ { 746, 15, 15, 17, 1, -13 },
/* 0x1E */ { 775, 17, 16, 19, 1, -14 },
/* 0x1F */ { 809, 11, 16, 13, 1, -14 },
/* ' ' 0x20 */ { 831, 0, 0, 5, 0, 0 },
/* '!' 0x21 */ { 831, 2, 13, 6, 2, -12 },
/* '"' 0x22 */ { 835, 5, 4, 6, 1, -12 },
/* '#' 0x23 */ { 838, 10, 12, 10, 0, -11 },
/* '$' 0x24 */ { 853, 9, 16, 10, 1, -13 },
/* '%' 0x25 */ { 871, 16, 13, 16, 1, -12 },
/* '&' 0x26 */ { 897, 10, 13, 12, 1, -12 },
/* ''' 0x27 */ { 914, 2, 4, 4, 1, -12 },
/* '(' 0x28 */ { 915, 4, 17, 6, 1, -12 },
/* ')' 0x29 */ { 924, 4, 17, 6, 1, -12 },
/* '*' 0x2A */ { 933, 5, 5, 7, 1, -12 },
/* '+' 0x2B */ { 937, 6, 8, 11, 3, -7 },
/* ',' 0x2C */ { 943, 2, 4, 5, 2, 0 },
/* '-' 0x2D */ { 944, 4, 1, 6, 1, -4 },
/* '.' 0x2E */ { 945, 2, 1, 5, 1, 0 },
/* '/' 0x2F */ { 946, 5, 13, 5, 0, -12 },
/* '0' 0x30 */ { 955, 8, 13, 10, 1, -12 },
/* '1' 0x31 */ { 968, 4, 13, 10, 3, -12 },
/* '2' 0x32 */ { 975, 9, 13, 10, 1, -12 },
/* '3' 0x33 */ { 990, 8, 13, 10, 1, -12 },
/* '4' 0x34 */ { 1003, 7, 13, 10, 2, -12 },
/* '5' 0x35 */ { 1015, 9, 13, 10, 1, -12 },
/* '6' 0x36 */ { 1030, 9, 13, 10, 1, -12 },
/* '7' 0x37 */ { 1045, 8, 13, 10, 0, -12 },
/* '8' 0x38 */ { 1058, 9, 13, 10, 1, -12 },
/* '9' 0x39 */ { 1073, 8, 13, 10, 1, -12 },
/* ':' 0x3A */ { 1086, 2, 10, 5, 1, -9 },
/* ';' 0x3B */ { 1089, 3, 12, 5, 1, -8 },
/* '<' 0x3C */ { 1094, 9, 9, 11, 1, -8 },
/* '=' 0x3D */ { 1105, 9, 4, 11, 1, -5 },
/* '>' 0x3E */ { 1110, 9, 8, 11, 1, -7 },
/* '?' 0x3F */ { 1119, 9, 13, 10, 1, -12 },
/* '@' 0x40 */ { 1134, 17, 16, 18, 1, -12 },
/* 'A' 0x41 */ { 1168, 12, 13, 12, 0, -12 },
/* 'B' 0x42 */ { 1188, 11, 13, 12, 1, -12 },
/* 'C' 0x43 */ { 1206, 11, 13, 13, 1, -12 },
/* 'D' 0x44 */ { 1224, 11, 13, 13, 1, -12 },
/* 'E' 0x45 */ { 1242, 9, 13, 11, 1, -12 },
/* 'F' 0x46 */ { 1257, 8, 13, 11, 1, -12 },
/* 'G' 0x47 */ { 1270, 12, 13, 14, 1, -12 },
/* 'H' 0x48 */ { 1290, 11, 13, 13, 1, -12 },
/* 'I' 0x49 */ { 1308, 2, 13, 5, 2, -12 },
/* 'J' 0x4A */ { 1312, 7, 13, 10, 1, -12 },
/* 'K' 0x4B */ { 1324, 10, 13, 12, 1, -12 },
/* 'L' 0x4C */ { 1341, 8, 13, 10, 1, -12 },
/* 'M' 0x4D */ { 1354, 13, 13, 15, 1, -12 },
/* 'N' 0x4E */ { 1376, 11, 13, 13, 1, -12 },
/* 'O' 0x4F */ { 1394, 13, 13, 14, 1, -12 },
/* 'P' 0x50 */ { 1416, 10, 13, 12, 1, -12 },
/* 'Q' 0x51 */ { 1433, 13, 14, 14, 1, -12 },
/* 'R' 0x52 */ { 1456, 12, 13, 13, 1, -12 },
/* 'S' 0x53 */ { 1476, 10, 13, 12, 1, -12 },
/* 'T' 0x54 */ { 1493, 9, 13, 11, 1, -12 },
/* 'U' 0x55 */ { 1508, 11, 13, 13, 1, -12 },
/* 'V' 0x56 */ { 1526, 11, 13, 11, 0, -12 },
/* 'W' 0x57 */ { 1544, 16, 13, 17, 0, -12 },
/* 'X' 0x58 */ { 1570, 10, 13, 12, 1, -12 },
/* 'Y' 0x59 */ { 1587, 12, 13, 12, 0, -12 },
/* 'Z' 0x5A */ { 1607, 10, 13, 11, 1, -12 },
/* '[' 0x5B */ { 1624, 3, 17, 5, 1, -12 },
/* '\' 0x5C */ { 1631, 5, 13, 5, 0, -12 },
/* ']' 0x5D */ { 1640, 3, 17, 5, 0, -12 },
/* '^' 0x5E */ { 1647, 7, 7, 8, 1, -12 },
/* '_' 0x5F */ { 1654, 10, 1, 10, 0, 3 },
/* '`' 0x60 */ { 1656, 4, 3, 5, 0, -12 },
/* 'a' 0x61 */ { 1658, 9, 10, 10, 1, -9 },
/* 'b' 0x62 */ { 1670, 9, 13, 10, 1, -12 },
/* 'c' 0x63 */ { 1685, 8, 10, 9, 1, -9 },
/* 'd' 0x64 */ { 1695, 8, 13, 10, 1, -12 },
/* 'e' 0x65 */ { 1708, 8, 10, 10, 1, -9 },
/* 'f' 0x66 */ { 1718, 4, 13, 5, 1, -12 },
/* 'g' 0x67 */ { 1725, 8, 14, 10, 1, -9 },
/* 'h' 0x68 */ { 1739, 8, 13, 10, 1, -12 },
/* 'i' 0x69 */ { 1752, 2, 13, 4, 1, -12 },
/* 'j' 0x6A */ { 1756, 4, 17, 4, 0, -12 },
/* 'k' 0x6B */ { 1765, 8, 13, 9, 1, -12 },
/* 'l' 0x6C */ { 1778, 2, 13, 4, 1, -12 },
/* 'm' 0x6D */ { 1782, 13, 10, 15, 1, -9 },
/* 'n' 0x6E */ { 1799, 8, 10, 10, 1, -9 },
/* 'o' 0x6F */ { 1809, 8, 10, 10, 1, -9 },
/* 'p' 0x70 */ { 1819, 9, 13, 10, 1, -9 },
/* 'q' 0x71 */ { 1834, 8, 13, 10, 1, -9 },
/* 'r' 0x72 */ { 1847, 5, 10, 6, 1, -9 },
/* 's' 0x73 */ { 1854, 8, 10, 9, 1, -9 },
/* 't' 0x74 */ { 1864, 4, 12, 5, 1, -11 },
/* 'u' 0x75 */ { 1870, 8, 10, 10, 1, -9 },
/* 'v' 0x76 */ { 1880, 9, 10, 9, 0, -9 },
/* 'w' 0x77 */ { 1892, 13, 10, 13, 0, -9 },
/* 'x' 0x78 */ { 1909, 7, 10, 9, 1, -9 },
/* 'y' 0x79 */ { 1918, 8, 14, 9, 0, -9 },
/* 'z' 0x7A */ { 1932, 7, 10, 9, 1, -9 },
/* '{' 0x7B */ { 1941, 4, 17, 6, 1, -12 },
/* '|' 0x7C */ { 1950, 2, 17, 4, 2, -12 },
/* '}' 0x7D */ { 1955, 4, 17, 6, 1, -12 },
/* '~' 0x7E */ { 1964, 7, 3, 9, 1, -7 },
/* 0x7F */ { 1967, 13, 14, 15, 1, -12 },
/* 0x80 */ { 1990, 10, 13, 12, 1, -12 },
/* 0x81 */ { 2007, 0, 0, 0, 0, 0 },
/* 0x82 */ { 2007, 2, 3, 5, 1, 0 },
/* 0x83 */ { 2008, 0, 0, 0, 0, 0 },
/* 0x84 */ { 2008, 5, 3, 7, 1, 0 },
/* 0x85 */ { 2010, 10, 1, 12, 1, 0 },
/* 0x86 */ { 2012, 8, 16, 10, 1, -12 },
/* 0x87 */ { 2028, 8, 16, 10, 1, -12 },
/* 0x88 */ { 2044, 0, 0, 0, 0, 0 },
/* 0x89 */ { 2044, 18, 13, 18, 0, -12 },
/* 0x8A */ { 2074, 10, 15, 12, 1, -14 },
/* 0x8B */ { 2093, 2, 4, 4, 1, -6 },
/* 0x8C */ { 2094, 10, 15, 12, 1, -14 },
/* 0x8D */ { 2113, 9, 15, 11, 1, -14 },
/* 0x8E */ { 2130, 10, 15, 11, 1, -14 },
/* 0x8F */ { 2149, 10, 15, 11, 1, -14 },
/* 0x90 */ { 2168, 0, 0, 0, 0, 0 },
/* 0x91 */ { 2168, 2, 4, 4, 2, -12 },
/* 0x92 */ { 2169, 2, 4, 4, 1, -12 },
/* 0x93 */ { 2170, 5, 4, 7, 2, -12 },
/* 0x94 */ { 2173, 5, 4, 7, 1, -12 },
/* 0x95 */ { 2176, 4, 5, 7, 1, -8 },
/* 0x96 */ { 2179, 7, 1, 9, 1, -4 },
/* 0x97 */ { 2180, 16, 1, 18, 1, -4 },
/* 0x98 */ { 2182, 0, 0, 0, 0, 0 },
/* 0x99 */ { 2182, 18, 10, 18, 1, -13 },
/* 0x9A */ { 2205, 8, 13, 9, 1, -12 },
/* 0x9B */ { 2218, 2, 4, 5, 2, -6 },
/* 0x9C */ { 2219, 8, 13, 9, 1, -12 },
/* 0x9D */ { 2232, 6, 13, 8, 1, -12 },
/* 0x9E */ { 2242, 7, 13, 9, 1, -12 },
/* 0x9F */ { 2254, 7, 13, 9, 1, -12 },
/* 0xA0 */ { 2266, 0, 0, 5, 0, 0 },
/* 0xA1 */ { 2266, 5, 3, 6, 0, -12 },
/* 0xA2 */ { 2268, 6, 2, 6, 0, -12 },
/* 0xA3 */ { 2270, 9, 13, 11, 1, -12 },
/* 0xA4 */ { 2285, 7, 6, 10, 2, -8 },
/* 0xA5 */ { 2291, 12, 17, 12, 1, -12 },
/* 0xA6 */ { 2317, 2, 17, 5, 2, -12 },
/* 0xA7 */ { 2322, 9, 17, 10, 1, -12 },
/* 0xA8 */ { 2342, 6, 1, 6, 0, -11 },
/* 0xA9 */ { 2343, 14, 13, 14, 1, -12 },
/* 0xAA */ { 2366, 10, 17, 12, 1, -12 },
/* 0xAB */ { 2388, 7, 6, 9, 1, -7 },
/* 0xAC */ { 2394, 9, 5, 11, 2, -5 },
/* 0xAD */ { 2400, 0, 0, 0, 0, 0 },
/* 0xAE */ { 2400, 14, 13, 14, 1, -12 },
/* 0xAF */ { 2423, 10, 15, 11, 1, -14 },
/* 0xB0 */ { 2442, 5, 5, 11, 3, -11 },
/* 0xB1 */ { 2446, 9, 11, 11, 1, -10 },
/* 0xB2 */ { 2459, 4, 4, 6, 1, 1 },
/* 0xB3 */ { 2461, 4, 13, 5, 1, -12 },
/* 0xB4 */ { 2468, 4, 3, 6, 2, -12 },
/* 0xB5 */ { 2470, 9, 13, 10, 1, -9 },
/* 0xB6 */ { 2485, 8, 16, 10, 2, -12 },
/* 0xB7 */ { 2501, 3, 1, 5, 1, -4 },
/* 0xB8 */ { 2502, 5, 4, 6, 1, 1 },
/* 0xB9 */ { 2505, 10, 14, 10, 1, -9 },
/* 0xBA */ { 2523, 8, 14, 9, 1, -9 },
/* 0xBB */ { 2537, 7, 6, 9, 1, -7 },
/* 0xBC */ { 2543, 8, 13, 10, 1, -12 },
/* 0xBD */ { 2556, 6, 3, 6, 0, -12 },
/* 0xBE */ { 2559, 5, 13, 7, 1, -12 },
/* 0xBF */ { 2568, 7, 12, 9, 1, -11 },
/* 0xC0 */ { 2579, 12, 15, 13, 1, -14 },
/* 0xC1 */ { 2602, 10, 14, 12, 1, -13 },
/* 0xC2 */ { 2620, 10, 14, 12, 1, -13 },
/* 0xC3 */ { 2638, 10, 14, 12, 1, -13 },
/* 0xC4 */ { 2656, 10, 14, 12, 1, -13 },
/* 0xC5 */ { 2674, 8, 14, 10, 1, -13 },
/* 0xC6 */ { 2688, 11, 15, 13, 1, -14 },
/* 0xC7 */ { 2709, 11, 17, 13, 1, -12 },
/* 0xC8 */ { 2733, 11, 15, 13, 1, -14 },
/* 0xC9 */ { 2754, 9, 14, 11, 1, -13 },
/* 0xCA */ { 2770, 11, 17, 12, 1, -12 },
/* 0xCB */ { 2794, 9, 14, 11, 1, -13 },
/* 0xCC */ { 2810, 9, 15, 11, 1, -14 },
/* 0xCD */ { 2827, 3, 14, 5, 1, -13 },
/* 0xCE */ { 2833, 5, 14, 5, 0, -13 },
/* 0xCF */ { 2842, 10, 15, 13, 2, -14 },
/* 0xD0 */ { 2861, 11, 13, 13, 1, -12 },
/* 0xD1 */ { 2879, 11, 14, 13, 1, -13 },
/* 0xD2 */ { 2899, 11, 14, 13, 1, -13 },
/* 0xD3 */ { 2919, 12, 15, 13, 1, -14 },
/* 0xD4 */ { 2942, 12, 15, 13, 1, -14 },
/* 0xD5 */ { 2965, 12, 15, 13, 1, -14 },
/* 0xD6 */ { 2988, 12, 15, 13, 1, -14 },
/* 0xD7 */ { 3011, 7, 7, 11, 2, -7 },
/* 0xD8 */ { 3018, 12, 15, 13, 1, -14 },
/* 0xD9 */ { 3041, 11, 14, 13, 1, -13 },
/* 0xDA */ { 3061, 11, 14, 13, 1, -13 },
/* 0xDB */ { 3081, 11, 14, 13, 1, -13 },
/* 0xDC */ { 3101, 11, 14, 13, 1, -13 },
/* 0xDD */ { 3121, 12, 14, 12, 0, -13 },
/* 0xDE */ { 3142, 9, 17, 11, 1, -12 },
/* 0xDF */ { 3162, 9, 13, 11, 1, -12 },
/* 0xE0 */ { 3177, 5, 13, 6, 1, -12 },
/* 0xE1 */ { 3186, 9, 13, 10, 1, -12 },
/* 0xE2 */ { 3201, 9, 13, 10, 1, -12 },
/* 0xE3 */ { 3216, 9, 13, 10, 1, -12 },
/* 0xE4 */ { 3231, 9, 12, 10, 1, -11 },
/* 0xE5 */ { 3245, 3, 15, 4, 0, -14 },
/* 0xE6 */ { 3251, 8, 13, 9, 1, -12 },
/* 0xE7 */ { 3264, 8, 14, 9, 1, -9 },
/* 0xE8 */ { 3278, 8, 13, 9, 1, -12 },
/* 0xE9 */ { 3291, 8, 13, 10, 1, -12 },
/* 0xEA */ { 3304, 8, 14, 10, 1, -9 },
/* 0xEB */ { 3318, 8, 12, 10, 1, -11 },
/* 0xEC */ { 3330, 8, 13, 10, 1, -12 },
/* 0xED */ { 3343, 3, 13, 4, 1, -12 },
/* 0xEE */ { 3348, 4, 13, 5, 0, -12 },
/* 0xEF */ { 3355, 12, 13, 12, 1, -12 },
/* 0xF0 */ { 3375, 9, 13, 10, 1, -12 },
/* 0xF1 */ { 3390, 8, 13, 10, 1, -12 },
/* 0xF2 */ { 3403, 8, 13, 10, 1, -12 },
/* 0xF3 */ { 3416, 8, 13, 10, 1, -12 },
/* 0xF4 */ { 3429, 8, 13, 10, 1, -12 },
/* 0xF5 */ { 3442, 8, 13, 10, 1, -12 },
/* 0xF6 */ { 3455, 8, 12, 10, 1, -11 },
/* 0xF7 */ { 3467, 9, 8, 11, 1, -7 },
/* 0xF8 */ { 3476, 5, 13, 6, 1, -12 },
/* 0xF9 */ { 3485, 8, 13, 10, 1, -12 },
/* 0xFA */ { 3498, 8, 13, 10, 1, -12 },
/* 0xFB */ { 3511, 8, 13, 10, 1, -12 },
/* 0xFC */ { 3524, 8, 12, 10, 1, -11 },
/* 0xFD */ { 3536, 8, 17, 9, 0, -12 },
/* 0xFE */ { 3553, 5, 16, 5, 1, -11 },
/* 0xFF */ { 3563, 2, 1, 6, 2, -11 },
};
const GFXfont FreeSans9pt_Win1250 PROGMEM = {
(uint8_t*)FreeSans9pt_Win1250Bitmaps,
(GFXglyph*)FreeSans9pt_Win1250Glyphs,
0x01, 0xFF, 21
};
@@ -0,0 +1,527 @@
// trunk-ignore-all(clang-format)
#pragma once
/* PROPERTIES
FONT_NAME FreeSans9pt_Win1251
*/
const uint8_t FreeSans9pt_Win1251Bitmaps[] PROGMEM = {
/* 0x01 */ 0x07, 0x00, 0x0A, 0x00, 0x24, 0x00, 0x48, 0x01, 0x10, 0x04, 0x40, 0x10, 0xFF, 0x20, 0x02, 0x81, 0xFD, 0x00, 0x06, 0x07, 0xF4, 0x08, 0x24, 0x0F, 0x88, 0x11, 0x0F, 0xDC, 0x00,
/* 0x02 */ 0x3F, 0x70, 0x81, 0x11, 0x03, 0xE4, 0x08, 0x28, 0x1F, 0xD0, 0x00, 0x60, 0x7F, 0x20, 0x02, 0x43, 0xFC, 0x44, 0x00, 0x44, 0x00, 0x48, 0x00, 0x90, 0x00, 0xA0, 0x01, 0xC0, 0x00,
/* 0x03 */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x00, 0x28, 0x00, 0x31, 0x8C, 0x63, 0x18, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x06, 0x20, 0x8C, 0x3E, 0x14, 0x00, 0x44, 0x01, 0x06, 0x0C, 0x03, 0xE0,
/* 0x04 */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x00, 0x28, 0x82, 0x30, 0x88, 0x62, 0x08, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x06, 0x3F, 0x8C, 0x3E, 0x14, 0x00, 0x44, 0x01, 0x06, 0x0C, 0x03, 0xE0,
/* 0x05 */ 0x0B, 0x10, 0x14, 0xA8, 0x12, 0x50, 0x29, 0x42, 0x24, 0xA5, 0x32, 0x95, 0x5A, 0x09, 0x48, 0x09, 0x24, 0x01, 0x10, 0x01, 0x48, 0x02, 0xA4, 0x02, 0x42, 0x04, 0x01, 0x98, 0x00, 0x60,
/* 0x06 */ 0x00, 0x80, 0x22, 0x80, 0x65, 0x00, 0xBE, 0xE1, 0x82, 0x4E, 0x03, 0x24, 0x04, 0x28, 0x06, 0x30, 0x12, 0x20, 0x3C, 0xA0, 0xC3, 0xFE, 0x80, 0x4D, 0x00, 0xA6, 0x01, 0x80, 0x00,
/* 0x07 */
/* 0x08 */ 0x00, 0xF8, 0x00, 0x82, 0x00, 0x80, 0x83, 0xE0, 0x41, 0x10, 0x21, 0x04, 0x1B, 0x00, 0x03, 0x00, 0x01, 0x80, 0x00, 0xE0, 0x00, 0x4F, 0xE1, 0xC0, 0x0F, 0x02, 0x00, 0x03, 0x01, 0x00, 0x09, 0x88, 0x0C, 0x0C,
/* 0x09 */ 0x00, 0xF8, 0x00, 0x82, 0x00, 0x80, 0x83, 0xE0, 0x41, 0x10, 0x21, 0x04, 0x1B, 0x00, 0x03, 0x00, 0x01, 0x80, 0x00, 0xE0, 0x00, 0x4F, 0xE1, 0xC0, 0x0F, 0x00,
/* 0x0A */
/* 0x0B */ 0x1C, 0x1C, 0x31, 0xB1, 0x90, 0x50, 0x50, 0x10, 0x18, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x02, 0x80, 0x02, 0x40, 0x01, 0x10, 0x01, 0x04, 0x01, 0x01, 0x01, 0x00, 0x41, 0x00, 0x11, 0x00, 0x07, 0x00, 0x01, 0x00,
/* 0x0C */ 0x06, 0x00, 0x0A, 0x00, 0x12, 0x00, 0x32, 0x01, 0x84, 0x04, 0x10, 0x08, 0x98, 0x1C, 0x18, 0x40, 0x48, 0x82, 0x11, 0xF0, 0x74, 0x02, 0x18, 0x70, 0x2F, 0x9F, 0x80,
/* 0x0D */
/* 0x0E */ 0x01, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x3E, 0x00, 0x82, 0x02, 0x82, 0x06, 0x04, 0x10, 0x04, 0x20, 0x08, 0x40, 0x10, 0xFF, 0x22, 0x00, 0x29, 0xFF, 0x3F, 0x8F, 0xDF, 0x9F, 0x01, 0xC0,
/* 0x0F */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x00, 0x28, 0x82, 0x36, 0x03, 0x60, 0x00, 0xCC, 0x19, 0xA4, 0x4B, 0x00, 0x06, 0x8E, 0x2B, 0x22, 0x66, 0x7C, 0xCC, 0x71, 0x98, 0x03, 0x00,
/* 0x10 */ 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1E, 0x00, 0x54, 0x00, 0xA8, 0x01, 0x50, 0x02, 0xA0, 0x05, 0x20, 0x32, 0x61, 0xC4, 0x74, 0x49, 0x10, 0x6C, 0x00, 0xD8, 0x01, 0x10, 0x00,
/* 0x11 */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x40, 0x29, 0x00, 0x31, 0x84, 0x63, 0x18, 0xC0, 0x00, 0x80, 0x15, 0x03, 0x7E, 0x02, 0xFA, 0x04, 0xE4, 0x18, 0x84, 0x00, 0x06, 0x0C, 0x03, 0xE0,
/* 0x12 */ 0x02, 0x08, 0x01, 0x08, 0x40, 0x10, 0xC0, 0x08, 0xC0, 0x60, 0x80, 0x28, 0x04, 0x12, 0x4C, 0x10, 0x80, 0x08, 0x23, 0x0E, 0x08, 0xC4, 0x82, 0x04, 0x20, 0x83, 0x09, 0x82, 0x47, 0x01, 0x1C, 0x01, 0x30, 0x00, 0xE0, 0x00, 0x00,
/* 0x13 */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x00, 0x28, 0x00, 0x31, 0x08, 0x65, 0x28, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x06, 0x3F, 0x8C, 0x3E, 0x14, 0x00, 0x44, 0x01, 0x06, 0x0C, 0x03, 0xE0,
/* 0x14 */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x22, 0x29, 0x83, 0x30, 0x00, 0x65, 0x14, 0xD3, 0x4D, 0xBA, 0xEB, 0x38, 0xE6, 0x00, 0x0A, 0x00, 0x24, 0x38, 0x44, 0x01, 0x07, 0x1C, 0x01, 0xC0,
/* 0x15 */ 0x07, 0xC0, 0x30, 0x18, 0x80, 0x32, 0x00, 0xF8, 0x01, 0xF1, 0x09, 0xA5, 0x28, 0x40, 0x01, 0x80, 0x03, 0x00, 0x06, 0x3F, 0x8C, 0x3E, 0x14, 0x00, 0x44, 0x01, 0x06, 0x0C, 0x03, 0xE0,
/* 0x16 */ 0x0C, 0x00, 0xC0, 0x1C, 0x03, 0x80, 0xF8, 0xBB, 0x36, 0xC7, 0x99, 0xF3, 0xFE, 0x3F, 0xC3, 0xF0, 0x7E, 0x0E, 0xC1, 0x8E, 0xE0, 0x20,
/* 0x17 */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x00, 0x28, 0x00, 0x10, 0x01, 0x20, 0x1D, 0x44, 0x42, 0x84, 0x85, 0x00, 0x86, 0x00, 0xC4, 0x00, 0x44, 0x7C, 0x44, 0x00, 0x06, 0x0C, 0x03, 0xE0,
/* 0x18 */ 0x01, 0xE0, 0x00, 0x84, 0x00, 0x40, 0x80, 0x20, 0x10, 0x08, 0x24, 0x02, 0x41, 0x00, 0x86, 0x03, 0x12, 0x03, 0xB4, 0x03, 0x52, 0x81, 0x23, 0x80, 0x70, 0xA0, 0x14, 0x28, 0x05, 0x0A, 0x01, 0x42, 0x80, 0x50,
/* 0x19 */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x00, 0x28, 0x00, 0x33, 0x18, 0x60, 0x00, 0xDC, 0xE1, 0xB9, 0xC3, 0x7B, 0xC6, 0x63, 0x0A, 0x00, 0x24, 0xF0, 0x44, 0x01, 0x06, 0x0C, 0x03, 0xE0,
/* 0x1A */ 0xFF, 0xFC, 0x00, 0x63, 0xE3, 0x31, 0x99, 0x04, 0xC8, 0x66, 0x06, 0x30, 0x61, 0x82, 0x0C, 0x10, 0x60, 0x03, 0x04, 0x18, 0x00, 0xFF, 0xFC,
/* 0x1B */ 0x07, 0xF0, 0x06, 0x0C, 0x04, 0x01, 0x04, 0x00, 0x44, 0x22, 0x12, 0x2A, 0x89, 0x00, 0x04, 0x80, 0x02, 0x44, 0x11, 0x01, 0xF0, 0x04, 0x01, 0x0D, 0x01, 0x6A, 0x41, 0x2C, 0x00, 0x05, 0xC0, 0x0E, 0x18, 0x18,
/* 0x1C */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0xC0, 0x2A, 0x00, 0x33, 0x00, 0x66, 0x00, 0xCC, 0x39, 0x80, 0x83, 0x00, 0x06, 0x00, 0x8C, 0x3E, 0x14, 0x00, 0x44, 0x01, 0x06, 0x0C, 0x03, 0xE0,
/* 0x1D */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x70, 0x28, 0x00, 0x31, 0x80, 0x63, 0x18, 0xC0, 0x31, 0x80, 0x03, 0x00, 0x06, 0x60, 0x0D, 0x33, 0x12, 0x10, 0x48, 0x21, 0x23, 0x8C, 0x00,
/* 0x1E */ 0x03, 0x00, 0x07, 0x9E, 0x07, 0x00, 0x86, 0x00, 0x27, 0xC0, 0x0F, 0xC0, 0x07, 0x8C, 0x62, 0x06, 0x31, 0x20, 0x00, 0x90, 0x00, 0x48, 0x00, 0x24, 0x3E, 0x11, 0x00, 0x10, 0x40, 0x10, 0x18, 0x30, 0x03, 0xE0,
/* 0x1F */ 0x18, 0x02, 0x80, 0x4C, 0x16, 0x41, 0x24, 0x3C, 0x88, 0x6E, 0x65, 0xF2, 0x78, 0x46, 0x88, 0xCF, 0x18, 0x02, 0x80, 0x8C, 0x60, 0x70,
/* ' ' 0x20 */
/* '!' 0x21 */ 0xFF, 0xFF, 0xF0, 0xC0,
/* '"' 0x22 */ 0xDE, 0xF7, 0x20,
/* '#' 0x23 */ 0x09, 0x86, 0x41, 0x91, 0xFF, 0x13, 0x04, 0xC3, 0x20, 0xC8, 0xFF, 0x89, 0x82, 0x61, 0x90,
/* '$' 0x24 */ 0x10, 0x1F, 0x14, 0xDA, 0x3D, 0x1E, 0x83, 0x40, 0x78, 0x17, 0x08, 0xF4, 0x7A, 0x35, 0x33, 0xF0, 0x40, 0x20,
/* '%' 0x25 */ 0x38, 0x10, 0xEC, 0x20, 0xC6, 0x20, 0xC6, 0x40, 0xC6, 0x40, 0x6C, 0x80, 0x39, 0x00, 0x01, 0x3C, 0x02, 0x77, 0x02, 0x63, 0x04, 0x63, 0x04, 0x77, 0x08, 0x3C,
/* '&' 0x26 */ 0x0E, 0x0C, 0xC3, 0x30, 0xCC, 0x1E, 0x03, 0x03, 0xC1, 0x9B, 0xC2, 0xF0, 0xEC, 0x19, 0x8F, 0x3C, 0x40,
/* ''' 0x27 */ 0xFE,
/* '(' 0x28 */ 0x13, 0x26, 0x6C, 0xCC, 0xCC, 0xC4, 0x66, 0x23, 0x10,
/* ')' 0x29 */ 0x8C, 0x46, 0x63, 0x33, 0x33, 0x32, 0x66, 0x4C, 0x80,
/* '*' 0x2A */ 0x25, 0x7E, 0xA5, 0x00,
/* '+' 0x2B */ 0x30, 0xC3, 0x3F, 0x30, 0xC3, 0x0C,
/* ',' 0x2C */ 0xD6,
/* '-' 0x2D */ 0xF0,
/* '.' 0x2E */ 0xC0,
/* '/' 0x2F */ 0x08, 0x44, 0x21, 0x10, 0x84, 0x42, 0x11, 0x08, 0x00,
/* '0' 0x30 */ 0x3C, 0x66, 0x42, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x42, 0x66, 0x3C,
/* '1' 0x31 */ 0x11, 0x3F, 0x33, 0x33, 0x33, 0x33, 0x30,
/* '2' 0x32 */ 0x3E, 0x31, 0xB0, 0x78, 0x30, 0x18, 0x1C, 0x1C, 0x1C, 0x18, 0x18, 0x10, 0x08, 0x07, 0xF8,
/* '3' 0x33 */ 0x3C, 0x66, 0xC3, 0xC3, 0x03, 0x06, 0x1C, 0x07, 0x03, 0xC3, 0xC3, 0x66, 0x3C,
/* '4' 0x34 */ 0x0C, 0x18, 0x71, 0x62, 0xC9, 0xA3, 0x46, 0xFE, 0x18, 0x30, 0x60, 0xC0,
/* '5' 0x35 */ 0x7F, 0x20, 0x10, 0x08, 0x08, 0x07, 0xF3, 0x8C, 0x03, 0x01, 0x80, 0xF0, 0x6C, 0x63, 0xE0,
/* '6' 0x36 */ 0x1E, 0x31, 0x98, 0x78, 0x0C, 0x06, 0xF3, 0x8D, 0x83, 0xC1, 0xE0, 0xD0, 0x6C, 0x63, 0xE0,
/* '7' 0x37 */ 0xFF, 0x03, 0x02, 0x06, 0x04, 0x0C, 0x08, 0x18, 0x18, 0x18, 0x10, 0x30, 0x30,
/* '8' 0x38 */ 0x3E, 0x31, 0xB0, 0x78, 0x3C, 0x1B, 0x18, 0xF8, 0xC6, 0xC1, 0xE0, 0xF0, 0x6C, 0x63, 0xE0,
/* '9' 0x39 */ 0x3C, 0x66, 0xC2, 0xC3, 0xC3, 0xC3, 0x67, 0x3B, 0x03, 0x03, 0xC2, 0x66, 0x3C,
/* ':' 0x3A */ 0xC0, 0x00, 0x30,
/* ';' 0x3B */ 0xC0, 0x00, 0x00, 0x64, 0xA0,
/* '<' 0x3C */ 0x00, 0x81, 0xC7, 0x8E, 0x0C, 0x07, 0x80, 0x70, 0x0E, 0x01, 0x80,
/* '=' 0x3D */ 0xFF, 0x80, 0x00, 0x1F, 0xF0,
/* '>' 0x3E */ 0xE0, 0x1C, 0x03, 0x80, 0x30, 0x70, 0xE3, 0x81, 0x00,
/* '?' 0x3F */ 0x3E, 0x31, 0xB0, 0x78, 0x30, 0x18, 0x18, 0x38, 0x18, 0x18, 0x0C, 0x00, 0x00, 0x01, 0x80,
/* '@' 0x40 */ 0x03, 0xF0, 0x06, 0x0E, 0x06, 0x01, 0x86, 0x00, 0x66, 0x1D, 0xBB, 0x31, 0xCF, 0x18, 0xC7, 0x98, 0x63, 0xCC, 0x31, 0xE6, 0x11, 0xB3, 0x99, 0xCC, 0xF7, 0x86, 0x00, 0x01, 0x80, 0x00, 0x70, 0x40, 0x0F, 0xE0,
/* 'A' 0x41 */ 0x06, 0x00, 0xF0, 0x0F, 0x00, 0x90, 0x19, 0x81, 0x98, 0x10, 0x83, 0x0C, 0x3F, 0xC2, 0x04, 0x60, 0x66, 0x06, 0xC0, 0x30,
/* 'B' 0x42 */ 0xFF, 0x18, 0x33, 0x03, 0x60, 0x6C, 0x0D, 0x83, 0x3F, 0xC6, 0x06, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x6F, 0xF8,
/* 'C' 0x43 */ 0x1F, 0x86, 0x19, 0x81, 0xA0, 0x3C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x68, 0x0D, 0x83, 0x18, 0x61, 0xF0,
/* 'D' 0x44 */ 0xFF, 0x18, 0x33, 0x03, 0x60, 0x3C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x03, 0x60, 0xCF, 0xF0,
/* 'E' 0x45 */ 0xFF, 0xE0, 0x30, 0x18, 0x0C, 0x06, 0x03, 0xFD, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0F, 0xF8,
/* 'F' 0x46 */ 0xFF, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xFE, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0,
/* 'G' 0x47 */ 0x0F, 0x83, 0x0E, 0x60, 0x66, 0x03, 0xC0, 0x0C, 0x00, 0xC1, 0xFC, 0x03, 0xC0, 0x36, 0x03, 0x60, 0x73, 0x0F, 0x0F, 0x10,
/* 'H' 0x48 */ 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xFF, 0xFE, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x06,
/* 'I' 0x49 */ 0xFF, 0xFF, 0xFF, 0xC0,
/* 'J' 0x4A */ 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x07, 0x8F, 0x1E, 0x27, 0x80,
/* 'K' 0x4B */ 0xC0, 0xF0, 0x6C, 0x33, 0x18, 0xCC, 0x37, 0x0F, 0xC3, 0x98, 0xC3, 0x30, 0xCC, 0x1B, 0x03, 0xC0, 0xC0,
/* 'L' 0x4C */ 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xFF,
/* 'M' 0x4D */ 0xE0, 0x3F, 0x01, 0xFC, 0x1F, 0xE0, 0xFD, 0x05, 0xEC, 0x6F, 0x63, 0x79, 0x13, 0xCD, 0x9E, 0x6C, 0xF1, 0x47, 0x8E, 0x3C, 0x71, 0x80,
/* 'N' 0x4E */ 0xE0, 0x7C, 0x0F, 0xC1, 0xE8, 0x3D, 0x87, 0x98, 0xF1, 0x1E, 0x33, 0xC3, 0x78, 0x6F, 0x07, 0xE0, 0x7C, 0x0E,
/* 'O' 0x4F */ 0x0F, 0x81, 0x83, 0x18, 0x0C, 0xC0, 0x6C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1B, 0x01, 0x98, 0x0C, 0x60, 0xC0, 0xF8, 0x00,
/* 'P' 0x50 */ 0xFF, 0x30, 0x6C, 0x0F, 0x03, 0xC0, 0xF0, 0x6F, 0xF3, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x00,
/* 'Q' 0x51 */ 0x0F, 0x81, 0x83, 0x18, 0x0C, 0xC0, 0x6C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1B, 0x01, 0x98, 0x6C, 0x60, 0xC0, 0xFB, 0x00, 0x08,
/* 'R' 0x52 */ 0xFF, 0x8C, 0x0E, 0xC0, 0x6C, 0x06, 0xC0, 0x6C, 0x0C, 0xFF, 0x8C, 0x0E, 0xC0, 0x6C, 0x06, 0xC0, 0x6C, 0x06, 0xC0, 0x70,
/* 'S' 0x53 */ 0x3F, 0x18, 0x6C, 0x0F, 0x03, 0xC0, 0x1E, 0x01, 0xF0, 0x0E, 0x00, 0xF0, 0x3C, 0x0D, 0x86, 0x3F, 0x00,
/* 'T' 0x54 */ 0xFF, 0x86, 0x03, 0x01, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x80, 0xC0,
/* 'U' 0x55 */ 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xB0, 0x61, 0xF0,
/* 'V' 0x56 */ 0xC0, 0x6C, 0x0D, 0x81, 0x10, 0x63, 0x0C, 0x61, 0x04, 0x60, 0xCC, 0x19, 0x01, 0x60, 0x3C, 0x07, 0x00, 0x60,
/* 'W' 0x57 */ 0xC1, 0x81, 0x61, 0xC3, 0x61, 0xC3, 0x61, 0x43, 0x62, 0x62, 0x22, 0x66, 0x32, 0x26, 0x36, 0x26, 0x14, 0x34, 0x14, 0x34, 0x1C, 0x1C, 0x18, 0x1C, 0x08, 0x18,
/* 'X' 0x58 */ 0xC0, 0xD8, 0x66, 0x18, 0xCC, 0x1E, 0x07, 0x00, 0xC0, 0x78, 0x32, 0x0C, 0xC6, 0x1B, 0x07, 0xC0, 0xC0,
/* 'Y' 0x59 */ 0xC0, 0x36, 0x06, 0x30, 0xC3, 0x0C, 0x19, 0x81, 0xD8, 0x0F, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00,
/* 'Z' 0x5A */ 0xFF, 0xC0, 0x60, 0x30, 0x0C, 0x06, 0x03, 0x01, 0xC0, 0x60, 0x30, 0x18, 0x06, 0x03, 0x00, 0xFF, 0xC0,
/* '[' 0x5B */ 0xFB, 0x6D, 0xB6, 0xDB, 0x6D, 0xB6, 0xE0,
/* '\' 0x5C */ 0x84, 0x10, 0x84, 0x10, 0x84, 0x10, 0x84, 0x10, 0x80,
/* ']' 0x5D */ 0xED, 0xB6, 0xDB, 0x6D, 0xB6, 0xDB, 0xE0,
/* '^' 0x5E */ 0x30, 0x60, 0xA2, 0x44, 0xD8, 0xA1, 0x80,
/* '_' 0x5F */ 0xFF, 0xC0,
/* '`' 0x60 */ 0xC6, 0x30,
/* 'a' 0x61 */ 0x7E, 0x71, 0xB0, 0xC0, 0x60, 0xF3, 0xDB, 0x0D, 0x86, 0xC7, 0x3D, 0xC0,
/* 'b' 0x62 */ 0xC0, 0x60, 0x30, 0x1B, 0xCE, 0x36, 0x0F, 0x07, 0x83, 0xC1, 0xE0, 0xF0, 0x7C, 0x6D, 0xE0,
/* 'c' 0x63 */ 0x3C, 0x66, 0xC3, 0xC0, 0xC0, 0xC0, 0xC0, 0xC3, 0x66, 0x3C,
/* 'd' 0x64 */ 0x03, 0x03, 0x03, 0x3B, 0x67, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x67, 0x3B,
/* 'e' 0x65 */ 0x3C, 0x66, 0xC3, 0xC3, 0xFF, 0xC0, 0xC0, 0xC3, 0x66, 0x3C,
/* 'f' 0x66 */ 0x36, 0x6F, 0x66, 0x66, 0x66, 0x66, 0x60,
/* 'g' 0x67 */ 0x3B, 0x67, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x67, 0x3B, 0x03, 0x03, 0xC6, 0x7C,
/* 'h' 0x68 */ 0xC0, 0xC0, 0xC0, 0xDE, 0xE3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,
/* 'i' 0x69 */ 0xC3, 0xFF, 0xFF, 0xC0,
/* 'j' 0x6A */ 0x30, 0x03, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xE0,
/* 'k' 0x6B */ 0xC0, 0xC0, 0xC0, 0xC2, 0xC4, 0xCC, 0xD8, 0xF8, 0xEC, 0xC4, 0xC6, 0xC3, 0xC3,
/* 'l' 0x6C */ 0xFF, 0xFF, 0xFF, 0xC0,
/* 'm' 0x6D */ 0xDE, 0xF7, 0x1C, 0xF0, 0xC7, 0x86, 0x3C, 0x31, 0xE1, 0x8F, 0x0C, 0x78, 0x63, 0xC3, 0x1E, 0x18, 0xC0,
/* 'n' 0x6E */ 0xDE, 0xE3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,
/* 'o' 0x6F */ 0x3C, 0x66, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x66, 0x3C,
/* 'p' 0x70 */ 0xDE, 0x71, 0xB0, 0x78, 0x3C, 0x1E, 0x0F, 0x07, 0x83, 0xE3, 0x6F, 0x30, 0x18, 0x0C, 0x00,
/* 'q' 0x71 */ 0x3B, 0x67, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x67, 0x3B, 0x03, 0x03, 0x03,
/* 'r' 0x72 */ 0xDF, 0x31, 0x8C, 0x63, 0x18, 0xC6, 0x00,
/* 's' 0x73 */ 0x3E, 0xE3, 0xC0, 0xC0, 0xE0, 0x3C, 0x07, 0xC3, 0xE3, 0x7E,
/* 't' 0x74 */ 0x66, 0xF6, 0x66, 0x66, 0x66, 0x67,
/* 'u' 0x75 */ 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0x7B,
/* 'v' 0x76 */ 0xC1, 0xA0, 0x98, 0xCC, 0x42, 0x21, 0xB0, 0xD0, 0x28, 0x1C, 0x0C, 0x00,
/* 'w' 0x77 */ 0xC6, 0x1E, 0x38, 0x91, 0xC4, 0xCA, 0x66, 0xD3, 0x16, 0xD0, 0xA6, 0x87, 0x1C, 0x38, 0xC0, 0xC6, 0x00,
/* 'x' 0x78 */ 0x87, 0x89, 0xB1, 0xC3, 0x07, 0x1E, 0x26, 0xC5, 0x0C,
/* 'y' 0x79 */ 0xC1, 0x43, 0x63, 0x62, 0x26, 0x36, 0x34, 0x1C, 0x1C, 0x18, 0x18, 0x18, 0x10, 0x60,
/* 'z' 0x7A */ 0xFE, 0x0C, 0x30, 0xC1, 0x86, 0x18, 0x20, 0xC1, 0xFC,
/* '{' 0x7B */ 0x36, 0x66, 0x66, 0x6E, 0xCE, 0x66, 0x66, 0x66, 0x30,
/* '|' 0x7C */ 0xFF, 0xFF, 0xFF, 0xFF, 0xC0,
/* '}' 0x7D */ 0xC6, 0x66, 0x66, 0x67, 0x37, 0x66, 0x66, 0x66, 0xC0,
/* '~' 0x7E */ 0x61, 0x24, 0x38,
/* 0x7F */ 0xFF, 0xFC, 0x00, 0x63, 0xE3, 0x31, 0x99, 0x04, 0xC8, 0x66, 0x06, 0x30, 0x61, 0x83, 0x0C, 0x18, 0x60, 0x03, 0x06, 0x18, 0x00, 0xFF, 0xFC,
/* 0x80 */ 0xFF, 0x01, 0x80, 0x18, 0x01, 0x80, 0x18, 0x01, 0xFE, 0x18, 0x31, 0x83, 0x18, 0x31, 0x83, 0x18, 0x31, 0x83, 0x18, 0x30, 0x03, 0x00, 0x30, 0x0E,
/* 0x81 */ 0x0C, 0x18, 0x00, 0xFF, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0,
/* 0x82 */ 0xDC,
/* 0x83 */ 0x18, 0x89, 0xFC, 0x63, 0x18, 0xC6, 0x31, 0x8C, 0x00,
/* 0x84 */ 0xDA, 0x76,
/* 0x85 */ 0xCC, 0xC0,
/* 0x86 */ 0x18, 0x18, 0x18, 0x18, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
/* 0x87 */ 0x18, 0x18, 0x18, 0x18, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xFF, 0x18, 0x18, 0x18, 0x18,
/* 0x88 */ 0x07, 0xC6, 0x13, 0x00, 0xC0, 0x60, 0x3F, 0xE6, 0x03, 0xFC, 0x60, 0x0C, 0x03, 0x00, 0x61, 0x07, 0xC0,
/* 0x89 */ 0x70, 0x80, 0x22, 0x20, 0x08, 0x90, 0x02, 0x24, 0x00, 0x72, 0x00, 0x00, 0x80, 0x00, 0x40, 0x00, 0x10, 0x00, 0x09, 0xC7, 0x84, 0x8B, 0x31, 0x22, 0x84, 0x88, 0xB3, 0x21, 0xC7, 0x80,
/* 0x8A */ 0x3F, 0x80, 0x18, 0xC0, 0x0C, 0x60, 0x06, 0x30, 0x03, 0x18, 0x01, 0x8C, 0x00, 0xC7, 0xF8, 0x63, 0x06, 0x31, 0x81, 0x90, 0xC0, 0xD8, 0x60, 0x6C, 0x30, 0x6C, 0x1F, 0xE0,
/* 0x8B */ 0x69,
/* 0x8C */ 0xC0, 0xC0, 0x60, 0x60, 0x30, 0x30, 0x18, 0x18, 0x0C, 0x0C, 0x06, 0x06, 0x03, 0xFF, 0xF9, 0x81, 0x86, 0xC0, 0xC1, 0xE0, 0x60, 0xF0, 0x30, 0x78, 0x18, 0x6C, 0x0F, 0xE0,
/* 0x8D */ 0x0C, 0x06, 0x0C, 0x1B, 0x0C, 0xC6, 0x33, 0x0D, 0x83, 0xC0, 0xF0, 0x3E, 0x0D, 0xC3, 0x38, 0xC7, 0x30, 0xEC, 0x1C,
/* 0x8E */ 0xFF, 0x01, 0x80, 0x18, 0x01, 0x80, 0x18, 0x01, 0xFE, 0x18, 0x31, 0x83, 0x18, 0x31, 0x83, 0x18, 0x31, 0x83, 0x18, 0x30,
/* 0x8F */ 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3F, 0xFE, 0x0C, 0x01, 0x80,
/* 0x90 */ 0x60, 0x7C, 0x18, 0x0D, 0xE7, 0x1B, 0x0D, 0x86, 0xC3, 0x61, 0xB0, 0xD8, 0x6C, 0x36, 0x18, 0x18, 0x08, 0x08,
/* 0x91 */ 0x6B,
/* 0x92 */ 0xD6,
/* 0x93 */ 0x4C, 0xA5, 0xB0,
/* 0x94 */ 0xDA, 0x53, 0x20,
/* 0x95 */ 0x6F, 0xFF, 0x60,
/* 0x96 */ 0xFE,
/* 0x97 */ 0xFF, 0xFF,
/* 0x98 */
/* 0x99 */ 0xFC, 0xE1, 0xCC, 0x38, 0x73, 0x0E, 0x1C, 0xC3, 0x8F, 0x30, 0xD2, 0xCC, 0x34, 0xB3, 0x0D, 0x6C, 0xC3, 0x53, 0x30, 0xCC, 0xCC, 0x33, 0x30,
/* 0x9A */ 0x7E, 0x03, 0x30, 0x19, 0x80, 0xCC, 0x06, 0x60, 0x33, 0xF9, 0x98, 0x6C, 0xC3, 0x46, 0x1E, 0x3F, 0x80,
/* 0x9B */ 0x96,
/* 0x9C */ 0xC3, 0x03, 0x0C, 0x0C, 0x30, 0x30, 0xC0, 0xC3, 0x03, 0xFF, 0xEC, 0x30, 0xF0, 0xC3, 0xC3, 0x0F, 0x0F, 0xE0,
/* 0x9D */ 0x0C, 0x30, 0x46, 0x3C, 0xDB, 0x34, 0x70, 0xF1, 0xB3, 0x36, 0x3C, 0x20,
/* 0x9E */ 0x60, 0x7C, 0x18, 0x0D, 0xE7, 0x3B, 0x0D, 0x86, 0xC3, 0x61, 0xB0, 0xD8, 0x6C, 0x36, 0x18,
/* 0x9F */ 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xFF, 0x18, 0x18,
/* 0xA0 */
/* 0xA1 */ 0x21, 0x07, 0x8C, 0x0F, 0x06, 0x61, 0x98, 0xC3, 0x30, 0xD8, 0x1E, 0x07, 0x00, 0xC0, 0x60, 0x18, 0x0C, 0x03, 0x00,
/* 0xA2 */ 0x66, 0x18, 0xC1, 0x43, 0x63, 0x62, 0x26, 0x36, 0x34, 0x1C, 0x1C, 0x18, 0x18, 0x18, 0x10, 0x60,
/* 0xA3 */ 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x07, 0x8F, 0x1E, 0x27, 0x80,
/* 0xA4 */ 0xFF, 0xDF, 0x1E, 0x3E, 0xFF, 0xC0,
/* 0xA5 */ 0x00, 0xC0, 0x3F, 0xFF, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x00,
/* 0xA6 */ 0xFF, 0xFC, 0x0F, 0xFF, 0xC0,
/* 0xA7 */ 0x0C, 0x09, 0x0C, 0xC6, 0x63, 0x81, 0xE3, 0x19, 0x87, 0xE1, 0xB8, 0xC6, 0x41, 0xC0, 0x73, 0x19, 0x8C, 0x66, 0x1E, 0x00,
/* 0xA8 */ 0x33, 0x00, 0x3F, 0xF8, 0x0C, 0x06, 0x03, 0x01, 0x80, 0xFF, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0xFE,
/* 0xA9 */ 0x0F, 0xC0, 0x61, 0x87, 0x03, 0x9B, 0xC6, 0xD9, 0x8F, 0x60, 0x3D, 0x00, 0xF4, 0x03, 0xD8, 0x0D, 0xE6, 0x67, 0xF3, 0x86, 0x18, 0x0F, 0xC0,
/* 0xAA */ 0x1F, 0x86, 0x19, 0x81, 0xB0, 0x3C, 0x01, 0x80, 0x3F, 0xC6, 0x00, 0xC0, 0x68, 0x0D, 0x83, 0x18, 0x61, 0xF0,
/* 0xAB */ 0x22, 0xCF, 0x26, 0x46, 0x64, 0x40,
/* 0xAC */ 0xFF, 0x80, 0xC0, 0x60, 0x30, 0x18,
/* 0xAD */
/* 0xAE */ 0x0F, 0xC0, 0x61, 0x87, 0x03, 0x9F, 0xE6, 0xD0, 0x8F, 0x42, 0x3D, 0xF0, 0xF4, 0x23, 0xD0, 0x8D, 0xC2, 0x67, 0x0B, 0x86, 0x18, 0x0F, 0xC0,
/* 0xAF */ 0xCC, 0x03, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x00,
/* 0xB0 */ 0x74, 0x63, 0x17, 0x00,
/* 0xB1 */ 0x0C, 0x06, 0x03, 0x07, 0xE0, 0xC0, 0x60, 0x30, 0x18, 0x00, 0x00, 0x3F, 0xE0,
/* 0xB2 */ 0xFF, 0xFF, 0xFF, 0xC0,
/* 0xB3 */ 0xC3, 0xFF, 0xFF, 0xC0,
/* 0xB4 */ 0x0C, 0x3F, 0xF0, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30,
/* 0xB5 */ 0xC3, 0x61, 0xB0, 0xD8, 0x6C, 0x36, 0x1B, 0x0D, 0x86, 0xE7, 0x7D, 0xF0, 0x18, 0x0C, 0x00,
/* 0xB6 */ 0x3F, 0x7E, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0x72, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
/* 0xB7 */ 0xE0,
/* 0xB8 */ 0x66, 0x00, 0x3C, 0x66, 0xC3, 0xC3, 0xFF, 0xC0, 0xC0, 0xC3, 0x66, 0x3C,
/* 0xB9 */ 0xC1, 0x81, 0x83, 0x03, 0x86, 0x05, 0x0C, 0xEB, 0x1A, 0x32, 0x34, 0x66, 0x68, 0xC4, 0xD1, 0x8D, 0xB3, 0x0B, 0x3A, 0x1E, 0x04, 0x1C, 0x08, 0x1B, 0xC0,
/* 0xBA */ 0x3C, 0x46, 0xC3, 0x80, 0xF8, 0x80, 0x80, 0xC3, 0x46, 0x3C,
/* 0xBB */ 0x89, 0x98, 0x99, 0x3C, 0xD1, 0x00,
/* 0xBC */ 0x30, 0x03, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xE0,
/* 0xBD */ 0x3F, 0x18, 0x6C, 0x0F, 0x03, 0xC0, 0x1E, 0x01, 0xF0, 0x0E, 0x00, 0xF0, 0x3C, 0x0D, 0x86, 0x3F, 0x00,
/* 0xBE */ 0x3E, 0xE3, 0xC0, 0xC0, 0x60, 0x3C, 0x07, 0xC3, 0xE3, 0x7E,
/* 0xBF */ 0xCC, 0x03, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C,
/* 0xC0 */ 0x06, 0x00, 0xF0, 0x0F, 0x00, 0x90, 0x19, 0x81, 0x98, 0x10, 0x83, 0x0C, 0x3F, 0xC2, 0x04, 0x60, 0x66, 0x06, 0xC0, 0x30,
/* 0xC1 */ 0xFF, 0x18, 0x03, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x3F, 0xE6, 0x06, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x6F, 0xF8,
/* 0xC2 */ 0xFF, 0x18, 0x33, 0x03, 0x60, 0x6C, 0x0D, 0x83, 0x3F, 0xC6, 0x06, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x6F, 0xF8,
/* 0xC3 */ 0xFF, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0,
/* 0xC4 */ 0x1F, 0xF0, 0x60, 0xC1, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x03, 0x0C, 0x0C, 0xFF, 0xFF, 0x00, 0x3C, 0x00, 0xF0, 0x03,
/* 0xC5 */ 0xFF, 0xE0, 0x30, 0x18, 0x0C, 0x06, 0x03, 0xFD, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0F, 0xF8,
/* 0xC6 */ 0x61, 0x86, 0x31, 0x8C, 0x19, 0x98, 0x19, 0x98, 0x0D, 0xB0, 0x07, 0xE0, 0x03, 0xC0, 0x07, 0xE0, 0x0D, 0xB0, 0x19, 0x98, 0x31, 0x8C, 0x61, 0x86, 0xC1, 0x83,
/* 0xC7 */ 0x3F, 0x18, 0x6C, 0x0F, 0x03, 0x00, 0xC0, 0x60, 0xF0, 0x06, 0x00, 0xF0, 0x3C, 0x0D, 0x86, 0x3F, 0x00,
/* 0xC8 */ 0xC0, 0xF8, 0x1F, 0x07, 0xE0, 0xBC, 0x37, 0x8C, 0xF1, 0x1E, 0x63, 0xD8, 0x7A, 0x0F, 0xC1, 0xF0, 0x3E, 0x06,
/* 0xC9 */ 0x11, 0x03, 0xE0, 0x00, 0x60, 0x7C, 0x0F, 0x83, 0xF0, 0x5E, 0x1B, 0xC6, 0x78, 0x8F, 0x31, 0xEC, 0x3D, 0x07, 0xE0, 0xF8, 0x1F, 0x03,
/* 0xCA */ 0xC1, 0xB0, 0xCC, 0x63, 0x30, 0xD8, 0x3C, 0x0F, 0x03, 0xE0, 0xDC, 0x33, 0x8C, 0x73, 0x0E, 0xC1, 0xC0,
/* 0xCB */ 0x3F, 0xCC, 0x33, 0x0C, 0xC3, 0x30, 0xCC, 0x33, 0x0C, 0xC3, 0x30, 0xC8, 0x36, 0x0D, 0x83, 0xC0, 0xC0,
/* 0xCC */ 0xE0, 0x3F, 0x01, 0xFC, 0x1F, 0xE0, 0xFD, 0x05, 0xEC, 0x6F, 0x63, 0x79, 0x13, 0xCD, 0x9E, 0x6C, 0xF1, 0x47, 0x8E, 0x3C, 0x71, 0x80,
/* 0xCD */ 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xFF, 0xFE, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x06,
/* 0xCE */ 0x0F, 0x81, 0x83, 0x18, 0x0C, 0xC0, 0x6C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1B, 0x01, 0x98, 0x0C, 0x60, 0xC0, 0xF8, 0x00,
/* 0xCF */ 0xFF, 0xF8, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x06,
/* 0xD0 */ 0xFF, 0x30, 0x6C, 0x0F, 0x03, 0xC0, 0xF0, 0x6F, 0xF3, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x00,
/* 0xD1 */ 0x1F, 0x86, 0x19, 0x81, 0xA0, 0x3C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x68, 0x0D, 0x83, 0x18, 0x61, 0xF0,
/* 0xD2 */ 0xFF, 0x86, 0x03, 0x01, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x80, 0xC0,
/* 0xD3 */ 0xC0, 0xF0, 0x66, 0x19, 0x8C, 0x33, 0x0D, 0x81, 0xE0, 0x70, 0x0C, 0x06, 0x01, 0x80, 0xC0, 0x30, 0x00,
/* 0xD4 */ 0x03, 0x00, 0x0C, 0x01, 0xFE, 0x1C, 0xCE, 0xE3, 0x1F, 0x0C, 0x3C, 0x30, 0xF0, 0xC3, 0xE3, 0x1D, 0xCC, 0xE3, 0xFF, 0x00, 0xC0, 0x03, 0x00,
/* 0xD5 */ 0xC0, 0xD8, 0x66, 0x18, 0xCC, 0x1E, 0x07, 0x00, 0xC0, 0x78, 0x32, 0x0C, 0xC6, 0x1B, 0x07, 0xC0, 0xC0,
/* 0xD6 */ 0xC0, 0x66, 0x03, 0x30, 0x19, 0x80, 0xCC, 0x06, 0x60, 0x33, 0x01, 0x98, 0x0C, 0xC0, 0x66, 0x03, 0x30, 0x19, 0x80, 0xCF, 0xFF, 0x80, 0x0C, 0x00, 0x60,
/* 0xD7 */ 0xC1, 0xE0, 0xF0, 0x78, 0x3C, 0x1E, 0x0F, 0x06, 0xFF, 0x01, 0x80, 0xC0, 0x60, 0x30, 0x18,
/* 0xD8 */ 0xC3, 0x1E, 0x18, 0xF0, 0xC7, 0x86, 0x3C, 0x31, 0xE1, 0x8F, 0x0C, 0x78, 0x63, 0xC3, 0x1E, 0x18, 0xF0, 0xC7, 0x86, 0x3F, 0xFF, 0x80,
/* 0xD9 */ 0xC3, 0x19, 0x86, 0x33, 0x0C, 0x66, 0x18, 0xCC, 0x31, 0x98, 0x63, 0x30, 0xC6, 0x61, 0x8C, 0xC3, 0x19, 0x86, 0x33, 0x0C, 0x66, 0x18, 0xCF, 0xFF, 0xE0, 0x00, 0xC0, 0x01, 0x80,
/* 0xDA */ 0xF8, 0x00, 0xC0, 0x06, 0x00, 0x30, 0x01, 0x80, 0x0F, 0xF0, 0x60, 0xC3, 0x03, 0x18, 0x18, 0xC0, 0xC6, 0x06, 0x30, 0x61, 0xFE, 0x00,
/* 0xDB */ 0xC0, 0x0F, 0x00, 0x3C, 0x00, 0xF0, 0x03, 0xC0, 0x0F, 0xFE, 0x3C, 0x0C, 0xF0, 0x1B, 0xC0, 0x6F, 0x01, 0xBC, 0x06, 0xF0, 0x33, 0xFF, 0x8C,
/* 0xDC */ 0xC0, 0x18, 0x03, 0x00, 0x60, 0x0C, 0x01, 0xFF, 0x30, 0x36, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x6F, 0xF8,
/* 0xDD */ 0x3F, 0x0C, 0x33, 0x83, 0x60, 0x20, 0x06, 0x00, 0x47, 0xF8, 0x01, 0xC0, 0x78, 0x0D, 0x81, 0x30, 0xC1, 0xF0,
/* 0xDE */ 0xC0, 0xF8, 0x61, 0x83, 0x31, 0x80, 0xD8, 0xC0, 0x6C, 0xC0, 0x1E, 0x60, 0x0F, 0xF0, 0x07, 0x98, 0x03, 0xCC, 0x01, 0xE3, 0x01, 0xB1, 0x80, 0xD8, 0x60, 0xCC, 0x0F, 0x80,
/* 0xDF */ 0x3F, 0xD8, 0x3C, 0x0F, 0x03, 0xC0, 0xD8, 0x33, 0xFC, 0x33, 0x18, 0xCC, 0x36, 0x0D, 0x83, 0xC0, 0xC0,
/* 0xE0 */ 0x7E, 0x71, 0xB0, 0xC0, 0x60, 0xF3, 0xDB, 0x0D, 0x86, 0xC7, 0x3D, 0xC0,
/* 0xE1 */ 0x03, 0x1F, 0x78, 0x40, 0xFC, 0xE6, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x66, 0x3C,
/* 0xE2 */ 0xFD, 0x8F, 0x0E, 0x3F, 0xDF, 0xB1, 0xE1, 0xC7, 0xF8,
/* 0xE3 */ 0xFE, 0x31, 0x8C, 0x63, 0x18, 0xC6, 0x00,
/* 0xE4 */ 0x1F, 0x83, 0x30, 0x66, 0x0C, 0xC1, 0x98, 0x33, 0x06, 0x61, 0x8C, 0x31, 0x9F, 0xFF, 0x01, 0xE0, 0x30,
/* 0xE5 */ 0x3C, 0x66, 0xC3, 0xC3, 0xFF, 0xC0, 0xC0, 0xC3, 0x66, 0x3C,
/* 0xE6 */ 0xC6, 0x36, 0x66, 0x36, 0xC1, 0xF8, 0x0F, 0x01, 0xF8, 0x36, 0xC6, 0x66, 0xC6, 0x38, 0x61,
/* 0xE7 */ 0x79, 0x8C, 0x18, 0x30, 0x43, 0x01, 0xE3, 0xC6, 0xF8,
/* 0xE8 */ 0xC7, 0xC7, 0xCF, 0xCB, 0xCB, 0xD3, 0xD3, 0xF3, 0xE3, 0xE3,
/* 0xE9 */ 0x66, 0x18, 0xC7, 0xC7, 0xCF, 0xCB, 0xCB, 0xD3, 0xD3, 0xF3, 0xE3, 0xE3,
/* 0xEA */ 0xC7, 0x9B, 0x66, 0x8E, 0x1E, 0x36, 0x66, 0xC7, 0x84,
/* 0xEB */ 0x7E, 0xCD, 0x9B, 0x36, 0x6C, 0xD9, 0xA3, 0xC7, 0x0C,
/* 0xEC */ 0xE3, 0xF1, 0xF8, 0xFE, 0xFF, 0x7E, 0xAF, 0x77, 0x93, 0xC9, 0xE0, 0xC0,
/* 0xED */ 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xFF, 0xC3, 0xC3, 0xC3, 0xC3,
/* 0xEE */ 0x3C, 0x66, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x66, 0x3C,
/* 0xEF */ 0xFF, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,
/* 0xF0 */ 0xDE, 0x71, 0xB0, 0x78, 0x3C, 0x1E, 0x0F, 0x07, 0x83, 0xE3, 0x6F, 0x30, 0x18, 0x0C, 0x00,
/* 0xF1 */ 0x3C, 0x66, 0xC3, 0xC0, 0xC0, 0xC0, 0xC0, 0xC3, 0x66, 0x3C,
/* 0xF2 */ 0xFC, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC0,
/* 0xF3 */ 0xC1, 0x43, 0x63, 0x62, 0x26, 0x36, 0x34, 0x1C, 0x1C, 0x18, 0x18, 0x18, 0x10, 0x60,
/* 0xF4 */ 0x03, 0x00, 0x0C, 0x03, 0xB7, 0x19, 0xE6, 0xC3, 0x0F, 0x0C, 0x3C, 0x30, 0xF0, 0xC3, 0xC3, 0x0F, 0x0C, 0x36, 0x79, 0x8E, 0xDC, 0x03, 0x00, 0x0C, 0x00, 0x30, 0x00,
/* 0xF5 */ 0x87, 0x89, 0xB1, 0xC3, 0x07, 0x1E, 0x26, 0xC5, 0x0C,
/* 0xF6 */ 0xC3, 0x30, 0xCC, 0x33, 0x0C, 0xC3, 0x30, 0xCC, 0x33, 0x0C, 0xC3, 0x3F, 0xF0, 0x0C, 0x03,
/* 0xF7 */ 0xC7, 0x8F, 0x1E, 0x3C, 0x6F, 0xC1, 0x83, 0x06, 0x0C,
/* 0xF8 */ 0xCC, 0xF3, 0x3C, 0xCF, 0x33, 0xCC, 0xF3, 0x3C, 0xCF, 0x33, 0xCC, 0xFF, 0xF0,
/* 0xF9 */ 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCF, 0xFF, 0x00, 0x30, 0x03,
/* 0xFA */ 0xF0, 0x18, 0x0C, 0x06, 0x03, 0xF1, 0x8C, 0xC6, 0x63, 0x31, 0x9F, 0x80,
/* 0xFB */ 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xFE, 0xF0, 0xFC, 0x3F, 0x0F, 0xC3, 0xFF, 0xB0,
/* 0xFC */ 0xC0, 0xC0, 0xC0, 0xC0, 0xFE, 0xC3, 0xC3, 0xC3, 0xC3, 0xFE,
/* 0xFD */ 0x3C, 0x62, 0xC3, 0x01, 0x1F, 0x01, 0x01, 0xC3, 0x62, 0x3C,
/* 0xFE */ 0xC7, 0xCC, 0xC6, 0xD8, 0x3D, 0x83, 0xF8, 0x3D, 0x83, 0xD8, 0x3C, 0xC2, 0xCC, 0x6C, 0x7C,
/* 0xFF */ 0x7F, 0xC3, 0xC3, 0xC3, 0x7F, 0x13, 0x33, 0x63, 0xC3, 0x83,
};
const GFXglyph FreeSans9pt_Win1251Glyphs[] PROGMEM = {
/* 0x01 */ { 0, 15, 15, 17, 1, -13 },
/* 0x02 */ { 29, 15, 15, 17, 1, -13 },
/* 0x03 */ { 58, 15, 16, 17, 1, -14 },
/* 0x04 */ { 88, 15, 16, 17, 1, -14 },
/* 0x05 */ { 118, 16, 15, 18, 1, -13 },
/* 0x06 */ { 148, 15, 15, 17, 1, -13 },
/* 0x07 */ { 177, 0, 0, 8, 0, 0 },
/* 0x08 */ { 177, 17, 16, 19, 1, -14 },
/* 0x09 */ { 211, 17, 12, 19, 1, -12 },
/* 0x0A */ { 237, 0, 0, 8, 0, 0 },
/* 0x0B */ { 237, 17, 16, 19, 1, -14 },
/* 0x0C */ { 271, 15, 14, 17, 1, -12 },
/* 0x0D */ { 298, 0, 0, 8, 0, 0 },
/* 0x0E */ { 298, 15, 16, 17, 1, -14 },
/* 0x0F */ { 328, 15, 15, 17, 1, -13 },
/* 0x10 */ { 357, 15, 15, 17, 1, -13 },
/* 0x11 */ { 386, 15, 16, 17, 1, -14 },
/* 0x12 */ { 416, 17, 17, 19, 1, -15 },
/* 0x13 */ { 453, 15, 16, 17, 1, -14 },
/* 0x14 */ { 483, 15, 16, 17, 1, -14 },
/* 0x15 */ { 513, 15, 16, 17, 1, -14 },
/* 0x16 */ { 543, 11, 16, 13, 1, -14 },
/* 0x17 */ { 565, 15, 16, 17, 1, -14 },
/* 0x18 */ { 595, 18, 15, 20, 1, -13 },
/* 0x19 */ { 629, 15, 16, 17, 1, -14 },
/* 0x1A */ { 659, 13, 14, 15, 1, -12 },
/* 0x1B */ { 682, 17, 16, 19, 1, -14 },
/* 0x1C */ { 716, 15, 16, 17, 1, -14 },
/* 0x1D */ { 746, 15, 15, 17, 1, -13 },
/* 0x1E */ { 775, 17, 16, 19, 1, -14 },
/* 0x1F */ { 809, 11, 16, 13, 1, -14 },
/* ' ' 0x20 */ { 831, 0, 0, 5, 0, 0 },
/* '!' 0x21 */ { 831, 2, 13, 6, 2, -12 },
/* '"' 0x22 */ { 835, 5, 4, 6, 1, -12 },
/* '#' 0x23 */ { 838, 10, 12, 10, 0, -11 },
/* '$' 0x24 */ { 853, 9, 16, 10, 1, -13 },
/* '%' 0x25 */ { 871, 16, 13, 16, 1, -12 },
/* '&' 0x26 */ { 897, 10, 13, 12, 1, -12 },
/* ''' 0x27 */ { 914, 2, 4, 4, 1, -12 },
/* '(' 0x28 */ { 915, 4, 17, 6, 1, -12 },
/* ')' 0x29 */ { 924, 4, 17, 6, 1, -12 },
/* '*' 0x2A */ { 933, 5, 5, 7, 1, -12 },
/* '+' 0x2B */ { 937, 6, 8, 11, 3, -7 },
/* ',' 0x2C */ { 943, 2, 4, 5, 2, 0 },
/* '-' 0x2D */ { 944, 4, 1, 6, 1, -4 },
/* '.' 0x2E */ { 945, 2, 1, 5, 1, 0 },
/* '/' 0x2F */ { 946, 5, 13, 5, 0, -12 },
/* '0' 0x30 */ { 955, 8, 13, 10, 1, -12 },
/* '1' 0x31 */ { 968, 4, 13, 10, 3, -12 },
/* '2' 0x32 */ { 975, 9, 13, 10, 1, -12 },
/* '3' 0x33 */ { 990, 8, 13, 10, 1, -12 },
/* '4' 0x34 */ { 1003, 7, 13, 10, 2, -12 },
/* '5' 0x35 */ { 1015, 9, 13, 10, 1, -12 },
/* '6' 0x36 */ { 1030, 9, 13, 10, 1, -12 },
/* '7' 0x37 */ { 1045, 8, 13, 10, 0, -12 },
/* '8' 0x38 */ { 1058, 9, 13, 10, 1, -12 },
/* '9' 0x39 */ { 1073, 8, 13, 10, 1, -12 },
/* ':' 0x3A */ { 1086, 2, 10, 5, 1, -9 },
/* ';' 0x3B */ { 1089, 3, 12, 5, 1, -8 },
/* '<' 0x3C */ { 1094, 9, 9, 11, 1, -8 },
/* '=' 0x3D */ { 1105, 9, 4, 11, 1, -5 },
/* '>' 0x3E */ { 1110, 9, 8, 11, 1, -7 },
/* '?' 0x3F */ { 1119, 9, 13, 10, 1, -12 },
/* '@' 0x40 */ { 1134, 17, 16, 18, 1, -12 },
/* 'A' 0x41 */ { 1168, 12, 13, 12, 0, -12 },
/* 'B' 0x42 */ { 1188, 11, 13, 12, 1, -12 },
/* 'C' 0x43 */ { 1206, 11, 13, 13, 1, -12 },
/* 'D' 0x44 */ { 1224, 11, 13, 13, 1, -12 },
/* 'E' 0x45 */ { 1242, 9, 13, 11, 1, -12 },
/* 'F' 0x46 */ { 1257, 8, 13, 11, 1, -12 },
/* 'G' 0x47 */ { 1270, 12, 13, 14, 1, -12 },
/* 'H' 0x48 */ { 1290, 11, 13, 13, 1, -12 },
/* 'I' 0x49 */ { 1308, 2, 13, 5, 2, -12 },
/* 'J' 0x4A */ { 1312, 7, 13, 10, 1, -12 },
/* 'K' 0x4B */ { 1324, 10, 13, 12, 1, -12 },
/* 'L' 0x4C */ { 1341, 8, 13, 10, 1, -12 },
/* 'M' 0x4D */ { 1354, 13, 13, 15, 1, -12 },
/* 'N' 0x4E */ { 1376, 11, 13, 13, 1, -12 },
/* 'O' 0x4F */ { 1394, 13, 13, 14, 1, -12 },
/* 'P' 0x50 */ { 1416, 10, 13, 12, 1, -12 },
/* 'Q' 0x51 */ { 1433, 13, 14, 14, 1, -12 },
/* 'R' 0x52 */ { 1456, 12, 13, 13, 1, -12 },
/* 'S' 0x53 */ { 1476, 10, 13, 12, 1, -12 },
/* 'T' 0x54 */ { 1493, 9, 13, 11, 1, -12 },
/* 'U' 0x55 */ { 1508, 11, 13, 13, 1, -12 },
/* 'V' 0x56 */ { 1526, 11, 13, 11, 0, -12 },
/* 'W' 0x57 */ { 1544, 16, 13, 17, 0, -12 },
/* 'X' 0x58 */ { 1570, 10, 13, 12, 1, -12 },
/* 'Y' 0x59 */ { 1587, 12, 13, 12, 0, -12 },
/* 'Z' 0x5A */ { 1607, 10, 13, 11, 1, -12 },
/* '[' 0x5B */ { 1624, 3, 17, 5, 1, -12 },
/* '\' 0x5C */ { 1631, 5, 13, 5, 0, -12 },
/* ']' 0x5D */ { 1640, 3, 17, 5, 0, -12 },
/* '^' 0x5E */ { 1647, 7, 7, 8, 1, -12 },
/* '_' 0x5F */ { 1654, 10, 1, 10, 0, 3 },
/* '`' 0x60 */ { 1656, 4, 3, 5, 0, -12 },
/* 'a' 0x61 */ { 1658, 9, 10, 10, 1, -9 },
/* 'b' 0x62 */ { 1670, 9, 13, 10, 1, -12 },
/* 'c' 0x63 */ { 1685, 8, 10, 9, 1, -9 },
/* 'd' 0x64 */ { 1695, 8, 13, 10, 1, -12 },
/* 'e' 0x65 */ { 1708, 8, 10, 10, 1, -9 },
/* 'f' 0x66 */ { 1718, 4, 13, 5, 1, -12 },
/* 'g' 0x67 */ { 1725, 8, 14, 10, 1, -9 },
/* 'h' 0x68 */ { 1739, 8, 13, 10, 1, -12 },
/* 'i' 0x69 */ { 1752, 2, 13, 4, 1, -12 },
/* 'j' 0x6A */ { 1756, 4, 17, 4, 0, -12 },
/* 'k' 0x6B */ { 1765, 8, 13, 9, 1, -12 },
/* 'l' 0x6C */ { 1778, 2, 13, 4, 1, -12 },
/* 'm' 0x6D */ { 1782, 13, 10, 15, 1, -9 },
/* 'n' 0x6E */ { 1799, 8, 10, 10, 1, -9 },
/* 'o' 0x6F */ { 1809, 8, 10, 10, 1, -9 },
/* 'p' 0x70 */ { 1819, 9, 13, 10, 1, -9 },
/* 'q' 0x71 */ { 1834, 8, 13, 10, 1, -9 },
/* 'r' 0x72 */ { 1847, 5, 10, 6, 1, -9 },
/* 's' 0x73 */ { 1854, 8, 10, 9, 1, -9 },
/* 't' 0x74 */ { 1864, 4, 12, 5, 1, -11 },
/* 'u' 0x75 */ { 1870, 8, 10, 10, 1, -9 },
/* 'v' 0x76 */ { 1880, 9, 10, 9, 0, -9 },
/* 'w' 0x77 */ { 1892, 13, 10, 13, 0, -9 },
/* 'x' 0x78 */ { 1909, 7, 10, 9, 1, -9 },
/* 'y' 0x79 */ { 1918, 8, 14, 9, 0, -9 },
/* 'z' 0x7A */ { 1932, 7, 10, 9, 1, -9 },
/* '{' 0x7B */ { 1941, 4, 17, 6, 1, -12 },
/* '|' 0x7C */ { 1950, 2, 17, 4, 2, -12 },
/* '}' 0x7D */ { 1955, 4, 17, 6, 1, -12 },
/* '~' 0x7E */ { 1964, 7, 3, 9, 1, -7 },
/* 0x7F */ { 1967, 13, 14, 15, 1, -12 },
/* 0x80 */ { 1990, 12, 16, 14, 1, -12 },
/* 0x81 */ { 2014, 8, 15, 11, 1, -14 },
/* 0x82 */ { 2029, 2, 3, 5, 1, 0 },
/* 0x83 */ { 2030, 5, 13, 7, 1, -12 },
/* 0x84 */ { 2039, 5, 3, 7, 1, 0 },
/* 0x85 */ { 2041, 10, 1, 12, 1, 0 },
/* 0x86 */ { 2043, 8, 16, 10, 1, -12 },
/* 0x87 */ { 2059, 8, 16, 10, 1, -12 },
/* 0x88 */ { 2075, 10, 13, 12, 1, -12 },
/* 0x89 */ { 2092, 18, 13, 18, 0, -12 },
/* 0x8A */ { 2122, 17, 13, 18, 1, -12 },
/* 0x8B */ { 2150, 2, 4, 4, 1, -6 },
/* 0x8C */ { 2151, 17, 13, 18, 1, -12 },
/* 0x8D */ { 2179, 10, 15, 11, 1, -14 },
/* 0x8E */ { 2198, 12, 13, 14, 1, -12 },
/* 0x8F */ { 2218, 11, 15, 13, 1, -12 },
/* 0x90 */ { 2239, 9, 16, 10, 1, -12 },
/* 0x91 */ { 2257, 2, 4, 4, 2, -12 },
/* 0x92 */ { 2258, 2, 4, 4, 1, -12 },
/* 0x93 */ { 2259, 5, 4, 7, 2, -12 },
/* 0x94 */ { 2262, 5, 4, 7, 1, -12 },
/* 0x95 */ { 2265, 4, 5, 7, 1, -8 },
/* 0x96 */ { 2268, 7, 1, 9, 1, -4 },
/* 0x97 */ { 2269, 16, 1, 18, 1, -4 },
/* 0x98 */ { 2271, 0, 0, 0, 0, 0 },
/* 0x99 */ { 2271, 18, 10, 18, 1, -13 },
/* 0x9A */ { 2294, 13, 10, 14, 1, -9 },
/* 0x9B */ { 2311, 2, 4, 5, 2, -6 },
/* 0x9C */ { 2312, 14, 10, 15, 1, -9 },
/* 0x9D */ { 2330, 7, 13, 9, 1, -12 },
/* 0x9E */ { 2342, 9, 13, 10, 1, -12 },
/* 0x9F */ { 2357, 8, 12, 10, 1, -9 },
/* 0xA0 */ { 2369, 0, 0, 5, 0, 0 },
/* 0xA1 */ { 2369, 10, 15, 11, 1, -14 },
/* 0xA2 */ { 2388, 8, 16, 9, 0, -11 },
/* 0xA3 */ { 2404, 7, 13, 10, 1, -12 },
/* 0xA4 */ { 2416, 7, 6, 10, 2, -8 },
/* 0xA5 */ { 2422, 10, 14, 11, 1, -13 },
/* 0xA6 */ { 2440, 2, 17, 5, 2, -12 },
/* 0xA7 */ { 2445, 9, 17, 10, 1, -12 },
/* 0xA8 */ { 2465, 9, 15, 12, 1, -14 },
/* 0xA9 */ { 2482, 14, 13, 14, 1, -12 },
/* 0xAA */ { 2505, 11, 13, 13, 1, -12 },
/* 0xAB */ { 2523, 7, 6, 9, 1, -7 },
/* 0xAC */ { 2529, 9, 5, 11, 2, -5 },
/* 0xAD */ { 2535, 0, 0, 0, 0, 0 },
/* 0xAE */ { 2535, 14, 13, 14, 1, -12 },
/* 0xAF */ { 2558, 6, 15, 5, 0, -14 },
/* 0xB0 */ { 2570, 5, 5, 11, 3, -11 },
/* 0xB1 */ { 2574, 9, 11, 11, 1, -10 },
/* 0xB2 */ { 2587, 2, 13, 4, 1, -12 },
/* 0xB3 */ { 2591, 2, 13, 4, 1, -12 },
/* 0xB4 */ { 2595, 6, 12, 7, 1, -11 },
/* 0xB5 */ { 2604, 9, 13, 10, 1, -9 },
/* 0xB6 */ { 2619, 8, 16, 10, 2, -12 },
/* 0xB7 */ { 2635, 3, 1, 5, 1, -4 },
/* 0xB8 */ { 2636, 8, 12, 10, 1, -11 },
/* 0xB9 */ { 2648, 15, 13, 17, 1, -12 },
/* 0xBA */ { 2673, 8, 10, 9, 1, -9 },
/* 0xBB */ { 2683, 7, 6, 9, 1, -7 },
/* 0xBC */ { 2689, 4, 17, 4, 0, -12 },
/* 0xBD */ { 2698, 10, 13, 12, 1, -12 },
/* 0xBE */ { 2715, 8, 10, 9, 1, -9 },
/* 0xBF */ { 2725, 6, 12, 5, -1, -11 },
/* 0xC0 */ { 2734, 12, 13, 12, 0, -12 },
/* 0xC1 */ { 2754, 11, 13, 12, 1, -12 },
/* 0xC2 */ { 2772, 11, 13, 12, 1, -12 },
/* 0xC3 */ { 2790, 8, 13, 8, 1, -12 },
/* 0xC4 */ { 2803, 14, 16, 15, 1, -12 },
/* 0xC5 */ { 2831, 9, 13, 12, 1, -12 },
/* 0xC6 */ { 2846, 16, 13, 16, 0, -12 },
/* 0xC7 */ { 2872, 10, 13, 12, 1, -12 },
/* 0xC8 */ { 2889, 11, 13, 13, 1, -12 },
/* 0xC9 */ { 2907, 11, 16, 13, 1, -15 },
/* 0xCA */ { 2929, 10, 13, 11, 1, -12 },
/* 0xCB */ { 2946, 10, 13, 12, 1, -12 },
/* 0xCC */ { 2963, 13, 13, 15, 1, -12 },
/* 0xCD */ { 2985, 11, 13, 13, 1, -12 },
/* 0xCE */ { 3003, 13, 13, 14, 1, -12 },
/* 0xCF */ { 3025, 11, 13, 13, 1, -12 },
/* 0xD0 */ { 3043, 10, 13, 12, 1, -12 },
/* 0xD1 */ { 3060, 11, 13, 13, 1, -12 },
/* 0xD2 */ { 3078, 9, 13, 11, 1, -12 },
/* 0xD3 */ { 3093, 10, 13, 11, 1, -12 },
/* 0xD4 */ { 3110, 14, 13, 15, 1, -12 },
/* 0xD5 */ { 3133, 10, 13, 12, 1, -12 },
/* 0xD6 */ { 3150, 13, 15, 13, 1, -12 },
/* 0xD7 */ { 3175, 9, 13, 11, 1, -12 },
/* 0xD8 */ { 3190, 13, 13, 15, 1, -12 },
/* 0xD9 */ { 3212, 15, 15, 15, 1, -12 },
/* 0xDA */ { 3241, 13, 13, 15, 2, -12 },
/* 0xDB */ { 3263, 14, 13, 16, 1, -12 },
/* 0xDC */ { 3286, 11, 13, 12, 1, -12 },
/* 0xDD */ { 3304, 11, 13, 13, 1, -12 },
/* 0xDE */ { 3322, 17, 13, 18, 1, -12 },
/* 0xDF */ { 3350, 10, 13, 12, 1, -12 },
/* 0xE0 */ { 3367, 9, 10, 10, 1, -9 },
/* 0xE1 */ { 3379, 8, 14, 10, 1, -13 },
/* 0xE2 */ { 3393, 7, 10, 9, 1, -9 },
/* 0xE3 */ { 3402, 5, 10, 7, 1, -9 },
/* 0xE4 */ { 3409, 11, 12, 10, 0, -9 },
/* 0xE5 */ { 3426, 8, 10, 10, 1, -9 },
/* 0xE6 */ { 3436, 12, 10, 14, 1, -9 },
/* 0xE7 */ { 3451, 7, 10, 9, 1, -9 },
/* 0xE8 */ { 3460, 8, 10, 10, 1, -9 },
/* 0xE9 */ { 3470, 8, 12, 10, 1, -11 },
/* 0xEA */ { 3482, 7, 10, 9, 1, -9 },
/* 0xEB */ { 3491, 7, 10, 8, 0, -9 },
/* 0xEC */ { 3500, 9, 10, 11, 1, -9 },
/* 0xED */ { 3512, 8, 10, 10, 1, -9 },
/* 0xEE */ { 3522, 8, 10, 10, 1, -9 },
/* 0xEF */ { 3532, 8, 10, 10, 1, -9 },
/* 0xF0 */ { 3542, 9, 13, 10, 1, -9 },
/* 0xF1 */ { 3557, 8, 10, 9, 1, -9 },
/* 0xF2 */ { 3567, 6, 10, 7, 1, -9 },
/* 0xF3 */ { 3575, 8, 14, 9, 0, -9 },
/* 0xF4 */ { 3589, 14, 15, 15, 1, -11 },
/* 0xF5 */ { 3616, 7, 10, 9, 1, -9 },
/* 0xF6 */ { 3625, 10, 12, 10, 1, -9 },
/* 0xF7 */ { 3640, 7, 10, 9, 1, -9 },
/* 0xF8 */ { 3649, 10, 10, 12, 1, -9 },
/* 0xF9 */ { 3662, 12, 12, 13, 1, -9 },
/* 0xFA */ { 3680, 9, 10, 12, 2, -9 },
/* 0xFB */ { 3692, 10, 10, 12, 1, -9 },
/* 0xFC */ { 3705, 8, 10, 9, 1, -9 },
/* 0xFD */ { 3715, 8, 10, 9, 1, -9 },
/* 0xFE */ { 3725, 12, 10, 13, 1, -9 },
/* 0xFF */ { 3740, 8, 10, 10, 1, -9 },
};
const GFXfont FreeSans9pt_Win1251 PROGMEM = {
(uint8_t*)FreeSans9pt_Win1251Bitmaps,
(GFXglyph*)FreeSans9pt_Win1251Glyphs,
0x01, 0xFF, 21
};
@@ -0,0 +1,527 @@
// trunk-ignore-all(clang-format)
#pragma once
/* PROPERTIES
FONT_NAME FreeSans9pt_Win1252
*/
const uint8_t FreeSans9pt_Win1252Bitmaps[] PROGMEM = {
/* 0x01 */ 0x07, 0x00, 0x0A, 0x00, 0x24, 0x00, 0x48, 0x01, 0x10, 0x04, 0x40, 0x10, 0xFF, 0x20, 0x02, 0x81, 0xFD, 0x00, 0x06, 0x07, 0xF4, 0x08, 0x24, 0x0F, 0x88, 0x11, 0x0F, 0xDC, 0x00,
/* 0x02 */ 0x3F, 0x70, 0x81, 0x11, 0x03, 0xE4, 0x08, 0x28, 0x1F, 0xD0, 0x00, 0x60, 0x7F, 0x20, 0x02, 0x43, 0xFC, 0x44, 0x00, 0x44, 0x00, 0x48, 0x00, 0x90, 0x00, 0xA0, 0x01, 0xC0, 0x00,
/* 0x03 */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x00, 0x28, 0x00, 0x31, 0x8C, 0x63, 0x18, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x06, 0x20, 0x8C, 0x3E, 0x14, 0x00, 0x44, 0x01, 0x06, 0x0C, 0x03, 0xE0,
/* 0x04 */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x00, 0x28, 0x82, 0x30, 0x88, 0x62, 0x08, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x06, 0x3F, 0x8C, 0x3E, 0x14, 0x00, 0x44, 0x01, 0x06, 0x0C, 0x03, 0xE0,
/* 0x05 */ 0x0B, 0x10, 0x14, 0xA8, 0x12, 0x50, 0x29, 0x42, 0x24, 0xA5, 0x32, 0x95, 0x5A, 0x09, 0x48, 0x09, 0x24, 0x01, 0x10, 0x01, 0x48, 0x02, 0xA4, 0x02, 0x42, 0x04, 0x01, 0x98, 0x00, 0x60,
/* 0x06 */ 0x00, 0x80, 0x22, 0x80, 0x65, 0x00, 0xBE, 0xE1, 0x82, 0x4E, 0x03, 0x24, 0x04, 0x28, 0x06, 0x30, 0x12, 0x20, 0x3C, 0xA0, 0xC3, 0xFE, 0x80, 0x4D, 0x00, 0xA6, 0x01, 0x80, 0x00,
/* 0x07 */
/* 0x08 */ 0x00, 0xF8, 0x00, 0x82, 0x00, 0x80, 0x83, 0xE0, 0x41, 0x10, 0x21, 0x04, 0x1B, 0x00, 0x03, 0x00, 0x01, 0x80, 0x00, 0xE0, 0x00, 0x4F, 0xE1, 0xC0, 0x0F, 0x02, 0x00, 0x03, 0x01, 0x00, 0x09, 0x88, 0x0C, 0x0C,
/* 0x09 */ 0x00, 0xF8, 0x00, 0x82, 0x00, 0x80, 0x83, 0xE0, 0x41, 0x10, 0x21, 0x04, 0x1B, 0x00, 0x03, 0x00, 0x01, 0x80, 0x00, 0xE0, 0x00, 0x4F, 0xE1, 0xC0, 0x0F, 0x00,
/* 0x0A */
/* 0x0B */ 0x1C, 0x1C, 0x31, 0xB1, 0x90, 0x50, 0x50, 0x10, 0x18, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x02, 0x80, 0x02, 0x40, 0x01, 0x10, 0x01, 0x04, 0x01, 0x01, 0x01, 0x00, 0x41, 0x00, 0x11, 0x00, 0x07, 0x00, 0x01, 0x00,
/* 0x0C */ 0x06, 0x00, 0x0A, 0x00, 0x12, 0x00, 0x32, 0x01, 0x84, 0x04, 0x10, 0x08, 0x98, 0x1C, 0x18, 0x40, 0x48, 0x82, 0x11, 0xF0, 0x74, 0x02, 0x18, 0x70, 0x2F, 0x9F, 0x80,
/* 0x0D */
/* 0x0E */ 0x01, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x3E, 0x00, 0x82, 0x02, 0x82, 0x06, 0x04, 0x10, 0x04, 0x20, 0x08, 0x40, 0x10, 0xFF, 0x22, 0x00, 0x29, 0xFF, 0x3F, 0x8F, 0xDF, 0x9F, 0x01, 0xC0,
/* 0x0F */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x00, 0x28, 0x82, 0x36, 0x03, 0x60, 0x00, 0xCC, 0x19, 0xA4, 0x4B, 0x00, 0x06, 0x8E, 0x2B, 0x22, 0x66, 0x7C, 0xCC, 0x71, 0x98, 0x03, 0x00,
/* 0x10 */ 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1E, 0x00, 0x54, 0x00, 0xA8, 0x01, 0x50, 0x02, 0xA0, 0x05, 0x20, 0x32, 0x61, 0xC4, 0x74, 0x49, 0x10, 0x6C, 0x00, 0xD8, 0x01, 0x10, 0x00,
/* 0x11 */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x40, 0x29, 0x00, 0x31, 0x84, 0x63, 0x18, 0xC0, 0x00, 0x80, 0x15, 0x03, 0x7E, 0x02, 0xFA, 0x04, 0xE4, 0x18, 0x84, 0x00, 0x06, 0x0C, 0x03, 0xE0,
/* 0x12 */ 0x02, 0x08, 0x01, 0x08, 0x40, 0x10, 0xC0, 0x08, 0xC0, 0x60, 0x80, 0x28, 0x04, 0x12, 0x4C, 0x10, 0x80, 0x08, 0x23, 0x0E, 0x08, 0xC4, 0x82, 0x04, 0x20, 0x83, 0x09, 0x82, 0x47, 0x01, 0x1C, 0x01, 0x30, 0x00, 0xE0, 0x00, 0x00,
/* 0x13 */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x00, 0x28, 0x00, 0x31, 0x08, 0x65, 0x28, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x06, 0x3F, 0x8C, 0x3E, 0x14, 0x00, 0x44, 0x01, 0x06, 0x0C, 0x03, 0xE0,
/* 0x14 */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x22, 0x29, 0x83, 0x30, 0x00, 0x65, 0x14, 0xD3, 0x4D, 0xBA, 0xEB, 0x38, 0xE6, 0x00, 0x0A, 0x00, 0x24, 0x38, 0x44, 0x01, 0x07, 0x1C, 0x01, 0xC0,
/* 0x15 */ 0x07, 0xC0, 0x30, 0x18, 0x80, 0x32, 0x00, 0xF8, 0x01, 0xF1, 0x09, 0xA5, 0x28, 0x40, 0x01, 0x80, 0x03, 0x00, 0x06, 0x3F, 0x8C, 0x3E, 0x14, 0x00, 0x44, 0x01, 0x06, 0x0C, 0x03, 0xE0,
/* 0x16 */ 0x0C, 0x00, 0xC0, 0x1C, 0x03, 0x80, 0xF8, 0xBB, 0x36, 0xC7, 0x99, 0xF3, 0xFE, 0x3F, 0xC3, 0xF0, 0x7E, 0x0E, 0xC1, 0x8E, 0xE0, 0x20,
/* 0x17 */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x00, 0x28, 0x00, 0x10, 0x01, 0x20, 0x1D, 0x44, 0x42, 0x84, 0x85, 0x00, 0x86, 0x00, 0xC4, 0x00, 0x44, 0x7C, 0x44, 0x00, 0x06, 0x0C, 0x03, 0xE0,
/* 0x18 */ 0x01, 0xE0, 0x00, 0x84, 0x00, 0x40, 0x80, 0x20, 0x10, 0x08, 0x24, 0x02, 0x41, 0x00, 0x86, 0x03, 0x12, 0x03, 0xB4, 0x03, 0x52, 0x81, 0x23, 0x80, 0x70, 0xA0, 0x14, 0x28, 0x05, 0x0A, 0x01, 0x42, 0x80, 0x50,
/* 0x19 */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x00, 0x28, 0x00, 0x33, 0x18, 0x60, 0x00, 0xDC, 0xE1, 0xB9, 0xC3, 0x7B, 0xC6, 0x63, 0x0A, 0x00, 0x24, 0xF0, 0x44, 0x01, 0x06, 0x0C, 0x03, 0xE0,
/* 0x1A */ 0xFF, 0xFC, 0x00, 0x63, 0xE3, 0x31, 0x99, 0x04, 0xC8, 0x66, 0x06, 0x30, 0x61, 0x82, 0x0C, 0x10, 0x60, 0x03, 0x04, 0x18, 0x00, 0xFF, 0xFC,
/* 0x1B */ 0x07, 0xF0, 0x06, 0x0C, 0x04, 0x01, 0x04, 0x00, 0x44, 0x22, 0x12, 0x2A, 0x89, 0x00, 0x04, 0x80, 0x02, 0x44, 0x11, 0x01, 0xF0, 0x04, 0x01, 0x0D, 0x01, 0x6A, 0x41, 0x2C, 0x00, 0x05, 0xC0, 0x0E, 0x18, 0x18,
/* 0x1C */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0xC0, 0x2A, 0x00, 0x33, 0x00, 0x66, 0x00, 0xCC, 0x39, 0x80, 0x83, 0x00, 0x06, 0x00, 0x8C, 0x3E, 0x14, 0x00, 0x44, 0x01, 0x06, 0x0C, 0x03, 0xE0,
/* 0x1D */ 0x07, 0xC0, 0x30, 0x60, 0x80, 0x22, 0x70, 0x28, 0x00, 0x31, 0x80, 0x63, 0x18, 0xC0, 0x31, 0x80, 0x03, 0x00, 0x06, 0x60, 0x0D, 0x33, 0x12, 0x10, 0x48, 0x21, 0x23, 0x8C, 0x00,
/* 0x1E */ 0x03, 0x00, 0x07, 0x9E, 0x07, 0x00, 0x86, 0x00, 0x27, 0xC0, 0x0F, 0xC0, 0x07, 0x8C, 0x62, 0x06, 0x31, 0x20, 0x00, 0x90, 0x00, 0x48, 0x00, 0x24, 0x3E, 0x11, 0x00, 0x10, 0x40, 0x10, 0x18, 0x30, 0x03, 0xE0,
/* 0x1F */ 0x18, 0x02, 0x80, 0x4C, 0x16, 0x41, 0x24, 0x3C, 0x88, 0x6E, 0x65, 0xF2, 0x78, 0x46, 0x88, 0xCF, 0x18, 0x02, 0x80, 0x8C, 0x60, 0x70,
/* ' ' 0x20 */
/* '!' 0x21 */ 0xFF, 0xFF, 0xF0, 0xC0,
/* '"' 0x22 */ 0xDE, 0xF7, 0x20,
/* '#' 0x23 */ 0x09, 0x86, 0x41, 0x91, 0xFF, 0x13, 0x04, 0xC3, 0x20, 0xC8, 0xFF, 0x89, 0x82, 0x61, 0x90,
/* '$' 0x24 */ 0x10, 0x1F, 0x14, 0xDA, 0x3D, 0x1E, 0x83, 0x40, 0x78, 0x17, 0x08, 0xF4, 0x7A, 0x35, 0x33, 0xF0, 0x40, 0x20,
/* '%' 0x25 */ 0x38, 0x10, 0xEC, 0x20, 0xC6, 0x20, 0xC6, 0x40, 0xC6, 0x40, 0x6C, 0x80, 0x39, 0x00, 0x01, 0x3C, 0x02, 0x77, 0x02, 0x63, 0x04, 0x63, 0x04, 0x77, 0x08, 0x3C,
/* '&' 0x26 */ 0x0E, 0x0C, 0xC3, 0x30, 0xCC, 0x1E, 0x03, 0x03, 0xC1, 0x9B, 0xC2, 0xF0, 0xEC, 0x19, 0x8F, 0x3C, 0x40,
/* ''' 0x27 */ 0xFE,
/* '(' 0x28 */ 0x13, 0x26, 0x6C, 0xCC, 0xCC, 0xC4, 0x66, 0x23, 0x10,
/* ')' 0x29 */ 0x8C, 0x46, 0x63, 0x33, 0x33, 0x32, 0x66, 0x4C, 0x80,
/* '*' 0x2A */ 0x25, 0x7E, 0xA5, 0x00,
/* '+' 0x2B */ 0x30, 0xC3, 0x3F, 0x30, 0xC3, 0x0C,
/* ',' 0x2C */ 0xD6,
/* '-' 0x2D */ 0xF0,
/* '.' 0x2E */ 0xC0,
/* '/' 0x2F */ 0x08, 0x44, 0x21, 0x10, 0x84, 0x42, 0x11, 0x08, 0x00,
/* '0' 0x30 */ 0x3C, 0x66, 0x42, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x42, 0x66, 0x3C,
/* '1' 0x31 */ 0x11, 0x3F, 0x33, 0x33, 0x33, 0x33, 0x30,
/* '2' 0x32 */ 0x3E, 0x31, 0xB0, 0x78, 0x30, 0x18, 0x1C, 0x1C, 0x1C, 0x18, 0x18, 0x10, 0x08, 0x07, 0xF8,
/* '3' 0x33 */ 0x3C, 0x66, 0xC3, 0xC3, 0x03, 0x06, 0x1C, 0x07, 0x03, 0xC3, 0xC3, 0x66, 0x3C,
/* '4' 0x34 */ 0x0C, 0x18, 0x71, 0x62, 0xC9, 0xA3, 0x46, 0xFE, 0x18, 0x30, 0x60, 0xC0,
/* '5' 0x35 */ 0x7F, 0x20, 0x10, 0x08, 0x08, 0x07, 0xF3, 0x8C, 0x03, 0x01, 0x80, 0xF0, 0x6C, 0x63, 0xE0,
/* '6' 0x36 */ 0x1E, 0x31, 0x98, 0x78, 0x0C, 0x06, 0xF3, 0x8D, 0x83, 0xC1, 0xE0, 0xD0, 0x6C, 0x63, 0xE0,
/* '7' 0x37 */ 0xFF, 0x03, 0x02, 0x06, 0x04, 0x0C, 0x08, 0x18, 0x18, 0x18, 0x10, 0x30, 0x30,
/* '8' 0x38 */ 0x3E, 0x31, 0xB0, 0x78, 0x3C, 0x1B, 0x18, 0xF8, 0xC6, 0xC1, 0xE0, 0xF0, 0x6C, 0x63, 0xE0,
/* '9' 0x39 */ 0x3C, 0x66, 0xC2, 0xC3, 0xC3, 0xC3, 0x67, 0x3B, 0x03, 0x03, 0xC2, 0x66, 0x3C,
/* ':' 0x3A */ 0xC0, 0x00, 0x30,
/* ';' 0x3B */ 0xC0, 0x00, 0x00, 0x64, 0xA0,
/* '<' 0x3C */ 0x00, 0x81, 0xC7, 0x8E, 0x0C, 0x07, 0x80, 0x70, 0x0E, 0x01, 0x80,
/* '=' 0x3D */ 0xFF, 0x80, 0x00, 0x1F, 0xF0,
/* '>' 0x3E */ 0xE0, 0x1C, 0x03, 0x80, 0x30, 0x70, 0xE3, 0x81, 0x00,
/* '?' 0x3F */ 0x3E, 0x31, 0xB0, 0x78, 0x30, 0x18, 0x18, 0x38, 0x18, 0x18, 0x0C, 0x00, 0x00, 0x01, 0x80,
/* '@' 0x40 */ 0x03, 0xF0, 0x06, 0x0E, 0x06, 0x01, 0x86, 0x00, 0x66, 0x1D, 0xBB, 0x31, 0xCF, 0x18, 0xC7, 0x98, 0x63, 0xCC, 0x31, 0xE6, 0x11, 0xB3, 0x99, 0xCC, 0xF7, 0x86, 0x00, 0x01, 0x80, 0x00, 0x70, 0x40, 0x0F, 0xE0,
/* 'A' 0x41 */ 0x06, 0x00, 0xF0, 0x0F, 0x00, 0x90, 0x19, 0x81, 0x98, 0x10, 0x83, 0x0C, 0x3F, 0xC2, 0x04, 0x60, 0x66, 0x06, 0xC0, 0x30,
/* 'B' 0x42 */ 0xFF, 0x18, 0x33, 0x03, 0x60, 0x6C, 0x0D, 0x83, 0x3F, 0xC6, 0x06, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x6F, 0xF8,
/* 'C' 0x43 */ 0x1F, 0x86, 0x19, 0x81, 0xA0, 0x3C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x68, 0x0D, 0x83, 0x18, 0x61, 0xF0,
/* 'D' 0x44 */ 0xFF, 0x18, 0x33, 0x03, 0x60, 0x3C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x03, 0x60, 0xCF, 0xF0,
/* 'E' 0x45 */ 0xFF, 0xE0, 0x30, 0x18, 0x0C, 0x06, 0x03, 0xFD, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0F, 0xF8,
/* 'F' 0x46 */ 0xFF, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xFE, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0,
/* 'G' 0x47 */ 0x0F, 0x83, 0x0E, 0x60, 0x66, 0x03, 0xC0, 0x0C, 0x00, 0xC1, 0xFC, 0x03, 0xC0, 0x36, 0x03, 0x60, 0x73, 0x0F, 0x0F, 0x10,
/* 'H' 0x48 */ 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xFF, 0xFE, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x06,
/* 'I' 0x49 */ 0xFF, 0xFF, 0xFF, 0xC0,
/* 'J' 0x4A */ 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x07, 0x8F, 0x1E, 0x27, 0x80,
/* 'K' 0x4B */ 0xC0, 0xF0, 0x6C, 0x33, 0x18, 0xCC, 0x37, 0x0F, 0xC3, 0x98, 0xC3, 0x30, 0xCC, 0x1B, 0x03, 0xC0, 0xC0,
/* 'L' 0x4C */ 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xFF,
/* 'M' 0x4D */ 0xE0, 0x3F, 0x01, 0xFC, 0x1F, 0xE0, 0xFD, 0x05, 0xEC, 0x6F, 0x63, 0x79, 0x13, 0xCD, 0x9E, 0x6C, 0xF1, 0x47, 0x8E, 0x3C, 0x71, 0x80,
/* 'N' 0x4E */ 0xE0, 0x7C, 0x0F, 0xC1, 0xE8, 0x3D, 0x87, 0x98, 0xF1, 0x1E, 0x33, 0xC3, 0x78, 0x6F, 0x07, 0xE0, 0x7C, 0x0E,
/* 'O' 0x4F */ 0x0F, 0x81, 0x83, 0x18, 0x0C, 0xC0, 0x6C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1B, 0x01, 0x98, 0x0C, 0x60, 0xC0, 0xF8, 0x00,
/* 'P' 0x50 */ 0xFF, 0x30, 0x6C, 0x0F, 0x03, 0xC0, 0xF0, 0x6F, 0xF3, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x00,
/* 'Q' 0x51 */ 0x0F, 0x81, 0x83, 0x18, 0x0C, 0xC0, 0x6C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1B, 0x01, 0x98, 0x6C, 0x60, 0xC0, 0xFB, 0x00, 0x08,
/* 'R' 0x52 */ 0xFF, 0x8C, 0x0E, 0xC0, 0x6C, 0x06, 0xC0, 0x6C, 0x0C, 0xFF, 0x8C, 0x0E, 0xC0, 0x6C, 0x06, 0xC0, 0x6C, 0x06, 0xC0, 0x70,
/* 'S' 0x53 */ 0x3F, 0x18, 0x6C, 0x0F, 0x03, 0xC0, 0x1E, 0x01, 0xF0, 0x0E, 0x00, 0xF0, 0x3C, 0x0D, 0x86, 0x3F, 0x00,
/* 'T' 0x54 */ 0xFF, 0x86, 0x03, 0x01, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x80, 0xC0,
/* 'U' 0x55 */ 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xB0, 0x61, 0xF0,
/* 'V' 0x56 */ 0xC0, 0x6C, 0x0D, 0x81, 0x10, 0x63, 0x0C, 0x61, 0x04, 0x60, 0xCC, 0x19, 0x01, 0x60, 0x3C, 0x07, 0x00, 0x60,
/* 'W' 0x57 */ 0xC1, 0x81, 0x61, 0xC3, 0x61, 0xC3, 0x61, 0x43, 0x62, 0x62, 0x22, 0x66, 0x32, 0x26, 0x36, 0x26, 0x14, 0x34, 0x14, 0x34, 0x1C, 0x1C, 0x18, 0x1C, 0x08, 0x18,
/* 'X' 0x58 */ 0xC0, 0xD8, 0x66, 0x18, 0xCC, 0x1E, 0x07, 0x00, 0xC0, 0x78, 0x32, 0x0C, 0xC6, 0x1B, 0x07, 0xC0, 0xC0,
/* 'Y' 0x59 */ 0xC0, 0x36, 0x06, 0x30, 0xC3, 0x0C, 0x19, 0x81, 0xD8, 0x0F, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00,
/* 'Z' 0x5A */ 0xFF, 0xC0, 0x60, 0x30, 0x0C, 0x06, 0x03, 0x01, 0xC0, 0x60, 0x30, 0x18, 0x06, 0x03, 0x00, 0xFF, 0xC0,
/* '[' 0x5B */ 0xFB, 0x6D, 0xB6, 0xDB, 0x6D, 0xB6, 0xE0,
/* '\' 0x5C */ 0x84, 0x10, 0x84, 0x10, 0x84, 0x10, 0x84, 0x10, 0x80,
/* ']' 0x5D */ 0xED, 0xB6, 0xDB, 0x6D, 0xB6, 0xDB, 0xE0,
/* '^' 0x5E */ 0x30, 0x60, 0xA2, 0x44, 0xD8, 0xA1, 0x80,
/* '_' 0x5F */ 0xFF, 0xC0,
/* '`' 0x60 */ 0xC6, 0x30,
/* 'a' 0x61 */ 0x7E, 0x71, 0xB0, 0xC0, 0x60, 0xF3, 0xDB, 0x0D, 0x86, 0xC7, 0x3D, 0xC0,
/* 'b' 0x62 */ 0xC0, 0x60, 0x30, 0x1B, 0xCE, 0x36, 0x0F, 0x07, 0x83, 0xC1, 0xE0, 0xF0, 0x7C, 0x6D, 0xE0,
/* 'c' 0x63 */ 0x3C, 0x66, 0xC3, 0xC0, 0xC0, 0xC0, 0xC0, 0xC3, 0x66, 0x3C,
/* 'd' 0x64 */ 0x03, 0x03, 0x03, 0x3B, 0x67, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x67, 0x3B,
/* 'e' 0x65 */ 0x3C, 0x66, 0xC3, 0xC3, 0xFF, 0xC0, 0xC0, 0xC3, 0x66, 0x3C,
/* 'f' 0x66 */ 0x36, 0x6F, 0x66, 0x66, 0x66, 0x66, 0x60,
/* 'g' 0x67 */ 0x3B, 0x67, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x67, 0x3B, 0x03, 0x03, 0xC6, 0x7C,
/* 'h' 0x68 */ 0xC0, 0xC0, 0xC0, 0xDE, 0xE3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,
/* 'i' 0x69 */ 0xC3, 0xFF, 0xFF, 0xC0,
/* 'j' 0x6A */ 0x30, 0x03, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xE0,
/* 'k' 0x6B */ 0xC0, 0xC0, 0xC0, 0xC2, 0xC4, 0xCC, 0xD8, 0xF8, 0xEC, 0xC4, 0xC6, 0xC3, 0xC3,
/* 'l' 0x6C */ 0xFF, 0xFF, 0xFF, 0xC0,
/* 'm' 0x6D */ 0xDE, 0xF7, 0x1C, 0xF0, 0xC7, 0x86, 0x3C, 0x31, 0xE1, 0x8F, 0x0C, 0x78, 0x63, 0xC3, 0x1E, 0x18, 0xC0,
/* 'n' 0x6E */ 0xDE, 0xE3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,
/* 'o' 0x6F */ 0x3C, 0x66, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x66, 0x3C,
/* 'p' 0x70 */ 0xDE, 0x71, 0xB0, 0x78, 0x3C, 0x1E, 0x0F, 0x07, 0x83, 0xE3, 0x6F, 0x30, 0x18, 0x0C, 0x00,
/* 'q' 0x71 */ 0x3B, 0x67, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x67, 0x3B, 0x03, 0x03, 0x03,
/* 'r' 0x72 */ 0xDF, 0x31, 0x8C, 0x63, 0x18, 0xC6, 0x00,
/* 's' 0x73 */ 0x3E, 0xE3, 0xC0, 0xC0, 0xE0, 0x3C, 0x07, 0xC3, 0xE3, 0x7E,
/* 't' 0x74 */ 0x66, 0xF6, 0x66, 0x66, 0x66, 0x67,
/* 'u' 0x75 */ 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0x7B,
/* 'v' 0x76 */ 0xC1, 0xA0, 0x98, 0xCC, 0x42, 0x21, 0xB0, 0xD0, 0x28, 0x1C, 0x0C, 0x00,
/* 'w' 0x77 */ 0xC6, 0x1E, 0x38, 0x91, 0xC4, 0xCA, 0x66, 0xD3, 0x16, 0xD0, 0xA6, 0x87, 0x1C, 0x38, 0xC0, 0xC6, 0x00,
/* 'x' 0x78 */ 0x87, 0x89, 0xB1, 0xC3, 0x07, 0x1E, 0x26, 0xC5, 0x0C,
/* 'y' 0x79 */ 0xC1, 0x43, 0x63, 0x62, 0x26, 0x36, 0x34, 0x1C, 0x1C, 0x18, 0x18, 0x18, 0x10, 0x60,
/* 'z' 0x7A */ 0xFE, 0x0C, 0x30, 0xC1, 0x86, 0x18, 0x20, 0xC1, 0xFC,
/* '{' 0x7B */ 0x36, 0x66, 0x66, 0x6E, 0xCE, 0x66, 0x66, 0x66, 0x30,
/* '|' 0x7C */ 0xFF, 0xFF, 0xFF, 0xFF, 0xC0,
/* '}' 0x7D */ 0xC6, 0x66, 0x66, 0x67, 0x37, 0x66, 0x66, 0x66, 0xC0,
/* '~' 0x7E */ 0x61, 0x24, 0x38,
/* 0x7F */
/* 0x80 */ 0x07, 0xC6, 0x13, 0x00, 0xC0, 0x60, 0x3F, 0xE6, 0x03, 0xFC, 0x60, 0x0C, 0x03, 0x00, 0x61, 0x07, 0xC0,
/* 0x81 */
/* 0x82 */ 0xDC,
/* 0x83 */ 0x19, 0x8C, 0xF3, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x18, 0xC6, 0xE0,
/* 0x84 */ 0xDA, 0x76,
/* 0x85 */ 0xCC, 0xC0,
/* 0x86 */ 0x18, 0x18, 0x18, 0x18, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
/* 0x87 */ 0x18, 0x18, 0x18, 0x18, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xFF, 0x18, 0x18, 0x18, 0x18,
/* 0x88 */ 0x72, 0xA2,
/* 0x89 */ 0x70, 0x80, 0x22, 0x20, 0x08, 0x90, 0x02, 0x24, 0x00, 0x72, 0x00, 0x00, 0x80, 0x00, 0x40, 0x00, 0x10, 0x00, 0x09, 0xC7, 0x84, 0x8B, 0x31, 0x22, 0x84, 0x88, 0xB3, 0x21, 0xC7, 0x80,
/* 0x8A */ 0x1B, 0x03, 0x80, 0x00, 0xFC, 0x61, 0xB0, 0x3C, 0x0F, 0x00, 0x78, 0x07, 0xC0, 0x38, 0x03, 0xC0, 0xF0, 0x36, 0x18, 0xFC,
/* 0x8B */ 0x69,
/* 0x8C */ 0x1E, 0xFE, 0x43, 0x81, 0x83, 0x06, 0x06, 0x0C, 0x0C, 0x18, 0x18, 0x30, 0x3F, 0xE0, 0x60, 0xC0, 0xC1, 0x81, 0x81, 0x83, 0x01, 0x8E, 0x01, 0xEF, 0xE0,
/* 0x8D */
/* 0x8E */ 0x1B, 0x03, 0x80, 0x03, 0xFF, 0x01, 0x80, 0xC0, 0x30, 0x18, 0x0C, 0x07, 0x01, 0x80, 0xC0, 0x60, 0x18, 0x0C, 0x03, 0xFF,
/* 0x8F */
/* 0x90 */
/* 0x91 */ 0x6B,
/* 0x92 */ 0xD6,
/* 0x93 */ 0x4C, 0xA5, 0xB0,
/* 0x94 */ 0xDA, 0x53, 0x20,
/* 0x95 */ 0x6F, 0xFF, 0x60,
/* 0x96 */ 0xFE,
/* 0x97 */ 0xFF, 0xFF,
/* 0x98 */ 0x4D, 0xC0,
/* 0x99 */ 0xFC, 0xE1, 0xCC, 0x38, 0x73, 0x0E, 0x1C, 0xC3, 0x8F, 0x30, 0xD2, 0xCC, 0x34, 0xB3, 0x0D, 0x6C, 0xC3, 0x53, 0x30, 0xCC, 0xCC, 0x33, 0x30,
/* 0x9A */ 0x24, 0x3C, 0x18, 0x7E, 0xE3, 0xC0, 0xC0, 0x60, 0x3C, 0x07, 0xC3, 0xE3, 0x7E,
/* 0x9B */ 0x96,
/* 0x9C */ 0x3C, 0xF8, 0xCF, 0x1B, 0x0C, 0x1E, 0x18, 0x3C, 0x3F, 0xF8, 0x60, 0x30, 0xC0, 0x61, 0x83, 0x67, 0x8C, 0x79, 0xF0,
/* 0x9D */
/* 0x9E */ 0x48, 0xF0, 0xC7, 0xF0, 0x61, 0x86, 0x0C, 0x30, 0xC1, 0x06, 0x0F, 0xE0,
/* 0x9F */ 0x19, 0x80, 0x00, 0xC0, 0x36, 0x06, 0x30, 0xC3, 0x0C, 0x19, 0x81, 0xD8, 0x0F, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60,
/* 0xA0 */
/* 0xA1 */ 0xCF, 0xFF, 0xFF, 0xC0,
/* 0xA2 */ 0x08, 0x04, 0x0F, 0x8D, 0x6C, 0x9E, 0x43, 0x21, 0x90, 0xC8, 0x64, 0xDA, 0xC7, 0xC0, 0x80, 0x40,
/* 0xA3 */ 0x1F, 0x0C, 0x66, 0x0D, 0x83, 0x60, 0x0C, 0x0F, 0xC0, 0x60, 0x18, 0x06, 0x03, 0x01, 0xF1, 0x43, 0xC0,
/* 0xA4 */ 0xFF, 0xDF, 0x1E, 0x3E, 0xFF, 0xC0,
/* 0xA5 */ 0xC3, 0x42, 0x42, 0x24, 0x24, 0x3C, 0x18, 0x7E, 0x18, 0x7E, 0x18, 0x18, 0x18,
/* 0xA6 */ 0xFF, 0xFC, 0x0F, 0xFF, 0xC0,
/* 0xA7 */ 0x0C, 0x09, 0x0C, 0xC6, 0x63, 0x81, 0xE3, 0x19, 0x87, 0xE1, 0xB8, 0xC6, 0x41, 0xC0, 0x73, 0x19, 0x8C, 0x66, 0x1E, 0x00,
/* 0xA8 */ 0xCC,
/* 0xA9 */ 0x0F, 0xC0, 0x61, 0x87, 0x03, 0x9B, 0xC6, 0xD9, 0x8F, 0x60, 0x3D, 0x00, 0xF4, 0x03, 0xD8, 0x0D, 0xE6, 0x67, 0xF3, 0x86, 0x18, 0x0F, 0xC0,
/* 0xAA */ 0x74, 0x8D, 0xA9, 0x7C, 0x1F,
/* 0xAB */ 0x22, 0xCF, 0x26, 0x46, 0x64, 0x40,
/* 0xAC */ 0xFF, 0x80, 0xC0, 0x60, 0x30, 0x18,
/* 0xAD */
/* 0xAE */ 0x0F, 0xC0, 0x61, 0x87, 0x03, 0x9F, 0xE6, 0xD0, 0x8F, 0x42, 0x3D, 0xF0, 0xF4, 0x23, 0xD0, 0x8D, 0xC2, 0x67, 0x0B, 0x86, 0x18, 0x0F, 0xC0,
/* 0xAF */ 0xF8,
/* 0xB0 */ 0x74, 0x63, 0x17, 0x00,
/* 0xB1 */ 0x0C, 0x06, 0x03, 0x07, 0xE0, 0xC0, 0x60, 0x30, 0x18, 0x00, 0x00, 0x3F, 0xE0,
/* 0xB2 */ 0x7B, 0x30, 0xC3, 0x11, 0x84, 0x3F,
/* 0xB3 */ 0x7D, 0x8C, 0x18, 0xC0, 0x60, 0xF1, 0xBE,
/* 0xB4 */ 0x36, 0xC0,
/* 0xB5 */ 0xC3, 0x61, 0xB0, 0xD8, 0x6C, 0x36, 0x1B, 0x0D, 0x86, 0xE7, 0x7D, 0xF0, 0x18, 0x0C, 0x00,
/* 0xB6 */ 0x3F, 0x7E, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0x72, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
/* 0xB7 */ 0xE0,
/* 0xB8 */ 0x21, 0xC7, 0xE0,
/* 0xB9 */ 0x3D, 0xB6, 0xD8,
/* 0xBA */ 0x74, 0x63, 0x18, 0xB8, 0x1F,
/* 0xBB */ 0x89, 0x98, 0x99, 0x3C, 0xD1, 0x00,
/* 0xBC */ 0x20, 0x43, 0x81, 0x06, 0x08, 0x18, 0x20, 0x61, 0x01, 0x84, 0x06, 0x21, 0x80, 0x86, 0x04, 0x78, 0x32, 0x60, 0x87, 0xC4, 0x06, 0x10, 0x18,
/* 0xBD */ 0x20, 0x43, 0x81, 0x06, 0x08, 0x18, 0x20, 0x61, 0x01, 0x8D, 0xE6, 0x2C, 0xC1, 0x03, 0x0C, 0x0C, 0x20, 0x41, 0x86, 0x0C, 0x30, 0x20, 0xFC,
/* 0xBE */ 0x78, 0x11, 0x98, 0x40, 0x31, 0x00, 0x82, 0x00, 0xC8, 0x01, 0x90, 0x33, 0x43, 0x3D, 0x06, 0x02, 0x3C, 0x08, 0x98, 0x10, 0xF8, 0x40, 0x61, 0x00, 0xC0,
/* 0xBF */ 0x0C, 0x00, 0x00, 0x01, 0x80, 0xC0, 0xC0, 0xE0, 0xC0, 0xC0, 0x60, 0xF0, 0x6C, 0x63, 0xE0,
/* 0xC0 */ 0x18, 0x03, 0x00, 0x00, 0x30, 0x1E, 0x07, 0x81, 0x20, 0xCC, 0x33, 0x0F, 0xC6, 0x19, 0x86, 0x40, 0xB0, 0x30,
/* 0xC1 */ 0x06, 0x03, 0x00, 0x00, 0x30, 0x1E, 0x07, 0x81, 0x20, 0xCC, 0x33, 0x0F, 0xC6, 0x19, 0x86, 0x40, 0xB0, 0x30,
/* 0xC2 */ 0x0C, 0x04, 0x80, 0x00, 0x30, 0x1E, 0x07, 0x81, 0x20, 0xCC, 0x33, 0x0F, 0xC6, 0x19, 0x86, 0x40, 0xB0, 0x30,
/* 0xC3 */ 0x19, 0x09, 0x80, 0x00, 0x30, 0x1E, 0x07, 0x81, 0x20, 0xCC, 0x33, 0x0F, 0xC6, 0x19, 0x86, 0x40, 0xB0, 0x30,
/* 0xC4 */ 0x33, 0x00, 0x00, 0xC0, 0x78, 0x1E, 0x04, 0x83, 0x30, 0xCC, 0x33, 0x1F, 0xE6, 0x19, 0x02, 0xC0, 0xF0, 0x30,
/* 0xC5 */ 0x0C, 0x04, 0x81, 0x20, 0x30, 0x1E, 0x07, 0x81, 0x20, 0xCC, 0x33, 0x0F, 0xC6, 0x19, 0x86, 0x40, 0xB0, 0x30,
/* 0xC6 */ 0x07, 0xFF, 0x04, 0xC0, 0x0C, 0xC0, 0x08, 0xC0, 0x18, 0xC0, 0x18, 0xC0, 0x30, 0xFF, 0x30, 0xC0, 0x3F, 0xC0, 0x60, 0xC0, 0x60, 0xC0, 0xC0, 0xC0, 0xC0, 0xFF,
/* 0xC7 */ 0x1F, 0x06, 0x19, 0x83, 0xA0, 0x3C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x68, 0x0D, 0x83, 0x18, 0xE1, 0xF0, 0x08, 0x01, 0xC0, 0x18, 0x0E, 0x00,
/* 0xC8 */ 0x18, 0x06, 0x00, 0x1F, 0xFC, 0x06, 0x03, 0x01, 0x80, 0xFF, 0x60, 0x30, 0x18, 0x0C, 0x07, 0xFC,
/* 0xC9 */ 0x0C, 0x0C, 0x00, 0x1F, 0xFC, 0x06, 0x03, 0x01, 0x80, 0xFF, 0x60, 0x30, 0x18, 0x0C, 0x07, 0xFC,
/* 0xCA */ 0x1C, 0x1B, 0x00, 0x1F, 0xFC, 0x06, 0x03, 0x01, 0x80, 0xFF, 0x60, 0x30, 0x18, 0x0C, 0x07, 0xFC,
/* 0xCB */ 0x33, 0x00, 0x3F, 0xF8, 0x0C, 0x06, 0x03, 0x01, 0xFE, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x07, 0xFC,
/* 0xCC */ 0xCC, 0x36, 0xDB, 0x6D, 0xB6, 0xD8,
/* 0xCD */ 0x78, 0x36, 0xDB, 0x6D, 0xB6, 0xC0,
/* 0xCE */ 0x76, 0xC0, 0x63, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x18,
/* 0xCF */ 0xCC, 0x03, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC0,
/* 0xD0 */ 0x7F, 0x0C, 0x31, 0x83, 0x30, 0x36, 0x06, 0xC0, 0xFE, 0x1B, 0x03, 0x60, 0x6C, 0x0D, 0x83, 0x30, 0xE7, 0xF0,
/* 0xD1 */ 0x19, 0x02, 0xC3, 0x81, 0xF0, 0x3F, 0x07, 0xA0, 0xF6, 0x1E, 0x63, 0xC4, 0x78, 0xCF, 0x0D, 0xE1, 0xBC, 0x1F, 0x81, 0xC0,
/* 0xD2 */ 0x0C, 0x00, 0x60, 0x00, 0x00, 0xF0, 0x39, 0xC6, 0x06, 0x60, 0x6C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x36, 0x06, 0x60, 0x63, 0x9C, 0x0F, 0x00,
/* 0xD3 */ 0x03, 0x00, 0x60, 0x00, 0x00, 0xF0, 0x39, 0xC6, 0x06, 0x60, 0x6C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x36, 0x06, 0x60, 0x63, 0x9C, 0x0F, 0x00,
/* 0xD4 */ 0x0F, 0x01, 0x98, 0x00, 0x00, 0xF0, 0x39, 0xC6, 0x06, 0x60, 0x6C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x36, 0x06, 0x60, 0x63, 0x9C, 0x0F, 0x00,
/* 0xD5 */ 0x1C, 0x81, 0x38, 0x00, 0x00, 0xF0, 0x39, 0xC6, 0x06, 0x60, 0x6C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x36, 0x06, 0x60, 0x63, 0x9C, 0x0F, 0x00,
/* 0xD6 */ 0x19, 0x81, 0x98, 0x00, 0x00, 0xF0, 0x39, 0xC6, 0x06, 0x60, 0x6C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x36, 0x06, 0x60, 0x63, 0x9C, 0x0F, 0x00,
/* 0xD7 */ 0x83, 0x89, 0xA1, 0x83, 0x89, 0xA1, 0x80,
/* 0xD8 */ 0x0F, 0xD9, 0x83, 0x18, 0x1C, 0xC1, 0xEC, 0x19, 0xE0, 0x8F, 0x08, 0x78, 0x83, 0xC8, 0x1B, 0x81, 0x98, 0x0C, 0xE0, 0xC8, 0xF8, 0x00,
/* 0xD9 */ 0x0C, 0x00, 0xC3, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x36, 0x0C, 0x3E, 0x00,
/* 0xDA */ 0x06, 0x01, 0x83, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x36, 0x0C, 0x3E, 0x00,
/* 0xDB */ 0x0E, 0x03, 0x63, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x36, 0x0C, 0x3E, 0x00,
/* 0xDC */ 0x1B, 0x00, 0x03, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x36, 0x0C, 0x3E, 0x00,
/* 0xDD */ 0x03, 0x0C, 0x63, 0x60, 0x63, 0x0C, 0x30, 0xC1, 0x98, 0x1D, 0x80, 0xF0, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60,
/* 0xDE */ 0xC0, 0x30, 0x0F, 0xF3, 0x06, 0xC0, 0xF0, 0x3C, 0x0F, 0x06, 0xFF, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x00,
/* 0xDF */ 0x3C, 0x33, 0x30, 0xD8, 0x6C, 0x36, 0x33, 0x39, 0x86, 0xC1, 0xE0, 0xF0, 0x78, 0x6D, 0xE0,
/* 0xE0 */ 0x60, 0x18, 0x06, 0x0F, 0xCE, 0x36, 0x18, 0x0C, 0x1E, 0x7B, 0x61, 0xB0, 0xD8, 0xE7, 0xB8,
/* 0xE1 */ 0x0C, 0x04, 0x04, 0x0F, 0xCE, 0x36, 0x18, 0x0C, 0x1E, 0x7B, 0x61, 0xB0, 0xD8, 0xE7, 0xB8,
/* 0xE2 */ 0x10, 0x14, 0x1B, 0x0F, 0xCE, 0x36, 0x18, 0x0C, 0x1E, 0x7B, 0x61, 0xB0, 0xD8, 0xE7, 0xB8,
/* 0xE3 */ 0x24, 0x2E, 0x00, 0x0F, 0xCE, 0x36, 0x18, 0x0C, 0x1E, 0x7B, 0x61, 0xB0, 0xD8, 0xE7, 0xB8,
/* 0xE4 */ 0x66, 0x00, 0x1F, 0x9C, 0x6C, 0x30, 0x18, 0x3C, 0xF6, 0xC3, 0x61, 0xB1, 0xCF, 0x70,
/* 0xE5 */ 0x1C, 0x1B, 0x0D, 0x83, 0x87, 0xE7, 0x1B, 0x0C, 0x06, 0x0F, 0x3D, 0xB0, 0xD8, 0x6C, 0x73, 0xDC,
/* 0xE6 */ 0x7E, 0xF9, 0xC7, 0x1B, 0x0C, 0x18, 0x18, 0x33, 0xFF, 0xFC, 0x60, 0x30, 0xC0, 0x61, 0x83, 0xC7, 0x8C, 0xF1, 0xF0,
/* 0xE7 */ 0x3C, 0x66, 0xC3, 0xC0, 0xC0, 0xC0, 0xC0, 0xC3, 0x66, 0x3C, 0x10, 0x1C, 0x0C, 0x38,
/* 0xE8 */ 0x60, 0x30, 0x18, 0x3C, 0x66, 0xC3, 0xC3, 0xFF, 0xC0, 0xC0, 0xC3, 0x66, 0x3C,
/* 0xE9 */ 0x0C, 0x08, 0x18, 0x3C, 0x66, 0xC3, 0xC3, 0xFF, 0xC0, 0xC0, 0xC3, 0x66, 0x3C,
/* 0xEA */ 0x10, 0x28, 0x6C, 0x3C, 0x66, 0xC3, 0xC3, 0xFF, 0xC0, 0xC0, 0xC3, 0x66, 0x3C,
/* 0xEB */ 0x66, 0x00, 0x3C, 0x66, 0xC3, 0xC3, 0xFF, 0xC0, 0xC0, 0xC3, 0x66, 0x3C,
/* 0xEC */ 0xCC, 0xB6, 0xDB, 0x6D, 0xB6,
/* 0xED */ 0x7A, 0x6D, 0xB6, 0xDB, 0x6C,
/* 0xEE */ 0x6E, 0x96, 0x66, 0x66, 0x66, 0x66, 0x60,
/* 0xEF */ 0xCC, 0x03, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C,
/* 0xF0 */ 0x34, 0x0C, 0x16, 0x03, 0x3F, 0x67, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x66, 0x3C,
/* 0xF1 */ 0x24, 0x5C, 0x00, 0xDE, 0xE3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,
/* 0xF2 */ 0x30, 0x18, 0x00, 0x3C, 0x66, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x66, 0x3C,
/* 0xF3 */ 0x0C, 0x18, 0x00, 0x3C, 0x66, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x66, 0x3C,
/* 0xF4 */ 0x18, 0x24, 0x00, 0x3C, 0x66, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x66, 0x3C,
/* 0xF5 */ 0x34, 0x2C, 0x00, 0x3C, 0x66, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x66, 0x3C,
/* 0xF6 */ 0x66, 0x00, 0x3C, 0x66, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x66, 0x3C,
/* 0xF7 */ 0x18, 0x00, 0x00, 0x1F, 0xF0, 0x00, 0x00, 0x00, 0x30,
/* 0xF8 */ 0x3D, 0x66, 0xC7, 0xCB, 0xCB, 0xD3, 0xD3, 0xE3, 0x66, 0xBC,
/* 0xF9 */ 0x60, 0x30, 0x18, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0x7B,
/* 0xFA */ 0x06, 0x0C, 0x18, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0x7B,
/* 0xFB */ 0x3C, 0x66, 0x00, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0x7B,
/* 0xFC */ 0x66, 0x00, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0x7B,
/* 0xFD */ 0x06, 0x04, 0x08, 0xC1, 0x43, 0x63, 0x62, 0x26, 0x36, 0x34, 0x1C, 0x1C, 0x18, 0x18, 0x18, 0x10, 0x60,
/* 0xFE */ 0xC0, 0x60, 0x30, 0x1B, 0xCE, 0x36, 0x0F, 0x07, 0x83, 0xC1, 0xE0, 0xF0, 0x7C, 0x6D, 0xE6, 0x03, 0x01, 0x80,
/* 0xFF */ 0x33, 0x00, 0xC1, 0x43, 0x63, 0x62, 0x26, 0x36, 0x34, 0x1C, 0x1C, 0x18, 0x18, 0x18, 0x10, 0x60,
};
const GFXglyph FreeSans9pt_Win1252Glyphs[] PROGMEM = {
/* 0x01 */ { 0, 15, 15, 17, 1, -13 },
/* 0x02 */ { 29, 15, 15, 17, 1, -13 },
/* 0x03 */ { 58, 15, 16, 17, 1, -14 },
/* 0x04 */ { 88, 15, 16, 17, 1, -14 },
/* 0x05 */ { 118, 16, 15, 18, 1, -13 },
/* 0x06 */ { 148, 15, 15, 17, 1, -13 },
/* 0x07 */ { 177, 0, 0, 8, 0, 0 },
/* 0x08 */ { 177, 17, 16, 19, 1, -14 },
/* 0x09 */ { 211, 17, 12, 19, 1, -12 },
/* 0x0A */ { 237, 0, 0, 8, 0, 0 },
/* 0x0B */ { 237, 17, 16, 19, 1, -14 },
/* 0x0C */ { 271, 15, 14, 17, 1, -12 },
/* 0x0D */ { 298, 0, 0, 8, 0, 0 },
/* 0x0E */ { 298, 15, 16, 17, 1, -14 },
/* 0x0F */ { 328, 15, 15, 17, 1, -13 },
/* 0x10 */ { 357, 15, 15, 17, 1, -13 },
/* 0x11 */ { 386, 15, 16, 17, 1, -14 },
/* 0x12 */ { 416, 17, 17, 19, 1, -15 },
/* 0x13 */ { 453, 15, 16, 17, 1, -14 },
/* 0x14 */ { 483, 15, 16, 17, 1, -14 },
/* 0x15 */ { 513, 15, 16, 17, 1, -14 },
/* 0x16 */ { 543, 11, 16, 13, 1, -14 },
/* 0x17 */ { 565, 15, 16, 17, 1, -14 },
/* 0x18 */ { 595, 18, 15, 20, 1, -13 },
/* 0x19 */ { 629, 15, 16, 17, 1, -14 },
/* 0x1A */ { 659, 13, 14, 15, 1, -12 },
/* 0x1B */ { 682, 17, 16, 19, 1, -14 },
/* 0x1C */ { 716, 15, 16, 17, 1, -14 },
/* 0x1D */ { 746, 15, 15, 17, 1, -13 },
/* 0x1E */ { 775, 17, 16, 19, 1, -14 },
/* 0x1F */ { 809, 11, 16, 13, 1, -14 },
/* ' ' 0x20 */ { 831, 0, 0, 5, 0, 0 },
/* '!' 0x21 */ { 831, 2, 13, 6, 2, -12 },
/* '"' 0x22 */ { 835, 5, 4, 6, 1, -12 },
/* '#' 0x23 */ { 838, 10, 12, 10, 0, -11 },
/* '$' 0x24 */ { 853, 9, 16, 10, 1, -13 },
/* '%' 0x25 */ { 871, 16, 13, 16, 1, -12 },
/* '&' 0x26 */ { 897, 10, 13, 12, 1, -12 },
/* ''' 0x27 */ { 914, 2, 4, 4, 1, -12 },
/* '(' 0x28 */ { 915, 4, 17, 6, 1, -12 },
/* ')' 0x29 */ { 924, 4, 17, 6, 1, -12 },
/* '*' 0x2A */ { 933, 5, 5, 7, 1, -12 },
/* '+' 0x2B */ { 937, 6, 8, 11, 3, -7 },
/* ',' 0x2C */ { 943, 2, 4, 5, 2, 0 },
/* '-' 0x2D */ { 944, 4, 1, 6, 1, -4 },
/* '.' 0x2E */ { 945, 2, 1, 5, 1, 0 },
/* '/' 0x2F */ { 946, 5, 13, 5, 0, -12 },
/* '0' 0x30 */ { 955, 8, 13, 10, 1, -12 },
/* '1' 0x31 */ { 968, 4, 13, 10, 3, -12 },
/* '2' 0x32 */ { 975, 9, 13, 10, 1, -12 },
/* '3' 0x33 */ { 990, 8, 13, 10, 1, -12 },
/* '4' 0x34 */ { 1003, 7, 13, 10, 2, -12 },
/* '5' 0x35 */ { 1015, 9, 13, 10, 1, -12 },
/* '6' 0x36 */ { 1030, 9, 13, 10, 1, -12 },
/* '7' 0x37 */ { 1045, 8, 13, 10, 0, -12 },
/* '8' 0x38 */ { 1058, 9, 13, 10, 1, -12 },
/* '9' 0x39 */ { 1073, 8, 13, 10, 1, -12 },
/* ':' 0x3A */ { 1086, 2, 10, 5, 1, -9 },
/* ';' 0x3B */ { 1089, 3, 12, 5, 1, -8 },
/* '<' 0x3C */ { 1094, 9, 9, 11, 1, -8 },
/* '=' 0x3D */ { 1105, 9, 4, 11, 1, -5 },
/* '>' 0x3E */ { 1110, 9, 8, 11, 1, -7 },
/* '?' 0x3F */ { 1119, 9, 13, 10, 1, -12 },
/* '@' 0x40 */ { 1134, 17, 16, 18, 1, -12 },
/* 'A' 0x41 */ { 1168, 12, 13, 12, 0, -12 },
/* 'B' 0x42 */ { 1188, 11, 13, 12, 1, -12 },
/* 'C' 0x43 */ { 1206, 11, 13, 13, 1, -12 },
/* 'D' 0x44 */ { 1224, 11, 13, 13, 1, -12 },
/* 'E' 0x45 */ { 1242, 9, 13, 11, 1, -12 },
/* 'F' 0x46 */ { 1257, 8, 13, 11, 1, -12 },
/* 'G' 0x47 */ { 1270, 12, 13, 14, 1, -12 },
/* 'H' 0x48 */ { 1290, 11, 13, 13, 1, -12 },
/* 'I' 0x49 */ { 1308, 2, 13, 5, 2, -12 },
/* 'J' 0x4A */ { 1312, 7, 13, 10, 1, -12 },
/* 'K' 0x4B */ { 1324, 10, 13, 12, 1, -12 },
/* 'L' 0x4C */ { 1341, 8, 13, 10, 1, -12 },
/* 'M' 0x4D */ { 1354, 13, 13, 15, 1, -12 },
/* 'N' 0x4E */ { 1376, 11, 13, 13, 1, -12 },
/* 'O' 0x4F */ { 1394, 13, 13, 14, 1, -12 },
/* 'P' 0x50 */ { 1416, 10, 13, 12, 1, -12 },
/* 'Q' 0x51 */ { 1433, 13, 14, 14, 1, -12 },
/* 'R' 0x52 */ { 1456, 12, 13, 13, 1, -12 },
/* 'S' 0x53 */ { 1476, 10, 13, 12, 1, -12 },
/* 'T' 0x54 */ { 1493, 9, 13, 11, 1, -12 },
/* 'U' 0x55 */ { 1508, 11, 13, 13, 1, -12 },
/* 'V' 0x56 */ { 1526, 11, 13, 11, 0, -12 },
/* 'W' 0x57 */ { 1544, 16, 13, 17, 0, -12 },
/* 'X' 0x58 */ { 1570, 10, 13, 12, 1, -12 },
/* 'Y' 0x59 */ { 1587, 12, 13, 12, 0, -12 },
/* 'Z' 0x5A */ { 1607, 10, 13, 11, 1, -12 },
/* '[' 0x5B */ { 1624, 3, 17, 5, 1, -12 },
/* '\' 0x5C */ { 1631, 5, 13, 5, 0, -12 },
/* ']' 0x5D */ { 1640, 3, 17, 5, 0, -12 },
/* '^' 0x5E */ { 1647, 7, 7, 8, 1, -12 },
/* '_' 0x5F */ { 1654, 10, 1, 10, 0, 3 },
/* '`' 0x60 */ { 1656, 4, 3, 5, 0, -12 },
/* 'a' 0x61 */ { 1658, 9, 10, 10, 1, -9 },
/* 'b' 0x62 */ { 1670, 9, 13, 10, 1, -12 },
/* 'c' 0x63 */ { 1685, 8, 10, 9, 1, -9 },
/* 'd' 0x64 */ { 1695, 8, 13, 10, 1, -12 },
/* 'e' 0x65 */ { 1708, 8, 10, 10, 1, -9 },
/* 'f' 0x66 */ { 1718, 4, 13, 5, 1, -12 },
/* 'g' 0x67 */ { 1725, 8, 14, 10, 1, -9 },
/* 'h' 0x68 */ { 1739, 8, 13, 10, 1, -12 },
/* 'i' 0x69 */ { 1752, 2, 13, 4, 1, -12 },
/* 'j' 0x6A */ { 1756, 4, 17, 4, 0, -12 },
/* 'k' 0x6B */ { 1765, 8, 13, 9, 1, -12 },
/* 'l' 0x6C */ { 1778, 2, 13, 4, 1, -12 },
/* 'm' 0x6D */ { 1782, 13, 10, 15, 1, -9 },
/* 'n' 0x6E */ { 1799, 8, 10, 10, 1, -9 },
/* 'o' 0x6F */ { 1809, 8, 10, 10, 1, -9 },
/* 'p' 0x70 */ { 1819, 9, 13, 10, 1, -9 },
/* 'q' 0x71 */ { 1834, 8, 13, 10, 1, -9 },
/* 'r' 0x72 */ { 1847, 5, 10, 6, 1, -9 },
/* 's' 0x73 */ { 1854, 8, 10, 9, 1, -9 },
/* 't' 0x74 */ { 1864, 4, 12, 5, 1, -11 },
/* 'u' 0x75 */ { 1870, 8, 10, 10, 1, -9 },
/* 'v' 0x76 */ { 1880, 9, 10, 9, 0, -9 },
/* 'w' 0x77 */ { 1892, 13, 10, 13, 0, -9 },
/* 'x' 0x78 */ { 1909, 7, 10, 9, 1, -9 },
/* 'y' 0x79 */ { 1918, 8, 14, 9, 0, -9 },
/* 'z' 0x7A */ { 1932, 7, 10, 9, 1, -9 },
/* '{' 0x7B */ { 1941, 4, 17, 6, 1, -12 },
/* '|' 0x7C */ { 1950, 2, 17, 4, 2, -12 },
/* '}' 0x7D */ { 1955, 4, 17, 6, 1, -12 },
/* '~' 0x7E */ { 1964, 7, 3, 9, 1, -7 },
/* 0x7F */ { 1967, 0, 0, 0, 0, 0 },
/* 0x80 */ { 1967, 10, 13, 12, 1, -12 },
/* 0x81 */ { 1984, 0, 0, 8, 0, 0 },
/* 0x82 */ { 1984, 2, 3, 5, 1, 0 },
/* 0x83 */ { 1985, 5, 17, 5, 0, -12 },
/* 0x84 */ { 1996, 5, 3, 7, 1, 0 },
/* 0x85 */ { 1998, 10, 1, 12, 1, 0 },
/* 0x86 */ { 2000, 8, 16, 10, 1, -12 },
/* 0x87 */ { 2016, 8, 16, 10, 1, -12 },
/* 0x88 */ { 2032, 5, 3, 6, 0, -12 },
/* 0x89 */ { 2034, 18, 13, 18, 0, -12 },
/* 0x8A */ { 2064, 10, 16, 12, 1, -15 },
/* 0x8B */ { 2084, 2, 4, 4, 1, -6 },
/* 0x8C */ { 2085, 15, 13, 18, 1, -12 },
/* 0x8D */ { 2110, 0, 0, 8, 0, 0 },
/* 0x8E */ { 2110, 10, 16, 11, 1, -15 },
/* 0x8F */ { 2130, 0, 0, 8, 0, 0 },
/* 0x90 */ { 2130, 0, 0, 8, 0, 0 },
/* 0x91 */ { 2130, 2, 4, 4, 2, -12 },
/* 0x92 */ { 2131, 2, 4, 4, 1, -12 },
/* 0x93 */ { 2132, 5, 4, 7, 2, -12 },
/* 0x94 */ { 2135, 5, 4, 7, 1, -12 },
/* 0x95 */ { 2138, 4, 5, 7, 1, -8 },
/* 0x96 */ { 2141, 7, 1, 9, 1, -4 },
/* 0x97 */ { 2142, 16, 1, 18, 1, -4 },
/* 0x98 */ { 2144, 5, 2, 6, 0, -12 },
/* 0x99 */ { 2146, 18, 10, 18, 1, -13 },
/* 0x9A */ { 2169, 8, 13, 9, 1, -12 },
/* 0x9B */ { 2182, 2, 4, 5, 2, -6 },
/* 0x9C */ { 2183, 15, 10, 17, 1, -9 },
/* 0x9D */ { 2202, 0, 0, 8, 0, 0 },
/* 0x9E */ { 2202, 7, 13, 9, 1, -12 },
/* 0x9F */ { 2214, 12, 14, 12, 0, -13 },
/* 0xA0 */ { 2235, 0, 0, 5, 0, 0 },
/* 0xA1 */ { 2235, 2, 13, 6, 2, -8 },
/* 0xA2 */ { 2239, 9, 14, 10, 1, -11 },
/* 0xA3 */ { 2255, 10, 13, 10, 0, -12 },
/* 0xA4 */ { 2272, 7, 6, 10, 2, -8 },
/* 0xA5 */ { 2278, 8, 13, 10, 1, -12 },
/* 0xA6 */ { 2291, 2, 17, 5, 2, -12 },
/* 0xA7 */ { 2296, 9, 17, 10, 1, -12 },
/* 0xA8 */ { 2316, 6, 1, 6, 0, -11 },
/* 0xA9 */ { 2317, 14, 13, 14, 1, -12 },
/* 0xAA */ { 2340, 5, 8, 7, 1, -12 },
/* 0xAB */ { 2345, 7, 6, 9, 1, -7 },
/* 0xAC */ { 2351, 9, 5, 11, 2, -5 },
/* 0xAD */ { 2357, 0, 0, 0, 0, 0 },
/* 0xAE */ { 2357, 14, 13, 14, 1, -12 },
/* 0xAF */ { 2380, 5, 1, 6, 0, -12 },
/* 0xB0 */ { 2381, 5, 5, 11, 3, -11 },
/* 0xB1 */ { 2385, 9, 11, 11, 1, -10 },
/* 0xB2 */ { 2398, 6, 8, 6, 1, -13 },
/* 0xB3 */ { 2404, 7, 8, 6, 0, -13 },
/* 0xB4 */ { 2411, 4, 3, 6, 2, -12 },
/* 0xB5 */ { 2413, 9, 13, 10, 1, -9 },
/* 0xB6 */ { 2428, 8, 16, 10, 2, -12 },
/* 0xB7 */ { 2444, 3, 1, 5, 1, -4 },
/* 0xB8 */ { 2445, 5, 4, 6, 1, 1 },
/* 0xB9 */ { 2448, 3, 7, 6, 2, -13 },
/* 0xBA */ { 2451, 5, 8, 7, 1, -12 },
/* 0xBB */ { 2456, 7, 6, 9, 1, -7 },
/* 0xBC */ { 2462, 14, 13, 16, 2, -12 },
/* 0xBD */ { 2485, 14, 13, 16, 2, -12 },
/* 0xBE */ { 2508, 15, 13, 16, 1, -12 },
/* 0xBF */ { 2533, 9, 13, 10, 1, -8 },
/* 0xC0 */ { 2548, 10, 14, 12, 1, -13 },
/* 0xC1 */ { 2566, 10, 14, 12, 1, -13 },
/* 0xC2 */ { 2584, 10, 14, 12, 1, -13 },
/* 0xC3 */ { 2602, 10, 14, 12, 1, -13 },
/* 0xC4 */ { 2620, 10, 14, 12, 1, -13 },
/* 0xC5 */ { 2638, 10, 14, 12, 1, -13 },
/* 0xC6 */ { 2656, 16, 13, 18, 1, -12 },
/* 0xC7 */ { 2682, 11, 17, 13, 1, -12 },
/* 0xC8 */ { 2706, 9, 14, 11, 1, -13 },
/* 0xC9 */ { 2722, 9, 14, 11, 1, -13 },
/* 0xCA */ { 2738, 9, 14, 11, 1, -13 },
/* 0xCB */ { 2754, 9, 14, 11, 1, -13 },
/* 0xCC */ { 2770, 3, 15, 5, 1, -13 },
/* 0xCD */ { 2776, 3, 14, 5, 1, -13 },
/* 0xCE */ { 2782, 5, 14, 5, 0, -13 },
/* 0xCF */ { 2791, 6, 14, 5, 0, -13 },
/* 0xD0 */ { 2802, 11, 13, 13, 1, -12 },
/* 0xD1 */ { 2820, 11, 14, 13, 1, -13 },
/* 0xD2 */ { 2840, 12, 15, 13, 1, -14 },
/* 0xD3 */ { 2863, 12, 15, 13, 1, -14 },
/* 0xD4 */ { 2886, 12, 15, 13, 1, -14 },
/* 0xD5 */ { 2909, 12, 15, 13, 1, -14 },
/* 0xD6 */ { 2932, 12, 15, 13, 1, -14 },
/* 0xD7 */ { 2955, 7, 7, 11, 2, -7 },
/* 0xD8 */ { 2962, 13, 13, 14, 1, -12 },
/* 0xD9 */ { 2984, 11, 14, 13, 1, -13 },
/* 0xDA */ { 3004, 11, 14, 13, 1, -13 },
/* 0xDB */ { 3024, 11, 14, 13, 1, -13 },
/* 0xDC */ { 3044, 11, 14, 13, 1, -13 },
/* 0xDD */ { 3064, 12, 14, 12, 0, -13 },
/* 0xDE */ { 3085, 10, 13, 12, 1, -12 },
/* 0xDF */ { 3102, 9, 13, 11, 1, -12 },
/* 0xE0 */ { 3117, 9, 13, 10, 1, -12 },
/* 0xE1 */ { 3132, 9, 13, 10, 1, -12 },
/* 0xE2 */ { 3147, 9, 13, 10, 1, -12 },
/* 0xE3 */ { 3162, 9, 13, 10, 1, -12 },
/* 0xE4 */ { 3177, 9, 12, 10, 1, -11 },
/* 0xE5 */ { 3191, 9, 14, 10, 1, -13 },
/* 0xE6 */ { 3207, 15, 10, 16, 1, -9 },
/* 0xE7 */ { 3226, 8, 14, 9, 1, -9 },
/* 0xE8 */ { 3240, 8, 13, 10, 1, -12 },
/* 0xE9 */ { 3253, 8, 13, 10, 1, -12 },
/* 0xEA */ { 3266, 8, 13, 10, 1, -12 },
/* 0xEB */ { 3279, 8, 12, 10, 1, -11 },
/* 0xEC */ { 3291, 3, 13, 4, 0, -12 },
/* 0xED */ { 3296, 3, 13, 4, 1, -12 },
/* 0xEE */ { 3301, 4, 13, 5, 0, -12 },
/* 0xEF */ { 3308, 6, 12, 5, -1, -11 },
/* 0xF0 */ { 3317, 8, 13, 10, 1, -12 },
/* 0xF1 */ { 3330, 8, 13, 10, 1, -12 },
/* 0xF2 */ { 3343, 8, 13, 10, 1, -12 },
/* 0xF3 */ { 3356, 8, 13, 10, 1, -12 },
/* 0xF4 */ { 3369, 8, 13, 10, 1, -12 },
/* 0xF5 */ { 3382, 8, 13, 10, 1, -12 },
/* 0xF6 */ { 3395, 8, 12, 10, 1, -11 },
/* 0xF7 */ { 3407, 9, 8, 11, 1, -7 },
/* 0xF8 */ { 3416, 8, 10, 10, 1, -9 },
/* 0xF9 */ { 3426, 8, 13, 10, 1, -12 },
/* 0xFA */ { 3439, 8, 13, 10, 1, -12 },
/* 0xFB */ { 3452, 8, 13, 10, 1, -12 },
/* 0xFC */ { 3465, 8, 12, 10, 1, -11 },
/* 0xFD */ { 3477, 8, 17, 9, 0, -12 },
/* 0xFE */ { 3494, 9, 16, 10, 1, -12 },
/* 0xFF */ { 3512, 8, 16, 9, 0, -11 },
};
const GFXfont FreeSans9pt_Win1252 PROGMEM = {
(uint8_t*)FreeSans9pt_Win1252Bitmaps,
(GFXglyph*)FreeSans9pt_Win1252Glyphs,
0x01, 0xFF, 16
};
@@ -0,0 +1,4 @@
# NicheGraphics - Fonts
A common area to store fonts which might be reused by different Niche Graphics UIs
In future, we may want to separate these by library (AdafruitGFX, u8g2, etc)
@@ -0,0 +1,978 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./Applet.h"
#include "main.h"
#include "RTC.h"
using namespace NicheGraphics;
InkHUD::AppletFont InkHUD::Applet::fontLarge; // General purpose fonts. Set in nicheGraphics.h
InkHUD::AppletFont InkHUD::Applet::fontMedium;
InkHUD::AppletFont InkHUD::Applet::fontSmall;
constexpr float InkHUD::Applet::LOGO_ASPECT_RATIO; // Ratio of the Meshtastic logo
InkHUD::Applet::Applet() : GFX(0, 0)
{
// GFX is given initial dimensions of 0
// The width and height will change dynamically, depending on Applet tiling
// If you're getting a "divide by zero error", consider it an assert:
// WindowManager should be the only one controlling the rendering
inkhud = InkHUD::getInstance();
settings = &inkhud->persistence->settings;
latestMessage = &inkhud->persistence->latestMessage;
}
// Draw a single pixel
// The raw pixel output generated by AdafruitGFX drawing all passes through here
// Hand off to the applet's tile, which will in-turn pass to the renderer
void InkHUD::Applet::drawPixel(int16_t x, int16_t y, uint16_t color)
{
// Only render pixels if they fall within user's cropped region
if (x >= cropLeft && x < (cropLeft + cropWidth) && y >= cropTop && y < (cropTop + cropHeight))
assignedTile->handleAppletPixel(x, y, (Color)color);
}
// Link our applet to a tile
// This can only be called by Tile::assignApplet
// The tile determines the applets dimensions
// Pixel output is passed to tile during render()
void InkHUD::Applet::setTile(Tile *t)
{
// If we're setting (not clearing), make sure the link is "reciprocal"
if (t)
assert(t->getAssignedApplet() == this);
assignedTile = t;
}
// The tile to which our applet is assigned
InkHUD::Tile *InkHUD::Applet::getTile()
{
return assignedTile;
}
// Draw the applet
void InkHUD::Applet::render()
{
assert(assignedTile); // Ensure that we have a tile
assert(assignedTile->getAssignedApplet() == this); // Ensure that we have a reciprocal link with the tile
// WindowManager::update has now consumed the info about our update request
// Clear everything for future requests
wantRender = false; // Flag set by requestUpdate
wantAutoshow = false; // Flag set by requestAutoShow. May or may not have been honored.
wantUpdateType = Drivers::EInk::UpdateTypes::UNSPECIFIED; // Update type we wanted. May on may not have been granted.
updateDimensions();
resetDrawingSpace();
onRender(); // Derived applet's drawing takes place here
// Handle "Tile Highlighting"
// Some devices may use an auxiliary button to switch between tiles
// When this happens, we temporarily highlight the newly focused tile with a border
// If our tile is (or was) highlighted, to indicate a change in focus
if (Tile::highlightTarget == assignedTile) {
// Draw the highlight
if (!Tile::highlightShown) {
drawRect(0, 0, width(), height(), BLACK);
Tile::startHighlightTimeout();
Tile::highlightShown = true;
}
// Clear the highlight
else {
Tile::cancelHighlightTimeout();
Tile::highlightShown = false;
Tile::highlightTarget = nullptr;
}
}
}
// Does the applet want to render now?
// Checks whether the applet called requestUpdate recently, in response to an event
// Used by WindowManager::update
bool InkHUD::Applet::wantsToRender()
{
return wantRender;
}
// Does the applet want to be moved to foreground before next render, to show new data?
// User specifies whether an applet has permission for this, using the on-screen menu
// Used by WindowManager::update
bool InkHUD::Applet::wantsToAutoshow()
{
return wantAutoshow;
}
// Which technique would this applet prefer that the display use to change the image?
// Used by WindowManager::update
Drivers::EInk::UpdateTypes InkHUD::Applet::wantsUpdateType()
{
return wantUpdateType;
}
// Get size of the applet's drawing space from its tile
// Performed immediately before derived applet's drawing code runs
void InkHUD::Applet::updateDimensions()
{
assert(assignedTile);
WIDTH = assignedTile->getWidth();
HEIGHT = assignedTile->getHeight();
_width = WIDTH;
_height = HEIGHT;
}
// Ensure that render() always starts with the same initial drawing config
void InkHUD::Applet::resetDrawingSpace()
{
resetCrop(); // Allow pixel from any region of the applet to draw
setTextColor(BLACK); // Reset text params
setCursor(0, 0);
setTextWrap(false);
setFont(fontSmall);
}
// Tell InkHUD::Renderer that we want to render now
// Applets should internally listen for events they are interested in, via MeshModule, CallbackObserver etc
// When an applet decides it has heard something important, and wants to redraw, it calls this method
// Once the renderer has given other applets a chance to process whatever event we just detected,
// it will run Applet::render(), which may draw our applet to screen, if it is shown (foreground)
// We should requestUpdate even if our applet is currently background, because this might be changed by autoshow
void InkHUD::Applet::requestUpdate(Drivers::EInk::UpdateTypes type)
{
wantRender = true;
wantUpdateType = type;
inkhud->requestUpdate();
}
// Ask window manager to move this applet to foreground at start of next render
// Users select which applets have permission for this using the on-screen menu
void InkHUD::Applet::requestAutoshow()
{
wantAutoshow = true;
}
// Called when an Applet begins running
// Active applets are considered "enabled"
// They should now listen for events, and request their own updates
// They may also be unexpectedly renderer at any time by other InkHUD components
// Applets can be activated at run-time through the on-screen menu
void InkHUD::Applet::activate()
{
onActivate(); // Call derived class' handler
active = true;
}
// Called when an Applet stops running
// Inactive applets are considered "disabled"
// They should not listen for events, process data
// They will not be rendered
// Applets can be deactivated at run-time through the on-screen menu
void InkHUD::Applet::deactivate()
{
// If applet is still in foreground, run its onBackground code first
if (isForeground())
sendToBackground();
// If applet is active, run its onDeactivate code first
if (isActive())
onDeactivate(); // Derived class' handler
active = false;
}
// Is the Applet running?
// Note: active / inactive is not related to background / foreground
// An inactive applet is *fully* disabled
bool InkHUD::Applet::isActive()
{
return active;
}
// Begin showing the Applet
// It will be rendered immediately to whichever tile it is assigned
// The Renderer will also now honor requestUpdate() calls from this applet
void InkHUD::Applet::bringToForeground()
{
if (!foreground) {
foreground = true;
onForeground(); // Run derived applet class' handler
}
requestUpdate();
}
// Stop showing the Applet
// Calls to requestUpdate() will no longer be honored
// When one applet moves to background, another should move to foreground (exception: some system applets)
void InkHUD::Applet::sendToBackground()
{
if (foreground) {
foreground = false;
onBackground(); // Run derived applet class' handler
}
}
// Is the applet currently displayed on a tile
// Note: in some uncommon situations, an applet may be "foreground", and still not visible.
// This can occur when a system applet is covering the screen (e.g. during BLE pairing)
// This is not our applets responsibility to handle,
// as in those situations, the system applet will have "locked" rendering
bool InkHUD::Applet::isForeground()
{
return foreground;
}
// Limit drawing to a certain region of the applet
// Pixels outside this region will be discarded
void InkHUD::Applet::setCrop(int16_t left, int16_t top, uint16_t width, uint16_t height)
{
cropLeft = left;
cropTop = top;
cropWidth = width;
cropHeight = height;
}
// Allow drawing to any region of the Applet
// Reverses Applet::setCrop
void InkHUD::Applet::resetCrop()
{
setCrop(0, 0, width(), height());
}
// Convert relative width to absolute width, in px
// X(0) is 0
// X(0.5) is width() / 2
// X(1) is width()
uint16_t InkHUD::Applet::X(float f)
{
return width() * f;
}
// Convert relative hight to absolute height, in px
// Y(0) is 0
// Y(0.5) is height() / 2
// Y(1) is height()
uint16_t InkHUD::Applet::Y(float f)
{
return height() * f;
}
// Print text, specifying the position of any edge / corner of the textbox
void InkHUD::Applet::printAt(int16_t x, int16_t y, const char *text, HorizontalAlignment ha, VerticalAlignment va)
{
// We do still have to run getTextBounds to find the width
int16_t textOffsetX, textOffsetY;
uint16_t textWidth, textHeight;
getTextBounds(text, 0, 0, &textOffsetX, &textOffsetY, &textWidth, &textHeight);
int16_t cursorX = 0;
int16_t cursorY = 0;
switch (ha) {
case LEFT:
cursorX = x - textOffsetX;
break;
case CENTER:
cursorX = (x - textOffsetX) - (textWidth / 2);
break;
case RIGHT:
cursorX = (x - textOffsetX) - textWidth;
break;
}
// We're using a fixed line height, rather than sizing to text (getTextBounds)
switch (va) {
case TOP:
cursorY = y + currentFont.heightAboveCursor();
break;
case MIDDLE:
cursorY = (y + currentFont.heightAboveCursor()) - (currentFont.lineHeight() / 2);
break;
case BOTTOM:
cursorY = (y + currentFont.heightAboveCursor()) - currentFont.lineHeight();
break;
}
setCursor(cursorX, cursorY);
print(text);
}
// Print text, specifying the position of any edge / corner of the textbox
void InkHUD::Applet::printAt(int16_t x, int16_t y, std::string text, HorizontalAlignment ha, VerticalAlignment va)
{
printAt(x, y, text.c_str(), ha, va);
}
// Set which font should be used for subsequent drawing
// This is AppletFont type, which is a wrapper for AdafruitGFX font, with some precalculated dimension data
void InkHUD::Applet::setFont(AppletFont f)
{
GFX::setFont(f.gfxFont);
currentFont = f;
}
// Get which font is currently being used for drawing
// This is AppletFont type, which is a wrapper for AdafruitGFX font, with some precalculated dimension data
InkHUD::AppletFont InkHUD::Applet::getFont()
{
return currentFont;
}
// Parse any text which might have "special characters"
// Re-encodes UTF-8 characters to match our 8-bit encoded fonts
std::string InkHUD::Applet::parse(std::string text)
{
return getFont().decodeUTF8(text);
}
// Get the best version of a node's short name available to us
// Parses any non-ascii chars
// Swaps for last-four of node-id if the real short name is unknown or can't be rendered (emoji)
std::string InkHUD::Applet::parseShortName(meshtastic_NodeInfoLite *node)
{
assert(node);
// Use the true shortname if known, and doesn't contain any unprintable characters (emoji, etc.)
if (node->has_user) {
std::string parsed = parse(node->user.short_name);
if (isPrintable(parsed))
return parsed;
}
// Otherwise, use the "last 4" of node id
// - if short name unknown, or
// - if short name is emoji (we can't render this)
std::string nodeID = hexifyNodeNum(node->num);
return nodeID.substr(nodeID.length() - 4);
}
// Determine if all characters of a string are printable using the current font
bool InkHUD::Applet::isPrintable(std::string text)
{
// Scan for SUB (0x1A), which is the value assigned by AppletFont::applyEncoding if a unicode character is not handled
for (char &c : text) {
if (c == '\x1A')
return false;
}
// No unprintable characters found
return true;
}
// Gets rendered width of a string
// Wrapper for getTextBounds
uint16_t InkHUD::Applet::getTextWidth(const char *text)
{
// We do still have to run getTextBounds to find the width
int16_t textOffsetX, textOffsetY;
uint16_t textWidth, textHeight;
getTextBounds(text, 0, 0, &textOffsetX, &textOffsetY, &textWidth, &textHeight);
return textWidth;
}
// Gets rendered width of a string
// Wrapper for getTextBounds
uint16_t InkHUD::Applet::getTextWidth(std::string text)
{
return getTextWidth(text.c_str());
}
// Evaluate SNR and RSSI to qualify signal strength at one of four discrete levels
// Roughly comparable to values used by the iOS app;
// I didn't actually go look up the code, just fit to a sample graphic I have of the iOS signal indicator
InkHUD::Applet::SignalStrength InkHUD::Applet::getSignalStrength(float snr, float rssi)
{
uint8_t score = 0;
// Give a score for the SNR
if (snr > -17.5)
score += 2;
else if (snr > -26.0)
score += 1;
// Give a score for the RSSI
if (rssi > -115.0)
score += 3;
else if (rssi > -120.0)
score += 2;
else if (rssi > -126.0)
score += 1;
// Combine scores, then give a result
if (score >= 5)
return SIGNAL_GOOD;
else if (score >= 4)
return SIGNAL_FAIR;
else if (score > 0)
return SIGNAL_BAD;
else
return SIGNAL_NONE;
}
// Apply the standard "node id" formatting to a nodenum int: !0123abdc
std::string InkHUD::Applet::hexifyNodeNum(NodeNum num)
{
// Not found in nodeDB, show a hex nodeid instead
char nodeIdHex[10];
sprintf(nodeIdHex, "!%0x", num); // Convert to the typical "fixed width hex with !" format
return std::string(nodeIdHex);
}
// Print text, with word wrapping
// Avoids splitting words in half, instead moving the entire word to a new line wherever possible
void InkHUD::Applet::printWrapped(int16_t left, int16_t top, uint16_t width, std::string text)
{
// Place the AdafruitGFX cursor to suit our "top" coord
setCursor(left, top + getFont().heightAboveCursor());
// How wide a space character is
// Used when simulating print, for dimensioning
// Works around issues where getTextDimensions() doesn't account for whitespace
const uint8_t wSp = getFont().widthBetweenWords();
// Move through our text, character by character
uint16_t wordStart = 0;
for (uint16_t i = 0; i < text.length(); i++) {
// Found: end of word (split by spaces or newline)
// Also handles end of string
if (text[i] == ' ' || text[i] == '\n' || i == text.length() - 1) {
// Isolate this word
uint16_t wordLength = (i - wordStart) + 1; // Plus one. Imagine: "a". End - Start is 0, but length is 1
std::string word = text.substr(wordStart, wordLength);
wordStart = i + 1; // Next word starts *after* the space
// If word is terminated by a newline char, don't actually print it.
// We'll manually add a new line later
if (word.back() == '\n')
word.pop_back();
// Measure the word, in px
int16_t l, t;
uint16_t w, h;
getTextBounds(word.c_str(), getCursorX(), getCursorY(), &l, &t, &w, &h);
// Word is short
if (w < width) {
// Word fits on current line
if ((l + w + wSp) < left + width)
print(word.c_str());
// Word doesn't fit on current line
else {
setCursor(left, getCursorY() + getFont().lineHeight()); // Newline
print(word.c_str());
}
}
// Word is really long
// (wider than applet)
else {
// Horribly inefficient:
// Rather than working directly with the glyph sizes,
// we're going to run everything through getTextBounds as a c-string of length 1
// This is because AdafruitGFX has special internal handling for their legacy 6x8 font,
// which would be a pain to add manually here.
// These super-long strings probably don't come up often so we can maybe tolerate this.
// Todo: rewrite making use of AdafruitGFX native text wrapping
char cstr[] = {0, 0};
int16_t l, t;
uint16_t w, h;
for (uint16_t c = 0; c < word.length(); c++) {
// Shove next char into a c string
cstr[0] = word[c];
getTextBounds(cstr, getCursorX(), getCursorY(), &l, &t, &w, &h);
// Manual newline, if next character will spill beyond screen edge
if ((l + w) > left + width)
setCursor(left, getCursorY() + getFont().lineHeight());
// Print next character
print(word[c]);
}
}
}
// If word was terminated by a newline char, manually add the new line now
if (text[i] == '\n') {
setCursor(left, getCursorY() + getFont().lineHeight()); // Manual newline
wordStart = i + 1; // New word begins after the newline. Otherwise print will add an *extra* line
}
}
}
// Simulate running printWrapped, to determine how tall the block of text will be.
// This is a wasteful way of handling things. Maybe some way to optimize in future?
uint32_t InkHUD::Applet::getWrappedTextHeight(int16_t left, uint16_t width, std::string text)
{
// Cache the current crop region
int16_t cL = cropLeft;
int16_t cT = cropTop;
uint16_t cW = cropWidth;
uint16_t cH = cropHeight;
setCrop(-1, -1, 0, 0); // Set crop to temporarily discard all pixels
printWrapped(left, 0, width, text); // Simulate only - no pixels drawn
// Restore previous crop region
cropLeft = cL;
cropTop = cT;
cropWidth = cW;
cropHeight = cH;
// Note: printWrapped() offsets the initial cursor position by heightAboveCursor() val,
// so we need to account for that when determining the height
return (getCursorY() + getFont().heightBelowCursor());
}
// Fill a region with sparse diagonal lines, to create a pseudo-translucent fill
void InkHUD::Applet::hatchRegion(int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t spacing, Color color)
{
// Cache the currently cropped region
int16_t oldCropL = cropLeft;
int16_t oldCropT = cropTop;
uint16_t oldCropW = cropWidth;
uint16_t oldCropH = cropHeight;
setCrop(x, y, w, h);
// Draw lines starting along the top edge, every few px
for (int16_t ix = x; ix < x + w; ix += spacing) {
for (int16_t i = 0; i < w || i < h; i++) {
drawPixel(ix + i, y + i, color);
}
}
// Draw lines starting along the left edge, every few px
for (int16_t iy = y; iy < y + h; iy += spacing) {
for (int16_t i = 0; i < w || i < h; i++) {
drawPixel(x + i, iy + i, color);
}
}
// Restore any previous crop
// If none was set, this will clear
cropLeft = oldCropL;
cropTop = oldCropT;
cropWidth = oldCropW;
cropHeight = oldCropH;
}
// Get a human readable time representation of an epoch time (seconds since 1970)
// If time is invalid, this will be an empty string
std::string InkHUD::Applet::getTimeString(uint32_t epochSeconds)
{
#ifdef BUILD_EPOCH
constexpr uint32_t validAfterEpoch = BUILD_EPOCH - (SEC_PER_DAY * 30 * 6); // 6 Months prior to build
#else
constexpr uint32_t validAfterEpoch = 1738368000 - (SEC_PER_DAY * 30 * 6); // 6 Months prior to Feb 1, 2025 12:00:00 AM GMT
#endif
uint32_t epochNow = getValidTime(RTCQuality::RTCQualityDevice, true);
int32_t daysAgo = (epochNow - epochSeconds) / SEC_PER_DAY;
int32_t hoursAgo = (epochNow - epochSeconds) / SEC_PER_HOUR;
// Times are invalid: rtc is much older than when code was built
// Don't give any human readable string
if (epochNow <= validAfterEpoch)
return "";
// Times are invalid: argument time is significantly ahead of RTC
// Don't give any human readable string
if (daysAgo < -2)
return "";
// Times are probably invalid: more than 6 months ago
if (daysAgo > 6 * 30)
return "";
if (daysAgo > 1)
return to_string(daysAgo) + " days ago";
else if (hoursAgo > 18)
return "Yesterday";
else {
uint32_t hms = epochSeconds % SEC_PER_DAY;
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
// Tear apart hms into h:m
uint32_t hour = hms / SEC_PER_HOUR;
uint32_t min = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
// Format the clock string, either 12 hour or 24 hour
char clockStr[11];
if (config.display.use_12h_clock)
sprintf(clockStr, "%u:%02u %s", (hour % 12 == 0 ? 12 : hour % 12), min, hour > 11 ? "PM" : "AM");
else
sprintf(clockStr, "%02u:%02u", hour, min);
return clockStr;
}
}
// If no argument specified, get time string for the current RTC time
std::string InkHUD::Applet::getTimeString()
{
return getTimeString(getValidTime(RTCQuality::RTCQualityDevice, true));
}
// Calculate how many nodes have been seen within our preferred window of activity
// This period is set by user, via the menu
// Todo: optimize to calculate once only per WindowManager::render
uint16_t InkHUD::Applet::getActiveNodeCount()
{
// Don't even try to count nodes if RTC isn't set
// The last heard values in nodedb will be incomprehensible
if (getRTCQuality() == RTCQualityNone)
return 0;
uint16_t count = 0;
// For each node in db
for (uint16_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
// Check if heard recently, and not our own node
if (sinceLastSeen(node) < settings->recentlyActiveSeconds && node->num != nodeDB->getNodeNum())
count++;
}
return count;
}
// Get an abbreviated, human readable, distance string
// Honors config.display.units, to offer both metric and imperial
std::string InkHUD::Applet::localizeDistance(uint32_t meters)
{
constexpr float FEET_PER_METER = 3.28084;
constexpr uint16_t FEET_PER_MILE = 5280;
// Resulting string
std::string localized;
// Imperial
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
uint32_t feet = meters * FEET_PER_METER;
// Distant (miles, rounded)
if (feet > FEET_PER_MILE / 2) {
localized += to_string((uint32_t)roundf(feet / FEET_PER_MILE));
localized += "mi";
}
// Nearby (feet)
else {
localized += to_string(feet);
localized += "ft";
}
}
// Metric
else {
// Distant (kilometers, rounded)
if (meters >= 500) {
localized += to_string((uint32_t)roundf(meters / 1000.0));
localized += "km";
}
// Nearby (meters)
else {
localized += to_string(meters);
localized += "m";
}
}
return localized;
}
// Print text with a "faux bold" effect, by drawing it multiple times, offsetting slightly
void InkHUD::Applet::printThick(int16_t xCenter, int16_t yCenter, std::string text, uint8_t thicknessX, uint8_t thicknessY)
{
// How many times to draw along x axis
int16_t xStart;
int16_t xEnd;
switch (thicknessX) {
case 0:
assert(false);
case 1:
xStart = xCenter;
xEnd = xCenter;
break;
case 2:
xStart = xCenter;
xEnd = xCenter + 1;
break;
default:
xStart = xCenter - (thicknessX / 2);
xEnd = xCenter + (thicknessX / 2);
}
// How many times to draw along Y axis
int16_t yStart;
int16_t yEnd;
switch (thicknessY) {
case 0:
assert(false);
case 1:
yStart = yCenter;
yEnd = yCenter;
break;
case 2:
yStart = yCenter;
yEnd = yCenter + 1;
break;
default:
yStart = yCenter - (thicknessY / 2);
yEnd = yCenter + (thicknessY / 2);
}
// Print multiple times, overlapping
for (int16_t x = xStart; x <= xEnd; x++) {
for (int16_t y = yStart; y <= yEnd; y++) {
printAt(x, y, text, CENTER, MIDDLE);
}
}
}
// Allow this applet to suppress notifications
// Asked before a notification is shown via the NotificationApplet
// An applet might want to suppress a notification if the applet itself already displays this info
// Example: AllMessageApplet should not approve notifications for messages, if it is in foreground
bool InkHUD::Applet::approveNotification(NicheGraphics::InkHUD::Notification &n)
{
// By default, no objection
return true;
}
// Draw the standard header, used by most Applets
/*
┌───────────────────────────────┐
│ Applet::name here │
│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
│ │
│ │
│ │
└───────────────────────────────┘
*/
void InkHUD::Applet::drawHeader(std::string text)
{
// Y position for divider
// - between header text and messages
constexpr int16_t padDivH = 2;
const int16_t headerDivY = padDivH + fontSmall.lineHeight() + padDivH - 1;
// Print header
printAt(0, padDivH, text);
// Divider
// - below header text: separates message
// - above header text: separates other applets
for (int16_t x = 0; x < width(); x += 2) {
drawPixel(x, 0, BLACK);
drawPixel(x, headerDivY, BLACK); // Dotted 50%
}
}
// Get the height of the standard applet header
// This will vary, depending on font
// Applets use this value to avoid drawing overtop the header
uint16_t InkHUD::Applet::getHeaderHeight()
{
// Y position for divider
// - between header text and messages
constexpr int16_t padDivH = 2;
const int16_t headerDivY = padDivH + fontSmall.lineHeight() + padDivH - 1;
return headerDivY + 1; // "Plus one": height is always one more than Y position
}
// "Scale to fit": width of Meshtastic logo to fit given region, maintaining aspect ratio
uint16_t InkHUD::Applet::getLogoWidth(uint16_t limitWidth, uint16_t limitHeight)
{
// Determine whether we're limited by width or height
// Makes sure we draw the logo as large as possible, within the specified region,
// while still maintaining correct aspect ratio
if (limitWidth > limitHeight * LOGO_ASPECT_RATIO)
return limitHeight * LOGO_ASPECT_RATIO;
else
return limitWidth;
}
// "Scale to fit": height of Meshtastic logo to fit given region, maintaining aspect ratio
uint16_t InkHUD::Applet::getLogoHeight(uint16_t limitWidth, uint16_t limitHeight)
{
// Determine whether we're limited by width or height
// Makes sure we draw the logo as large as possible, within the specified region,
// while still maintaining correct aspect ratio
if (limitHeight > limitWidth / LOGO_ASPECT_RATIO)
return limitWidth / LOGO_ASPECT_RATIO;
else
return limitHeight;
}
// Draw a scalable Meshtastic logo
// Make sure to provide dimensions which have the correct aspect ratio (~2)
// Three paths, drawn thick using quads, with one corner "radiused"
/*
- ^
/- /-\
// // \\
// // \\
// // \\
// // \\
*/
void InkHUD::Applet::drawLogo(int16_t centerX, int16_t centerY, uint16_t width, uint16_t height, Color color)
{
struct Point {
int x;
int y;
};
typedef Point Distance;
int16_t logoTh = width * 0.068; // Thickness scales with width. Measured from logo at meshtastic.org.
int16_t logoL = centerX - (width / 2) + (logoTh / 2);
int16_t logoT = centerY - (height / 2) + (logoTh / 2);
int16_t logoW = width - logoTh;
int16_t logoH = height - logoTh;
int16_t logoR = logoL + logoW - 1;
int16_t logoB = logoT + logoH - 1;
// Points for paths (a, b, and c)
/*
+-----------------------------+
--| a2 b2/c1 |
| |
| |
| |
--| a1 b1 c2 |
+-----------------------------+
| | | |
*/
Point a1 = {map(0, 0, 3, logoL, logoR), logoB};
Point a2 = {map(1, 0, 3, logoL, logoR), logoT};
Point b1 = {map(1, 0, 3, logoL, logoR), logoB};
Point b2 = {map(2, 0, 3, logoL, logoR), logoT};
Point c1 = {map(2, 0, 3, logoL, logoR), logoT};
Point c2 = {map(3, 0, 3, logoL, logoR), logoB};
// Find angle of the path(s)
// Used to thicken the single pixel paths
/*
+-------------------------------+
| a2 |
| -| |
| -/ | |
| -/ | |
| -/# | |
| -/ # | |
| / # | |
| a1---------- |
+-------------------------------+
*/
Distance deltaA = {abs(a2.x - a1.x), abs(a2.y - a1.y)};
float angle = tanh((float)deltaA.y / deltaA.x);
// Distance (at right angle to the paths), which will give corners for our "quads"
// The distance is unsigned. We will vary the signedness of the x and y components to suit the path and corner
/*
| a2
| .
| ..
| aq1 ..
| # ..
| | # ..
|fromPath.y | # ..
| +----a1
|
| fromPath.x
+--------------------------------
*/
Distance fromPath;
fromPath.x = cos(radians(90) - angle) * logoTh * 0.5;
fromPath.y = sin(radians(90) - angle) * logoTh * 0.5;
// Make the paths thick
// Corner points for the rectangles (quads):
/*
aq2
a2
/ aq3
/
/
aq1 /
a1
aq3
*/
// Filled as two triangles per quad:
/*
aq2 #
# ###
## # aq3
## ### -
## #### -/
## ### -/
## #### -/
aq1 ## -/
--- -/
\---aq4
*/
// Make the path thick: path a becomes quad a
Point aq1{a1.x - fromPath.x, a1.y - fromPath.y};
Point aq2{a2.x - fromPath.x, a2.y - fromPath.y};
Point aq3{a2.x + fromPath.x, a2.y + fromPath.y};
Point aq4{a1.x + fromPath.x, a1.y + fromPath.y};
fillTriangle(aq1.x, aq1.y, aq2.x, aq2.y, aq3.x, aq3.y, color);
fillTriangle(aq1.x, aq1.y, aq3.x, aq3.y, aq4.x, aq4.y, color);
// Make the path thick: path b becomes quad b
Point bq1{b1.x - fromPath.x, b1.y - fromPath.y};
Point bq2{b2.x - fromPath.x, b2.y - fromPath.y};
Point bq3{b2.x + fromPath.x, b2.y + fromPath.y};
Point bq4{b1.x + fromPath.x, b1.y + fromPath.y};
fillTriangle(bq1.x, bq1.y, bq2.x, bq2.y, bq3.x, bq3.y, color);
fillTriangle(bq1.x, bq1.y, bq3.x, bq3.y, bq4.x, bq4.y, color);
// Make the path thick: path c becomes quad c
Point cq1{c1.x - fromPath.x, c1.y + fromPath.y};
Point cq2{c2.x - fromPath.x, c2.y + fromPath.y};
Point cq3{c2.x + fromPath.x, c2.y - fromPath.y};
Point cq4{c1.x + fromPath.x, c1.y - fromPath.y};
fillTriangle(cq1.x, cq1.y, cq2.x, cq2.y, cq3.x, cq3.y, color);
fillTriangle(cq1.x, cq1.y, cq3.x, cq3.y, cq4.x, cq4.y, color);
// Radius the intersection of quad b and quad c
/*
b2 / c1
####
## ##
/ \
/ \/ \
/ /\ \
/ / \ \
*/
// Don't attempt if logo is tiny
if (logoTh > 3) {
// The radius for the cap *should* be the same as logoTh, but it's not, due to accumulated rounding
// We get better results just re-deriving it
int16_t capRad = sqrt(pow(fromPath.x, 2) + pow(fromPath.y, 2));
fillCircle(b2.x, b2.y, capRad, color);
}
}
#endif
@@ -0,0 +1,176 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
Base class for InkHUD applets
Must be overriden
An applet is one "program" which may show info on the display.
*/
#pragma once
#include "configuration.h"
#include <GFX.h> // GFXRoot drawing lib
#include "mesh/MeshTypes.h"
#include "./AppletFont.h"
#include "./Applets/System/Notification/Notification.h" // The notification object, not the applet
#include "./InkHUD.h"
#include "./Persistence.h"
#include "./Tile.h"
#include "graphics/niche/Drivers/EInk/EInk.h"
namespace NicheGraphics::InkHUD
{
using NicheGraphics::Drivers::EInk;
using std::to_string;
class Applet : public GFX
{
public:
// Which edge Applet::printAt will place on the Y parameter
enum VerticalAlignment : uint8_t {
TOP,
MIDDLE,
BOTTOM,
};
// Which edge Applet::printAt will place on the X parameter
enum HorizontalAlignment : uint8_t {
LEFT,
RIGHT,
CENTER,
};
// An easy-to-understand interpretation of SNR and RSSI
// Calculate with Applet::getSignalStrength
enum SignalStrength : int8_t {
SIGNAL_UNKNOWN = -1,
SIGNAL_NONE,
SIGNAL_BAD,
SIGNAL_FAIR,
SIGNAL_GOOD,
};
Applet();
void setTile(Tile *t); // Should only be called via Tile::setApplet
Tile *getTile(); // Tile with which this applet is linked
// Rendering
void render(); // Draw the applet
bool wantsToRender(); // Check whether applet wants to render
bool wantsToAutoshow(); // Check whether applet wants to become foreground
Drivers::EInk::UpdateTypes wantsUpdateType(); // Check which display update type the applet would prefer
void updateDimensions(); // Get current size from tile
void resetDrawingSpace(); // Makes sure every render starts with same parameters
// State of the applet
void activate(); // Begin running
void deactivate(); // Stop running
void bringToForeground(); // Show
void sendToBackground(); // Hide
bool isActive();
bool isForeground();
// Event handlers
virtual void onRender() = 0; // All drawing happens here
virtual void onActivate() {}
virtual void onDeactivate() {}
virtual void onForeground() {}
virtual void onBackground() {}
virtual void onShutdown() {}
virtual void onButtonShortPress() {} // (System Applets only)
virtual void onButtonLongPress() {} // (System Applets only)
virtual bool approveNotification(Notification &n); // Allow an applet to veto a notification
static uint16_t getHeaderHeight(); // How tall the "standard" applet header is
static AppletFont fontSmall, fontMedium, fontLarge; // The general purpose fonts, used by all applets
const char *name = nullptr; // Shown in applet selection menu. Also used as an identifier by InkHUD::getSystemApplet
protected:
void drawPixel(int16_t x, int16_t y, uint16_t color) override; // Place a single pixel. All drawing output passes through here
void requestUpdate(EInk::UpdateTypes type = EInk::UpdateTypes::UNSPECIFIED); // Ask WindowManager to schedule a display update
void requestAutoshow(); // Ask for applet to be moved to foreground
uint16_t X(float f); // Map applet width, mapped from 0 to 1.0
uint16_t Y(float f); // Map applet height, mapped from 0 to 1.0
void setCrop(int16_t left, int16_t top, uint16_t width, uint16_t height); // Ignore pixels drawn outside a certain region
void resetCrop(); // Removes setCrop()
// Text
void setFont(AppletFont f);
AppletFont getFont();
uint16_t getTextWidth(std::string text);
uint16_t getTextWidth(const char *text);
uint32_t getWrappedTextHeight(int16_t left, uint16_t width, std::string text); // Result of printWrapped
void printAt(int16_t x, int16_t y, const char *text, HorizontalAlignment ha = LEFT, VerticalAlignment va = TOP);
void printAt(int16_t x, int16_t y, std::string text, HorizontalAlignment ha = LEFT, VerticalAlignment va = TOP);
void printThick(int16_t xCenter, int16_t yCenter, std::string text, uint8_t thicknessX, uint8_t thicknessY); // Faux bold
void printWrapped(int16_t left, int16_t top, uint16_t width, std::string text); // Per-word line wrapping
void hatchRegion(int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t spacing, Color color); // Fill with sparse lines
void drawHeader(std::string text); // Draw the standard applet header
// Meshtastic Logo
static constexpr float LOGO_ASPECT_RATIO = 1.9; // Width:Height for drawing the Meshtastic logo
uint16_t getLogoWidth(uint16_t limitWidth, uint16_t limitHeight); // Size Meshtastic logo to fit within region
uint16_t getLogoHeight(uint16_t limitWidth, uint16_t limitHeight); // Size Meshtastic logo to fit within region
void drawLogo(int16_t centerX, int16_t centerY, uint16_t width, uint16_t height,
Color color = BLACK); // Draw the Meshtastic logo
std::string hexifyNodeNum(NodeNum num); // Style as !0123abdc
SignalStrength getSignalStrength(float snr, float rssi); // Interpret SNR and RSSI, as an easy to understand value
std::string getTimeString(uint32_t epochSeconds); // Human readable
std::string getTimeString(); // Current time, human readable
uint16_t getActiveNodeCount(); // Duration determined by user, in onscreen menu
std::string localizeDistance(uint32_t meters); // Human readable distance, imperial or metric
std::string parse(std::string text); // Handle text which might contain special chars
std::string parseShortName(meshtastic_NodeInfoLite *node); // Get the shortname, or a substitute if has unprintable chars
bool isPrintable(std::string); // Check for characters which the font can't print
// Convenient references
InkHUD *inkhud = nullptr;
Persistence::Settings *settings = nullptr;
Persistence::LatestMessage *latestMessage = nullptr;
private:
Tile *assignedTile = nullptr; // Rendered pixels are fed into a Tile object, which translates them, then passes to WM
bool active = false; // Has the user enabled this applet (at run-time)?
bool foreground = false; // Is the applet currently drawn on a tile?
bool wantRender = false; // In some situations, checked by WindowManager when updating, to skip unneeded redrawing.
bool wantAutoshow = false; // Does the applet have new data it would like to display in foreground?
NicheGraphics::Drivers::EInk::UpdateTypes wantUpdateType =
NicheGraphics::Drivers::EInk::UpdateTypes::UNSPECIFIED; // Which update method we'd prefer when redrawing the display
using GFX::setFont; // Make sure derived classes use AppletFont instead of AdafruitGFX fonts directly
using GFX::setRotation; // Block setRotation calls. Rotation is handled globally by WindowManager.
AppletFont currentFont; // As passed to setFont
// As set by setCrop
int16_t cropLeft = 0;
int16_t cropTop = 0;
uint16_t cropWidth = 0;
uint16_t cropHeight = 0;
};
}; // namespace NicheGraphics::InkHUD
#endif
@@ -0,0 +1,735 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./AppletFont.h"
#include <assert.h>
using namespace NicheGraphics;
InkHUD::AppletFont::AppletFont()
{
// Default constructor uses the in-built AdafruitGFX font (not recommended)
}
InkHUD::AppletFont::AppletFont(const GFXfont &adafruitGFXFont, Encoding encoding, int8_t paddingTop, int8_t paddingBottom)
: gfxFont(&adafruitGFXFont), encoding(encoding)
{
// AdafruitGFX fonts are drawn relative to a "cursor line";
// they print as if the glyphs are resting on the line of piece of ruled paper.
// The glyphs also each have a different height.
// To simplify drawing, we will scan the entire font now, and determine an appropriate height for a line of text
// We also need to know where that "cursor line" sits inside this "line height";
// we need this additional info in order to align text by top-left, bottom-right, etc
// AdafruitGFX fonts do declare a line-height, but this seems to include a certain amount of padding,
// which we'd rather not deal with. If we want padding, we'll add it manually.
this->ascenderHeight = 0;
this->descenderHeight = 0;
this->height = 0;
// Scan each glyph in the AdafruitGFX font
for (uint16_t i = 0; i <= (gfxFont->last - gfxFont->first); i++) {
uint8_t glyphHeight = gfxFont->glyph[i].height; // Height of glyph
this->height = max(this->height, glyphHeight); // Store if it's a new max
// Calculate how far the glyph rises the cursor line
// Store if new max value
// Caution: signed and unsigned types
int8_t glyphAscender = 0 - gfxFont->glyph[i].yOffset;
if (glyphAscender > 0)
this->ascenderHeight = max(this->ascenderHeight, (uint8_t)glyphAscender);
int8_t glyphDescender = gfxFont->glyph[i].height + gfxFont->glyph[i].yOffset;
if (glyphDescender > 0)
this->descenderHeight = max(this->descenderHeight, (uint8_t)glyphDescender);
}
// Apply any manual padding to grow or shrink the line size
// Helpful if a font has one or two exceptionally large characters, which would make the lines ridiculously tall
ascenderHeight += paddingTop;
descenderHeight += paddingBottom;
// Find how far the cursor advances when we "print" a space character
spaceCharWidth = gfxFont->glyph[(uint8_t)' ' - gfxFont->first].xAdvance;
}
/*
▲ ##### # ▲
│ # # │
lineHeight │ ### # │
│ # # # # │ heightAboveCursor
│ # # # # │
│ # # #### │
│ -----------------#----
│ # │ heightBelowCursor
▼ ### ▼
*/
uint8_t InkHUD::AppletFont::lineHeight()
{
return this->height;
}
// AdafruitGFX fonts print characters so that they nicely on an imaginary line (think: ruled paper).
// This value is the height of the font, above that imaginary line.
// Used to calculate the true height of the font
uint8_t InkHUD::AppletFont::heightAboveCursor()
{
return this->ascenderHeight;
}
// AdafruitGFX fonts print characters so that they nicely on an imaginary line (think: ruled paper).
// This value is the height of the font, below that imaginary line.
// Used to calculate the true height of the font
uint8_t InkHUD::AppletFont::heightBelowCursor()
{
return this->descenderHeight;
}
// Width of the space character
// Used with Applet::printWrapped
uint8_t InkHUD::AppletFont::widthBetweenWords()
{
return this->spaceCharWidth;
}
// Convert a unicode char from set of UTF-8 bytes to UTF-32
// Used by AppletFont::applyEncoding, which remaps unicode chars for extended ASCII fonts, based on their UTF-32 value
uint32_t InkHUD::AppletFont::toUtf32(std::string utf8)
{
uint32_t utf32 = 0;
switch (utf8.length()) {
case 2:
// 5 bits + 6 bits
utf32 |= (utf8.at(0) & 0b00011111) << 6;
utf32 |= (utf8.at(1) & 0b00111111);
break;
case 3:
// 4 bits + 6 bits + 6 bits
utf32 |= (utf8.at(0) & 0b00001111) << (6 + 6);
utf32 |= (utf8.at(1) & 0b00111111) << 6;
utf32 |= (utf8.at(2) & 0b00111111);
break;
case 4:
// 3 bits + 6 bits + 6 bits + 6 bits
utf32 |= (utf8.at(0) & 0b00000111) << (6 + 6 + 6);
utf32 |= (utf8.at(1) & 0b00111111) << (6 + 6);
utf32 |= (utf8.at(2) & 0b00111111) << 6;
utf32 |= (utf8.at(3) & 0b00111111);
break;
default:
assert(false);
}
return utf32;
}
// Process a string, collating UTF-8 bytes, and sending them off for re-encoding to extended ASCII
// Not all InkHUD text is passed through here, only text which could potentially contain non-ASCII chars
std::string InkHUD::AppletFont::decodeUTF8(std::string encoded)
{
// Final processed output
std::string decoded;
// Holds bytes for one UTF-8 char during parsing
std::string utf8Char;
uint8_t utf8CharSize = 0;
for (char &c : encoded) {
// If first byte
if (utf8Char.empty()) {
// If MSB is unset, byte is an ASCII char
// If MSB is set, byte is part of a UTF-8 char. Counting number of higher-order bits tells how many bytes in char
if ((c & 0x80)) {
char c1 = c;
while (c1 & 0x80) {
c1 <<= 1;
utf8CharSize++;
}
}
}
// Append the byte to the UTF-8 char we're building
utf8Char += c;
// More bytes left to collect. Iterate.
if (utf8Char.length() < utf8CharSize)
continue;
// Now collected all bytes for this char
// Remap the value to match the encoding of our 8-bit AppletFont
decoded += applyEncoding(utf8Char);
// Reset, ready to build next UTF-8 char from the encoded bytes
utf8Char.clear();
utf8CharSize = 0;
} // For each char
// All chars processed, return result
return decoded;
}
// Re-encode a single UTF-8 character to extended ASCII
// Target encoding depends on the font
char InkHUD::AppletFont::applyEncoding(std::string utf8)
{
// ##################################################### Syntactic Sugar #####################################################
#define REMAP(in, out) \
case in: \
return out;
// ###########################################################################################################################
// Latin - Central Europe
// https://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1250.TXT
if (encoding == WINDOWS_1250) {
// 1-Byte chars: no remapping
if (utf8.length() == 1)
return utf8.at(0);
// Multi-byte chars:
switch (toUtf32(utf8)) {
REMAP(0x20AC, 0x80); // EURO SIGN
REMAP(0x201A, 0x82); // SINGLE LOW-9 QUOTATION MARK
REMAP(0x201E, 0x84); // DOUBLE LOW-9 QUOTATION MARK
REMAP(0x2026, 0x85); // HORIZONTAL ELLIPSIS
REMAP(0x2020, 0x86); // DAGGER
REMAP(0x2021, 0x87); // DOUBLE DAGGER
REMAP(0x2030, 0x89); // PER MILLE SIGN
REMAP(0x0160, 0x8A); // LATIN CAPITAL LETTER S WITH CARON
REMAP(0x2039, 0x8B); // SINGLE LEFT-POINTING ANGLE QUOTATION MARK
REMAP(0x015A, 0x8C); // LATIN CAPITAL LETTER S WITH ACUTE
REMAP(0x0164, 0x8D); // LATIN CAPITAL LETTER T WITH CARON
REMAP(0x017D, 0x8E); // LATIN CAPITAL LETTER Z WITH CARON
REMAP(0x0179, 0x8F); // LATIN CAPITAL LETTER Z WITH ACUTE
REMAP(0x2018, 0x91); // LEFT SINGLE QUOTATION MARK
REMAP(0x2019, 0x92); // RIGHT SINGLE QUOTATION MARK
REMAP(0x201C, 0x93); // LEFT DOUBLE QUOTATION MARK
REMAP(0x201D, 0x94); // RIGHT DOUBLE QUOTATION MARK
REMAP(0x2022, 0x95); // BULLET
REMAP(0x2013, 0x96); // EN DASH
REMAP(0x2014, 0x97); // EM DASH
REMAP(0x2122, 0x99); // TRADE MARK SIGN
REMAP(0x0161, 0x9A); // LATIN SMALL LETTER S WITH CARON
REMAP(0x203A, 0x9B); // SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
REMAP(0x015B, 0x9C); // LATIN SMALL LETTER S WITH ACUTE
REMAP(0x0165, 0x9D); // LATIN SMALL LETTER T WITH CARON
REMAP(0x017E, 0x9E); // LATIN SMALL LETTER Z WITH CARON
REMAP(0x017A, 0x9F); // LATIN SMALL LETTER Z WITH ACUTE
REMAP(0x00A0, 0xA0); // NO-BREAK SPACE
REMAP(0x02C7, 0xA1); // CARON
REMAP(0x02D8, 0xA2); // BREVE
REMAP(0x0141, 0xA3); // LATIN CAPITAL LETTER L WITH STROKE
REMAP(0x00A4, 0xA4); // CURRENCY SIGN
REMAP(0x0104, 0xA5); // LATIN CAPITAL LETTER A WITH OGONEK
REMAP(0x00A6, 0xA6); // BROKEN BAR
REMAP(0x00A7, 0xA7); // SECTION SIGN
REMAP(0x00A8, 0xA8); // DIAERESIS
REMAP(0x00A9, 0xA9); // COPYRIGHT SIGN
REMAP(0x015E, 0xAA); // LATIN CAPITAL LETTER S WITH CEDILLA
REMAP(0x00AB, 0xAB); // LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
REMAP(0x00AC, 0xAC); // NOT SIGN
REMAP(0x00AD, 0xAD); // SOFT HYPHEN
REMAP(0x00AE, 0xAE); // REGISTERED SIGN
REMAP(0x017B, 0xAF); // LATIN CAPITAL LETTER Z WITH DOT ABOVE
REMAP(0x00B0, 0xB0); // DEGREE SIGN
REMAP(0x00B1, 0xB1); // PLUS-MINUS SIGN
REMAP(0x02DB, 0xB2); // OGONEK
REMAP(0x0142, 0xB3); // LATIN SMALL LETTER L WITH STROKE
REMAP(0x00B4, 0xB4); // ACUTE ACCENT
REMAP(0x00B5, 0xB5); // MICRO SIGN
REMAP(0x00B6, 0xB6); // PILCROW SIGN
REMAP(0x00B7, 0xB7); // MIDDLE DOT
REMAP(0x00B8, 0xB8); // CEDILLA
REMAP(0x0105, 0xB9); // LATIN SMALL LETTER A WITH OGONEK
REMAP(0x015F, 0xBA); // LATIN SMALL LETTER S WITH CEDILLA
REMAP(0x00BB, 0xBB); // RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
REMAP(0x013D, 0xBC); // LATIN CAPITAL LETTER L WITH CARON
REMAP(0x02DD, 0xBD); // DOUBLE ACUTE ACCENT
REMAP(0x013E, 0xBE); // LATIN SMALL LETTER L WITH CARON
REMAP(0x017C, 0xBF); // LATIN SMALL LETTER Z WITH DOT ABOVE
REMAP(0x0154, 0xC0); // LATIN CAPITAL LETTER R WITH ACUTE
REMAP(0x00C1, 0xC1); // LATIN CAPITAL LETTER A WITH ACUTE
REMAP(0x00C2, 0xC2); // LATIN CAPITAL LETTER A WITH CIRCUMFLEX
REMAP(0x0102, 0xC3); // LATIN CAPITAL LETTER A WITH BREVE
REMAP(0x00C4, 0xC4); // LATIN CAPITAL LETTER A WITH DIAERESIS
REMAP(0x0139, 0xC5); // LATIN CAPITAL LETTER L WITH ACUTE
REMAP(0x0106, 0xC6); // LATIN CAPITAL LETTER C WITH ACUTE
REMAP(0x00C7, 0xC7); // LATIN CAPITAL LETTER C WITH CEDILLA
REMAP(0x010C, 0xC8); // LATIN CAPITAL LETTER C WITH CARON
REMAP(0x00C9, 0xC9); // LATIN CAPITAL LETTER E WITH ACUTE
REMAP(0x0118, 0xCA); // LATIN CAPITAL LETTER E WITH OGONEK
REMAP(0x00CB, 0xCB); // LATIN CAPITAL LETTER E WITH DIAERESIS
REMAP(0x011A, 0xCC); // LATIN CAPITAL LETTER E WITH CARON
REMAP(0x00CD, 0xCD); // LATIN CAPITAL LETTER I WITH ACUTE
REMAP(0x00CE, 0xCE); // LATIN CAPITAL LETTER I WITH CIRCUMFLEX
REMAP(0x010E, 0xCF); // LATIN CAPITAL LETTER D WITH CARON
REMAP(0x0110, 0xD0); // LATIN CAPITAL LETTER D WITH STROKE
REMAP(0x0143, 0xD1); // LATIN CAPITAL LETTER N WITH ACUTE
REMAP(0x0147, 0xD2); // LATIN CAPITAL LETTER N WITH CARON
REMAP(0x00D3, 0xD3); // LATIN CAPITAL LETTER O WITH ACUTE
REMAP(0x00D4, 0xD4); // LATIN CAPITAL LETTER O WITH CIRCUMFLEX
REMAP(0x0150, 0xD5); // LATIN CAPITAL LETTER O WITH DOUBLE ACUTE
REMAP(0x00D6, 0xD6); // LATIN CAPITAL LETTER O WITH DIAERESIS
REMAP(0x00D7, 0xD7); // MULTIPLICATION SIGN
REMAP(0x0158, 0xD8); // LATIN CAPITAL LETTER R WITH CARON
REMAP(0x016E, 0xD9); // LATIN CAPITAL LETTER U WITH RING ABOVE
REMAP(0x00DA, 0xDA); // LATIN CAPITAL LETTER U WITH ACUTE
REMAP(0x0170, 0xDB); // LATIN CAPITAL LETTER U WITH DOUBLE ACUTE
REMAP(0x00DC, 0xDC); // LATIN CAPITAL LETTER U WITH DIAERESIS
REMAP(0x00DD, 0xDD); // LATIN CAPITAL LETTER Y WITH ACUTE
REMAP(0x0162, 0xDE); // LATIN CAPITAL LETTER T WITH CEDILLA
REMAP(0x00DF, 0xDF); // LATIN SMALL LETTER SHARP S
REMAP(0x0155, 0xE0); // LATIN SMALL LETTER R WITH ACUTE
REMAP(0x00E1, 0xE1); // LATIN SMALL LETTER A WITH ACUTE
REMAP(0x00E2, 0xE2); // LATIN SMALL LETTER A WITH CIRCUMFLEX
REMAP(0x0103, 0xE3); // LATIN SMALL LETTER A WITH BREVE
REMAP(0x00E4, 0xE4); // LATIN SMALL LETTER A WITH DIAERESIS
REMAP(0x013A, 0xE5); // LATIN SMALL LETTER L WITH ACUTE
REMAP(0x0107, 0xE6); // LATIN SMALL LETTER C WITH ACUTE
REMAP(0x00E7, 0xE7); // LATIN SMALL LETTER C WITH CEDILLA
REMAP(0x010D, 0xE8); // LATIN SMALL LETTER C WITH CARON
REMAP(0x00E9, 0xE9); // LATIN SMALL LETTER E WITH ACUTE
REMAP(0x0119, 0xEA); // LATIN SMALL LETTER E WITH OGONEK
REMAP(0x00EB, 0xEB); // LATIN SMALL LETTER E WITH DIAERESIS
REMAP(0x011B, 0xEC); // LATIN SMALL LETTER E WITH CARON
REMAP(0x00ED, 0xED); // LATIN SMALL LETTER I WITH ACUTE
REMAP(0x00EE, 0xEE); // LATIN SMALL LETTER I WITH CIRCUMFLEX
REMAP(0x010F, 0xEF); // LATIN SMALL LETTER D WITH CARON
REMAP(0x0111, 0xF0); // LATIN SMALL LETTER D WITH STROKE
REMAP(0x0144, 0xF1); // LATIN SMALL LETTER N WITH ACUTE
REMAP(0x0148, 0xF2); // LATIN SMALL LETTER N WITH CARON
REMAP(0x00F3, 0xF3); // LATIN SMALL LETTER O WITH ACUTE
REMAP(0x00F4, 0xF4); // LATIN SMALL LETTER O WITH CIRCUMFLEX
REMAP(0x0151, 0xF5); // LATIN SMALL LETTER O WITH DOUBLE ACUTE
REMAP(0x00F6, 0xF6); // LATIN SMALL LETTER O WITH DIAERESIS
REMAP(0x00F7, 0xF7); // DIVISION SIGN
REMAP(0x0159, 0xF8); // LATIN SMALL LETTER R WITH CARON
REMAP(0x016F, 0xF9); // LATIN SMALL LETTER U WITH RING ABOVE
REMAP(0x00FA, 0xFA); // LATIN SMALL LETTER U WITH ACUTE
REMAP(0x0171, 0xFB); // LATIN SMALL LETTER U WITH DOUBLE ACUTE
REMAP(0x00FC, 0xFC); // LATIN SMALL LETTER U WITH DIAERESIS
REMAP(0x00FD, 0xFD); // LATIN SMALL LETTER Y WITH ACUTE
REMAP(0x0163, 0xFE); // LATIN SMALL LETTER T WITH CEDILLA
REMAP(0x02D9, 0xFF); // DOT ABOVE
}
}
// Latin - Cyrillic
// https://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1251.TXT
else if (encoding == WINDOWS_1251) {
// 1-Byte chars: no remapping
if (utf8.length() == 1)
return utf8.at(0);
// Multi-byte chars:
switch (toUtf32(utf8)) {
REMAP(0x0402, 0x80); // CYRILLIC CAPITAL LETTER DJE
REMAP(0x0403, 0x81); // CYRILLIC CAPITAL LETTER GJE
REMAP(0x201A, 0x82); // SINGLE LOW-9 QUOTATION MARK
REMAP(0x0453, 0x83); // CYRILLIC SMALL LETTER GJE
REMAP(0x201E, 0x84); // DOUBLE LOW-9 QUOTATION MARK
REMAP(0x2026, 0x85); // HORIZONTAL ELLIPSIS
REMAP(0x2020, 0x86); // DAGGER
REMAP(0x2021, 0x87); // DOUBLE DAGGER
REMAP(0x20AC, 0x88); // EURO SIGN
REMAP(0x2030, 0x89); // PER MILLE SIGN
REMAP(0x0409, 0x8A); // CYRILLIC CAPITAL LETTER LJE
REMAP(0x2039, 0x8B); // SINGLE LEFT-POINTING ANGLE QUOTATION MARK
REMAP(0x040A, 0x8C); // CYRILLIC CAPITAL LETTER NJE
REMAP(0x040C, 0x8D); // CYRILLIC CAPITAL LETTER KJE
REMAP(0x040B, 0x8E); // CYRILLIC CAPITAL LETTER TSHE
REMAP(0x040F, 0x8F); // CYRILLIC CAPITAL LETTER DZHE
REMAP(0x0452, 0x90); // CYRILLIC SMALL LETTER DJE
REMAP(0x2018, 0x91); // LEFT SINGLE QUOTATION MARK
REMAP(0x2019, 0x92); // RIGHT SINGLE QUOTATION MARK
REMAP(0x201C, 0x93); // LEFT DOUBLE QUOTATION MARK
REMAP(0x201D, 0x94); // RIGHT DOUBLE QUOTATION MARK
REMAP(0x2022, 0x95); // BULLET
REMAP(0x2013, 0x96); // EN DASH
REMAP(0x2014, 0x97); // EM DASH
REMAP(0x2122, 0x99); // TRADE MARK SIGN
REMAP(0x0459, 0x9A); // CYRILLIC SMALL LETTER LJE
REMAP(0x203A, 0x9B); // SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
REMAP(0x045A, 0x9C); // CYRILLIC SMALL LETTER NJE
REMAP(0x045C, 0x9D); // CYRILLIC SMALL LETTER KJE
REMAP(0x045B, 0x9E); // CYRILLIC SMALL LETTER TSHE
REMAP(0x045F, 0x9F); // CYRILLIC SMALL LETTER DZHE
REMAP(0x00A0, 0xA0); // NO-BREAK SPACE
REMAP(0x040E, 0xA1); // CYRILLIC CAPITAL LETTER SHORT U
REMAP(0x045E, 0xA2); // CYRILLIC SMALL LETTER SHORT U
REMAP(0x0408, 0xA3); // CYRILLIC CAPITAL LETTER JE
REMAP(0x00A4, 0xA4); // CURRENCY SIGN
REMAP(0x0490, 0xA5); // CYRILLIC CAPITAL LETTER GHE WITH UPTURN
REMAP(0x00A6, 0xA6); // BROKEN BAR
REMAP(0x00A7, 0xA7); // SECTION SIGN
REMAP(0x0401, 0xA8); // CYRILLIC CAPITAL LETTER IO
REMAP(0x00A9, 0xA9); // COPYRIGHT SIGN
REMAP(0x0404, 0xAA); // CYRILLIC CAPITAL LETTER UKRAINIAN IE
REMAP(0x00AB, 0xAB); // LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
REMAP(0x00AC, 0xAC); // NOT SIGN
REMAP(0x00AD, 0xAD); // SOFT HYPHEN
REMAP(0x00AE, 0xAE); // REGISTERED SIGN
REMAP(0x0407, 0xAF); // CYRILLIC CAPITAL LETTER YI
REMAP(0x00B0, 0xB0); // DEGREE SIGN
REMAP(0x00B1, 0xB1); // PLUS-MINUS SIGN
REMAP(0x0406, 0xB2); // CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I
REMAP(0x0456, 0xB3); // CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
REMAP(0x0491, 0xB4); // CYRILLIC SMALL LETTER GHE WITH UPTURN
REMAP(0x00B5, 0xB5); // MICRO SIGN
REMAP(0x00B6, 0xB6); // PILCROW SIGN
REMAP(0x00B7, 0xB7); // MIDDLE DOT
REMAP(0x0451, 0xB8); // CYRILLIC SMALL LETTER IO
REMAP(0x2116, 0xB9); // NUMERO SIGN
REMAP(0x0454, 0xBA); // CYRILLIC SMALL LETTER UKRAINIAN IE
REMAP(0x00BB, 0xBB); // RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
REMAP(0x0458, 0xBC); // CYRILLIC SMALL LETTER JE
REMAP(0x0405, 0xBD); // CYRILLIC CAPITAL LETTER DZE
REMAP(0x0455, 0xBE); // CYRILLIC SMALL LETTER DZE
REMAP(0x0457, 0xBF); // CYRILLIC SMALL LETTER YI
REMAP(0x0410, 0xC0); // CYRILLIC CAPITAL LETTER A
REMAP(0x0411, 0xC1); // CYRILLIC CAPITAL LETTER BE
REMAP(0x0412, 0xC2); // CYRILLIC CAPITAL LETTER VE
REMAP(0x0413, 0xC3); // CYRILLIC CAPITAL LETTER GHE
REMAP(0x0414, 0xC4); // CYRILLIC CAPITAL LETTER DE
REMAP(0x0415, 0xC5); // CYRILLIC CAPITAL LETTER IE
REMAP(0x0416, 0xC6); // CYRILLIC CAPITAL LETTER ZHE
REMAP(0x0417, 0xC7); // CYRILLIC CAPITAL LETTER ZE
REMAP(0x0418, 0xC8); // CYRILLIC CAPITAL LETTER I
REMAP(0x0419, 0xC9); // CYRILLIC CAPITAL LETTER SHORT I
REMAP(0x041A, 0xCA); // CYRILLIC CAPITAL LETTER KA
REMAP(0x041B, 0xCB); // CYRILLIC CAPITAL LETTER EL
REMAP(0x041C, 0xCC); // CYRILLIC CAPITAL LETTER EM
REMAP(0x041D, 0xCD); // CYRILLIC CAPITAL LETTER EN
REMAP(0x041E, 0xCE); // CYRILLIC CAPITAL LETTER O
REMAP(0x041F, 0xCF); // CYRILLIC CAPITAL LETTER PE
REMAP(0x0420, 0xD0); // CYRILLIC CAPITAL LETTER ER
REMAP(0x0421, 0xD1); // CYRILLIC CAPITAL LETTER ES
REMAP(0x0422, 0xD2); // CYRILLIC CAPITAL LETTER TE
REMAP(0x0423, 0xD3); // CYRILLIC CAPITAL LETTER U
REMAP(0x0424, 0xD4); // CYRILLIC CAPITAL LETTER EF
REMAP(0x0425, 0xD5); // CYRILLIC CAPITAL LETTER HA
REMAP(0x0426, 0xD6); // CYRILLIC CAPITAL LETTER TSE
REMAP(0x0427, 0xD7); // CYRILLIC CAPITAL LETTER CHE
REMAP(0x0428, 0xD8); // CYRILLIC CAPITAL LETTER SHA
REMAP(0x0429, 0xD9); // CYRILLIC CAPITAL LETTER SHCHA
REMAP(0x042A, 0xDA); // CYRILLIC CAPITAL LETTER HARD SIGN
REMAP(0x042B, 0xDB); // CYRILLIC CAPITAL LETTER YERU
REMAP(0x042C, 0xDC); // CYRILLIC CAPITAL LETTER SOFT SIGN
REMAP(0x042D, 0xDD); // CYRILLIC CAPITAL LETTER E
REMAP(0x042E, 0xDE); // CYRILLIC CAPITAL LETTER YU
REMAP(0x042F, 0xDF); // CYRILLIC CAPITAL LETTER YA
REMAP(0x0430, 0xE0); // CYRILLIC SMALL LETTER A
REMAP(0x0431, 0xE1); // CYRILLIC SMALL LETTER BE
REMAP(0x0432, 0xE2); // CYRILLIC SMALL LETTER VE
REMAP(0x0433, 0xE3); // CYRILLIC SMALL LETTER GHE
REMAP(0x0434, 0xE4); // CYRILLIC SMALL LETTER DE
REMAP(0x0435, 0xE5); // CYRILLIC SMALL LETTER IE
REMAP(0x0436, 0xE6); // CYRILLIC SMALL LETTER ZHE
REMAP(0x0437, 0xE7); // CYRILLIC SMALL LETTER ZE
REMAP(0x0438, 0xE8); // CYRILLIC SMALL LETTER I
REMAP(0x0439, 0xE9); // CYRILLIC SMALL LETTER SHORT I
REMAP(0x043A, 0xEA); // CYRILLIC SMALL LETTER KA
REMAP(0x043B, 0xEB); // CYRILLIC SMALL LETTER EL
REMAP(0x043C, 0xEC); // CYRILLIC SMALL LETTER EM
REMAP(0x043D, 0xED); // CYRILLIC SMALL LETTER EN
REMAP(0x043E, 0xEE); // CYRILLIC SMALL LETTER O
REMAP(0x043F, 0xEF); // CYRILLIC SMALL LETTER PE
REMAP(0x0440, 0xF0); // CYRILLIC SMALL LETTER ER
REMAP(0x0441, 0xF1); // CYRILLIC SMALL LETTER ES
REMAP(0x0442, 0xF2); // CYRILLIC SMALL LETTER TE
REMAP(0x0443, 0xF3); // CYRILLIC SMALL LETTER U
REMAP(0x0444, 0xF4); // CYRILLIC SMALL LETTER EF
REMAP(0x0445, 0xF5); // CYRILLIC SMALL LETTER HA
REMAP(0x0446, 0xF6); // CYRILLIC SMALL LETTER TSE
REMAP(0x0447, 0xF7); // CYRILLIC SMALL LETTER CHE
REMAP(0x0448, 0xF8); // CYRILLIC SMALL LETTER SHA
REMAP(0x0449, 0xF9); // CYRILLIC SMALL LETTER SHCHA
REMAP(0x044A, 0xFA); // CYRILLIC SMALL LETTER HARD SIGN
REMAP(0x044B, 0xFB); // CYRILLIC SMALL LETTER YERU
REMAP(0x044C, 0xFC); // CYRILLIC SMALL LETTER SOFT SIGN
REMAP(0x044D, 0xFD); // CYRILLIC SMALL LETTER E
REMAP(0x044E, 0xFE); // CYRILLIC SMALL LETTER YU
REMAP(0x044F, 0xFF); // CYRILLIC SMALL LETTER YA
}
}
// Latin - Western Europe
// https://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1252.TXT
else if (encoding == WINDOWS_1252) {
// 1-Byte chars: no remapping
if (utf8.length() == 1)
return utf8.at(0);
// Multi-byte chars:
switch (toUtf32(utf8)) {
REMAP(0x20AC, 0x80) // EURO SIGN
REMAP(0x201A, 0x82) // SINGLE LOW-9 QUOTATION MARK
REMAP(0x0192, 0x83) // LATIN SMALL LETTER F WITH HOOK
REMAP(0x201E, 0x84) // DOUBLE LOW-9 QUOTATION MARK
REMAP(0x2026, 0x85) // HORIZONTAL ELLIPSIS
REMAP(0x2020, 0x86) // DAGGER
REMAP(0x2021, 0x87) // DOUBLE DAGGER
REMAP(0x02C6, 0x88) // MODIFIER LETTER CIRCUMFLEX ACCENT
REMAP(0x2030, 0x89) // PER MILLE SIGN
REMAP(0x0160, 0x8A) // LATIN CAPITAL LETTER S WITH CARON
REMAP(0x2039, 0x8B) // SINGLE LEFT-POINTING ANGLE QUOTATION MARK
REMAP(0x0152, 0x8C) // LATIN CAPITAL LIGATURE OE
REMAP(0x017D, 0x8E) // LATIN CAPITAL LETTER Z WITH CARON
REMAP(0x2018, 0x91) // LEFT SINGLE QUOTATION MARK
REMAP(0x2019, 0x92) // RIGHT SINGLE QUOTATION MARK
REMAP(0x201C, 0x93) // LEFT DOUBLE QUOTATION MARK
REMAP(0x201D, 0x94) // RIGHT DOUBLE QUOTATION MARK
REMAP(0x2022, 0x95) // BULLET
REMAP(0x2013, 0x96) // EN DASH
REMAP(0x2014, 0x97) // EM DASH
REMAP(0x02DC, 0x98) // SMALL TILDE
REMAP(0x2122, 0x99) // TRADE MARK SIGN
REMAP(0x0161, 0x9A) // LATIN SMALL LETTER S WITH CARON
REMAP(0x203A, 0x9B) // SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
REMAP(0x0153, 0x9C) // LATIN SMALL LIGATURE OE
REMAP(0x017E, 0x9E) // LATIN SMALL LETTER Z WITH CARON
REMAP(0x0178, 0x9F) // LATIN CAPITAL LETTER Y WITH DIAERESIS
REMAP(0x00A0, 0xA0) // NO-BREAK SPACE
REMAP(0x00A1, 0xA1) // INVERTED EXCLAMATION MARK
REMAP(0x00A2, 0xA2) // CENT SIGN
REMAP(0x00A3, 0xA3) // POUND SIGN
REMAP(0x00A4, 0xA4) // CURRENCY SIGN
REMAP(0x00A5, 0xA5) // YEN SIGN
REMAP(0x00A6, 0xA6) // BROKEN BAR
REMAP(0x00A7, 0xA7) // SECTION SIGN
REMAP(0x00A8, 0xA8) // DIAERESIS
REMAP(0x00A9, 0xA9) // COPYRIGHT SIGN
REMAP(0x00AA, 0xAA) // FEMININE ORDINAL INDICATOR
REMAP(0x00AB, 0xAB) // LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
REMAP(0x00AC, 0xAC) // NOT SIGN
REMAP(0x00AD, 0xAD) // SOFT HYPHEN
REMAP(0x00AE, 0xAE) // REGISTERED SIGN
REMAP(0x00AF, 0xAF) // MACRON
REMAP(0x00B0, 0xB0) // DEGREE SIGN
REMAP(0x00B1, 0xB1) // PLUS-MINUS SIGN
REMAP(0x00B2, 0xB2) // SUPERSCRIPT TWO
REMAP(0x00B3, 0xB3) // SUPERSCRIPT THREE
REMAP(0x00B4, 0xB4) // ACUTE ACCENT
REMAP(0x00B5, 0xB5) // MICRO SIGN
REMAP(0x00B6, 0xB6) // PILCROW SIGN
REMAP(0x00B7, 0xB7) // MIDDLE DOT
REMAP(0x00B8, 0xB8) // CEDILLA
REMAP(0x00B9, 0xB9) // SUPERSCRIPT ONE
REMAP(0x00BA, 0xBA) // MASCULINE ORDINAL INDICATOR
REMAP(0x00BB, 0xBB) // RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
REMAP(0x00BC, 0xBC) // VULGAR FRACTION ONE QUARTER
REMAP(0x00BD, 0xBD) // VULGAR FRACTION ONE HALF
REMAP(0x00BE, 0xBE) // VULGAR FRACTION THREE QUARTERS
REMAP(0x00BF, 0xBF) // INVERTED QUESTION MARK
REMAP(0x00C0, 0xC0) // LATIN CAPITAL LETTER A WITH GRAVE
REMAP(0x00C1, 0xC1) // LATIN CAPITAL LETTER A WITH ACUTE
REMAP(0x00C2, 0xC2) // LATIN CAPITAL LETTER A WITH CIRCUMFLEX
REMAP(0x00C3, 0xC3) // LATIN CAPITAL LETTER A WITH TILDE
REMAP(0x00C4, 0xC4) // LATIN CAPITAL LETTER A WITH DIAERESIS
REMAP(0x00C5, 0xC5) // LATIN CAPITAL LETTER A WITH RING ABOVE
REMAP(0x00C6, 0xC6) // LATIN CAPITAL LETTER AE
REMAP(0x00C7, 0xC7) // LATIN CAPITAL LETTER C WITH CEDILLA
REMAP(0x00C8, 0xC8) // LATIN CAPITAL LETTER E WITH GRAVE
REMAP(0x00C9, 0xC9) // LATIN CAPITAL LETTER E WITH ACUTE
REMAP(0x00CA, 0xCA) // LATIN CAPITAL LETTER E WITH CIRCUMFLEX
REMAP(0x00CB, 0xCB) // LATIN CAPITAL LETTER E WITH DIAERESIS
REMAP(0x00CC, 0xCC) // LATIN CAPITAL LETTER I WITH GRAVE
REMAP(0x00CD, 0xCD) // LATIN CAPITAL LETTER I WITH ACUTE
REMAP(0x00CE, 0xCE) // LATIN CAPITAL LETTER I WITH CIRCUMFLEX
REMAP(0x00CF, 0xCF) // LATIN CAPITAL LETTER I WITH DIAERESIS
REMAP(0x00D0, 0xD0) // LATIN CAPITAL LETTER ETH
REMAP(0x00D1, 0xD1) // LATIN CAPITAL LETTER N WITH TILDE
REMAP(0x00D2, 0xD2) // LATIN CAPITAL LETTER O WITH GRAVE
REMAP(0x00D3, 0xD3) // LATIN CAPITAL LETTER O WITH ACUTE
REMAP(0x00D4, 0xD4) // LATIN CAPITAL LETTER O WITH CIRCUMFLEX
REMAP(0x00D5, 0xD5) // LATIN CAPITAL LETTER O WITH TILDE
REMAP(0x00D6, 0xD6) // LATIN CAPITAL LETTER O WITH DIAERESIS
REMAP(0x00D7, 0xD7) // MULTIPLICATION SIGN
REMAP(0x00D8, 0xD8) // LATIN CAPITAL LETTER O WITH STROKE
REMAP(0x00D9, 0xD9) // LATIN CAPITAL LETTER U WITH GRAVE
REMAP(0x00DA, 0xDA) // LATIN CAPITAL LETTER U WITH ACUTE
REMAP(0x00DB, 0xDB) // LATIN CAPITAL LETTER U WITH CIRCUMFLEX
REMAP(0x00DC, 0xDC) // LATIN CAPITAL LETTER U WITH DIAERESIS
REMAP(0x00DD, 0xDD) // LATIN CAPITAL LETTER Y WITH ACUTE
REMAP(0x00DE, 0xDE) // LATIN CAPITAL LETTER THORN
REMAP(0x00DF, 0xDF) // LATIN SMALL LETTER SHARP S
REMAP(0x00E0, 0xE0) // LATIN SMALL LETTER A WITH GRAVE
REMAP(0x00E1, 0xE1) // LATIN SMALL LETTER A WITH ACUTE
REMAP(0x00E2, 0xE2) // LATIN SMALL LETTER A WITH CIRCUMFLEX
REMAP(0x00E3, 0xE3) // LATIN SMALL LETTER A WITH TILDE
REMAP(0x00E4, 0xE4) // LATIN SMALL LETTER A WITH DIAERESIS
REMAP(0x00E5, 0xE5) // LATIN SMALL LETTER A WITH RING ABOVE
REMAP(0x00E6, 0xE6) // LATIN SMALL LETTER AE
REMAP(0x00E7, 0xE7) // LATIN SMALL LETTER C WITH CEDILLA
REMAP(0x00E8, 0xE8) // LATIN SMALL LETTER E WITH GRAVE
REMAP(0x00E9, 0xE9) // LATIN SMALL LETTER E WITH ACUTE
REMAP(0x00EA, 0xEA) // LATIN SMALL LETTER E WITH CIRCUMFLEX
REMAP(0x00EB, 0xEB) // LATIN SMALL LETTER E WITH DIAERESIS
REMAP(0x00EC, 0xEC) // LATIN SMALL LETTER I WITH GRAVE
REMAP(0x00ED, 0xED) // LATIN SMALL LETTER I WITH ACUTE
REMAP(0x00EE, 0xEE) // LATIN SMALL LETTER I WITH CIRCUMFLEX
REMAP(0x00EF, 0xEF) // LATIN SMALL LETTER I WITH DIAERESIS
REMAP(0x00F0, 0xF0) // LATIN SMALL LETTER ETH
REMAP(0x00F1, 0xF1) // LATIN SMALL LETTER N WITH TILDE
REMAP(0x00F2, 0xF2) // LATIN SMALL LETTER O WITH GRAVE
REMAP(0x00F3, 0xF3) // LATIN SMALL LETTER O WITH ACUTE
REMAP(0x00F4, 0xF4) // LATIN SMALL LETTER O WITH CIRCUMFLEX
REMAP(0x00F5, 0xF5) // LATIN SMALL LETTER O WITH TILDE
REMAP(0x00F6, 0xF6) // LATIN SMALL LETTER O WITH DIAERESIS
REMAP(0x00F7, 0xF7) // DIVISION SIGN
REMAP(0x00F8, 0xF8) // LATIN SMALL LETTER O WITH STROKE
REMAP(0x00F9, 0xF9) // LATIN SMALL LETTER U WITH GRAVE
REMAP(0x00FA, 0xFA) // LATIN SMALL LETTER U WITH ACUTE
REMAP(0x00FB, 0xFB) // LATIN SMALL LETTER U WITH CIRCUMFLEX
REMAP(0x00FC, 0xFC) // LATIN SMALL LETTER U WITH DIAERESIS
REMAP(0x00FD, 0xFD) // LATIN SMALL LETTER Y WITH ACUTE
REMAP(0x00FE, 0xFE) // LATIN SMALL LETTER THORN
REMAP(0x00FF, 0xFF) // LATIN SMALL LETTER Y WITH DIAERESIS
}
}
else /*ASCII or Unhandled*/ {
if (utf8.length() == 1)
return utf8.at(0);
}
// All single-byte (ASCII) characters should have been handled by now
// Only unhandled multi-byte UTF8 characters should remain
assert(utf8.length() > 1);
// Parse emoji
// Strip emoji modifiers
switch (toUtf32(utf8)) {
REMAP(0x1F44D, 0x01) // 👍 Thumbs Up
REMAP(0x1F44E, 0x02) // 👎 Thumbs Down
REMAP(0x1F60A, 0x03) // 😊 Smiling Face with Smiling Eyes
REMAP(0x1F642, 0x03) // 🙂 Slightly Smiling Face
REMAP(0x1F601, 0x03) // 😁 Grinning Face with Smiling Eye
REMAP(0x1F602, 0x04) // 😂 Face with Tears of Joy
REMAP(0x1F923, 0x04) // 🤣 Rolling on the Floor Laughing
REMAP(0x1F606, 0x04) // 😆 Smiling with Open Mouth and Closed Eyes
REMAP(0x1F44B, 0x05) // 👋 Waving Hand
REMAP(0x02600, 0x06) // ☀ Sun
REMAP(0x1F31E, 0x06) // 🌞 Sun with Face
// 0x07 - Bell character (unused)
REMAP(0x1F327, 0x08) // 🌧️ Cloud with Rain
REMAP(0x02601, 0x09) // ☁️ Cloud
REMAP(0x1F32B, 0x09) // Fog
REMAP(0x1F9E1, 0x0B) // 🧡 Orange Heart
REMAP(0x02763, 0x0B) // ❣ Heart Exclamation
REMAP(0x02764, 0x0B) // ❤ Heart
REMAP(0x1F495, 0x0B) // 💕 Two Hearts
REMAP(0x1F496, 0x0B) // 💖 Sparkling Heart
REMAP(0x1F497, 0x0B) // 💗 Growing Heart
REMAP(0x1F498, 0x0B) // 💘 Heart with Arrow
REMAP(0x1F4A9, 0x0C) // 💩 Pile of Poo
// 0x0D - Carriage return (unused)
REMAP(0x1F514, 0x0E) // 🔔 Bell
REMAP(0x1F62D, 0x0F) // 😭 Loudly Crying Face
REMAP(0x1F622, 0x0F) // 😢 Crying Face
REMAP(0x1F64F, 0x10) // 🙏 Person with Folded Hands
REMAP(0x1F618, 0x11) // 😘 Face Throwing a Kiss
REMAP(0x1F389, 0x12) // 🎉 Party Popper
REMAP(0x1F600, 0x13) // 😀 Grinning Face
REMAP(0x1F603, 0x13) // 😃 Smiling Face with Open Mouth
REMAP(0x1F604, 0x13) // 😄 Smiling Face with Open Mouth and Smiling Eyes
REMAP(0x1F97A, 0x14) // 🥺 Face with Pleading Eyes
REMAP(0x1F605, 0x15) // 😅 Smiling with Sweat
REMAP(0x1F525, 0x16) // 🔥 Fire
REMAP(0x1F926, 0x17) // 🤦 Face Palm
REMAP(0x1F937, 0x18) // 🤷 Shrug
REMAP(0x1F644, 0x19) // 🙄 Face with Rolling Eyes
// 0x1A Substitution (unused)
REMAP(0x1F917, 0x1B) // 🤗 Hugging Face
REMAP(0x1F609, 0x1C) // 😉 Winking Face
REMAP(0x1F61C, 0x1C) // 😜 Face with Stuck-Out Tongue and Winking Eye
REMAP(0x1F60F, 0x1C) // 😏 Smirking Face
REMAP(0x1F914, 0x1D) // 🤔 Thinking Face
REMAP(0x1FAE1, 0x1E) // 🫡 Saluting Face
REMAP(0x1F44C, 0x1F) // 👌 OK Hand Sign
REMAP(0x02755, '!') // ❕
REMAP(0x02757, '!') // ❗
REMAP(0x0203C, '!') // ‼
REMAP(0x02753, '?') // ❓
REMAP(0x02754, '?') // ❔
REMAP(0x02049, '?') // ⁉
// Modifiers (deleted)
REMAP(0x02640, 0x7F) // Gender
REMAP(0x02642, 0x7F)
REMAP(0x1F3FB, 0x7F) // Skin Tones
REMAP(0x1F3FC, 0x7F)
REMAP(0x1F3FD, 0x7F)
REMAP(0x1F3FE, 0x7F)
REMAP(0x1F3FF, 0x7F)
REMAP(0x0FE00, 0x7F) // Variation Selectors
REMAP(0x0FE01, 0x7F)
REMAP(0x0FE02, 0x7F)
REMAP(0x0FE03, 0x7F)
REMAP(0x0FE04, 0x7F)
REMAP(0x0FE05, 0x7F)
REMAP(0x0FE06, 0x7F)
REMAP(0x0FE07, 0x7F)
REMAP(0x0FE08, 0x7F)
REMAP(0x0FE09, 0x7F)
REMAP(0x0FE0A, 0x7F)
REMAP(0x0FE0B, 0x7F)
REMAP(0x0FE0C, 0x7F)
REMAP(0x0FE0D, 0x7F)
REMAP(0x0FE0E, 0x7F)
REMAP(0x0FE0F, 0x7F)
REMAP(0x0200D, 0x7F) // Zero Width Joiner
}
// If not handled, return SUB
return '\x1A';
// Sweep up the syntactic sugar
// Don't want ants in the house
#undef REMAP
}
#endif
@@ -0,0 +1,87 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
Wrapper class for an AdafruitGFX font
Pre-calculates some font dimension info which InkHUD uses repeatedly
Re-encodes UTF-8 characters to suit extended ASCII AdafruitGFX fonts
*/
#pragma once
#include "configuration.h"
#include <GFX.h> // GFXRoot drawing lib
namespace NicheGraphics::InkHUD
{
// An AdafruitGFX font, bundled with precalculated dimensions which are used frequently by InkHUD
class AppletFont
{
public:
enum Encoding {
ASCII,
WINDOWS_1250,
WINDOWS_1251,
WINDOWS_1252,
};
AppletFont();
AppletFont(const GFXfont &adafruitGFXFont, Encoding encoding = ASCII, int8_t paddingTop = 0, int8_t paddingBottom = 0);
uint8_t lineHeight();
uint8_t heightAboveCursor();
uint8_t heightBelowCursor();
uint8_t widthBetweenWords(); // Width of the space character
std::string decodeUTF8(std::string encoded);
const GFXfont *gfxFont = NULL; // Default value: in-built AdafruitGFX font
private:
uint32_t toUtf32(std::string utf8);
char applyEncoding(std::string utf8);
uint8_t height = 8; // Default value: in-built AdafruitGFX font
uint8_t ascenderHeight = 0; // Default value: in-built AdafruitGFX font
uint8_t descenderHeight = 8; // Default value: in-built AdafruitGFX font
uint8_t spaceCharWidth = 8; // Default value: in-built AdafruitGFX font
Encoding encoding = ASCII;
};
} // namespace NicheGraphics::InkHUD
// Macros for InkHUD's standard fonts
// --------------------------------------
// Use these once only, passing them to InkHUD::Applet::fontLarge and InkHUD::Applet:fontSmall
// Line padding has been adjusted manually, to compensate for a few *extra tall* diacritics
// Central European
#include "graphics/niche/Fonts/FreeSans12pt_Win1250.h"
#include "graphics/niche/Fonts/FreeSans6pt_Win1250.h"
#include "graphics/niche/Fonts/FreeSans9pt_Win1250.h"
#define FREESANS_12PT_WIN1250 InkHUD::AppletFont(FreeSans12pt_Win1250, InkHUD::AppletFont::WINDOWS_1250, -3, 1)
#define FREESANS_9PT_WIN1250 InkHUD::AppletFont(FreeSans9pt_Win1250, InkHUD::AppletFont::WINDOWS_1250, -1, -1)
#define FREESANS_6PT_WIN1250 InkHUD::AppletFont(FreeSans6pt_Win1250, InkHUD::AppletFont::WINDOWS_1250, -1, -2)
// Cyrillic
#include "graphics/niche/Fonts/FreeSans12pt_Win1251.h"
#include "graphics/niche/Fonts/FreeSans6pt_Win1251.h"
#include "graphics/niche/Fonts/FreeSans9pt_Win1251.h"
#define FREESANS_12PT_WIN1251 InkHUD::AppletFont(FreeSans12pt_Win1251, InkHUD::AppletFont::WINDOWS_1251, -3, 1)
#define FREESANS_9PT_WIN1251 InkHUD::AppletFont(FreeSans9pt_Win1251, InkHUD::AppletFont::WINDOWS_1251, -2, -1)
#define FREESANS_6PT_WIN1251 InkHUD::AppletFont(FreeSans6pt_Win1251, InkHUD::AppletFont::WINDOWS_1251, -1, -2)
// Western European
#include "graphics/niche/Fonts/FreeSans12pt_Win1252.h"
#include "graphics/niche/Fonts/FreeSans6pt_Win1252.h"
#include "graphics/niche/Fonts/FreeSans9pt_Win1252.h"
#define FREESANS_12PT_WIN1252 InkHUD::AppletFont(FreeSans12pt_Win1252, InkHUD::AppletFont::WINDOWS_1252, -3, 1)
#define FREESANS_9PT_WIN1252 InkHUD::AppletFont(FreeSans9pt_Win1252, InkHUD::AppletFont::WINDOWS_1252, -2, -1)
#define FREESANS_6PT_WIN1252 InkHUD::AppletFont(FreeSans6pt_Win1252, InkHUD::AppletFont::WINDOWS_1252, -1, -2)
#endif
@@ -0,0 +1,558 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./MapApplet.h"
using namespace NicheGraphics;
void InkHUD::MapApplet::onRender()
{
// Abort if no markers to render
if (!enoughMarkers()) {
printAt(X(0.5), Y(0.5) - (getFont().lineHeight() / 2), "Node positions", CENTER, MIDDLE);
printAt(X(0.5), Y(0.5) + (getFont().lineHeight() / 2), "will appear here", CENTER, MIDDLE);
return;
}
// Helper: draw rounded rectangle centered at x,y
auto fillRoundedRect = [&](int16_t cx, int16_t cy, int16_t w, int16_t h, int16_t r, uint16_t color) {
int16_t x = cx - (w / 2);
int16_t y = cy - (h / 2);
// center rects
fillRect(x + r, y, w - 2 * r, h, color);
fillRect(x, y + r, r, h - 2 * r, color);
fillRect(x + w - r, y + r, r, h - 2 * r, color);
// corners
fillCircle(x + r, y + r, r, color);
fillCircle(x + w - r - 1, y + r, r, color);
fillCircle(x + r, y + h - r - 1, r, color);
fillCircle(x + w - r - 1, y + h - r - 1, r, color);
};
// Find center of map
getMapCenter(&latCenter, &lngCenter);
calculateAllMarkers();
getMapSize(&widthMeters, &heightMeters);
calculateMapScale();
// Draw all markers first
for (Marker m : markers) {
int16_t x = X(0.5) + (m.eastMeters * metersToPx);
int16_t y = Y(0.5) - (m.northMeters * metersToPx);
// Add white halo outline first
constexpr int outlinePad = 1;
int boxSize = 11;
int radius = 2; // rounded corner radius
// White halo background
fillRoundedRect(x, y, boxSize + (outlinePad * 2), boxSize + (outlinePad * 2), radius + 1, WHITE);
// Draw inner box
fillRoundedRect(x, y, boxSize, boxSize, radius, BLACK);
// Text inside
setFont(fontSmall);
setTextColor(WHITE);
// Draw actual marker on top
if (m.hasHopsAway && m.hopsAway > config.lora.hop_limit) {
printAt(x + 1, y + 1, "X", CENTER, MIDDLE);
} else if (!m.hasHopsAway) {
printAt(x + 1, y + 1, "?", CENTER, MIDDLE);
} else {
char hopStr[4];
snprintf(hopStr, sizeof(hopStr), "%d", m.hopsAway);
printAt(x, y + 1, hopStr, CENTER, MIDDLE);
}
// Restore default font and color
setFont(fontSmall);
setTextColor(BLACK);
}
// Dual map scale bars
int16_t horizPx = width() * 0.25f;
int16_t vertPx = height() * 0.25f;
float horizMeters = horizPx / metersToPx;
float vertMeters = vertPx / metersToPx;
auto formatDistance = [&](float meters, char *out, size_t len) {
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
float feet = meters * 3.28084f;
if (feet < 528)
snprintf(out, len, "%.0f ft", feet);
else {
float miles = feet / 5280.0f;
snprintf(out, len, miles < 10 ? "%.1f mi" : "%.0f mi", miles);
}
} else {
if (meters >= 1000)
snprintf(out, len, "%.1f km", meters / 1000.0f);
else
snprintf(out, len, "%.0f m", meters);
}
};
// Horizontal scale bar
int16_t horizBarY = height() - 2;
int16_t horizBarX = 1;
drawLine(horizBarX, horizBarY, horizBarX + horizPx, horizBarY, BLACK);
drawLine(horizBarX, horizBarY - 3, horizBarX, horizBarY + 3, BLACK);
drawLine(horizBarX + horizPx, horizBarY - 3, horizBarX + horizPx, horizBarY + 3, BLACK);
char horizLabel[32];
formatDistance(horizMeters, horizLabel, sizeof(horizLabel));
int16_t horizLabelW = getTextWidth(horizLabel);
int16_t horizLabelH = getFont().lineHeight();
int16_t horizLabelX = horizBarX + horizPx + 4;
int16_t horizLabelY = horizBarY - horizLabelH + 1;
fillRect(horizLabelX - 2, horizLabelY - 1, horizLabelW + 4, horizLabelH + 2, WHITE);
printAt(horizLabelX, horizBarY, horizLabel, LEFT, BOTTOM);
// Vertical scale bar
int16_t vertBarX = 1;
int16_t vertBarBottom = horizBarY;
int16_t vertBarTop = vertBarBottom - vertPx;
drawLine(vertBarX, vertBarBottom, vertBarX, vertBarTop, BLACK);
drawLine(vertBarX - 3, vertBarBottom, vertBarX + 3, vertBarBottom, BLACK);
drawLine(vertBarX - 3, vertBarTop, vertBarX + 3, vertBarTop, BLACK);
char vertTopLabel[32];
formatDistance(vertMeters, vertTopLabel, sizeof(vertTopLabel));
int16_t topLabelY = vertBarTop - getFont().lineHeight() - 2;
int16_t topLabelW = getTextWidth(vertTopLabel);
int16_t topLabelH = getFont().lineHeight();
fillRect(vertBarX - 2, topLabelY - 1, topLabelW + 6, topLabelH + 2, WHITE);
printAt(vertBarX + (topLabelW / 2) + 1, topLabelY + (topLabelH / 2), vertTopLabel, CENTER, MIDDLE);
char vertBottomLabel[32];
formatDistance(vertMeters, vertBottomLabel, sizeof(vertBottomLabel));
int16_t bottomLabelY = vertBarBottom + 4;
int16_t bottomLabelW = getTextWidth(vertBottomLabel);
int16_t bottomLabelH = getFont().lineHeight();
fillRect(vertBarX - 2, bottomLabelY - 1, bottomLabelW + 6, bottomLabelH + 2, WHITE);
printAt(vertBarX + (bottomLabelW / 2) + 1, bottomLabelY + (bottomLabelH / 2), vertBottomLabel, CENTER, MIDDLE);
// Draw our node LAST with full white fill + outline
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
if (ourNode && nodeDB->hasValidPosition(ourNode)) {
Marker self = calculateMarker(ourNode->position.latitude_i * 1e-7, ourNode->position.longitude_i * 1e-7, false, 0);
int16_t centerX = X(0.5) + (self.eastMeters * metersToPx);
int16_t centerY = Y(0.5) - (self.northMeters * metersToPx);
// White fill background + halo
fillCircle(centerX, centerY, 8, WHITE); // big white base
drawCircle(centerX, centerY, 8, WHITE); // crisp edge
// Black bullseye on top
drawCircle(centerX, centerY, 6, BLACK);
fillCircle(centerX, centerY, 2, BLACK);
// Crosshairs
drawLine(centerX - 8, centerY, centerX + 8, centerY, BLACK);
drawLine(centerX, centerY - 8, centerX, centerY + 8, BLACK);
}
}
// Find the center point, in the middle of all node positions
// Calculated values are written to the *lat and *long pointer args
// - Finds the "mean lat long"
// - Calculates furthest nodes from "mean lat long"
// - Place map center directly between these furthest nodes
void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
{
// If we have a valid position for our own node, use that as the anchor
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
if (ourNode && nodeDB->hasValidPosition(ourNode)) {
*lat = ourNode->position.latitude_i * 1e-7;
*lng = ourNode->position.longitude_i * 1e-7;
} else {
// Find mean lat long coords
// ============================
// - assigning X, Y and Z values to position on Earth's surface in 3D space, relative to center of planet
// - averages the x, y and z coords
// - uses tan to find angles for lat / long degrees
// - longitude: triangle formed by x and y (on plane of the equator)
// - latitude: triangle formed by z (north south),
// and the line along plane of equator which stretches from earth's axis to where point xyz intersects planet's
// surface
// Working totals, averaged after nodeDB processed
uint32_t positionCount = 0;
float xAvg = 0;
float yAvg = 0;
float zAvg = 0;
// For each node in db
for (uint32_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
// Skip if no position
if (!nodeDB->hasValidPosition(node))
continue;
// Skip if derived applet doesn't want to show this node on the map
if (!shouldDrawNode(node))
continue;
// Latitude and Longitude of node, in radians
float latRad = node->position.latitude_i * (1e-7) * DEG_TO_RAD;
float lngRad = node->position.longitude_i * (1e-7) * DEG_TO_RAD;
// Convert to cartesian points, with center of earth at 0, 0, 0
// Exact distance from center is irrelevant, as we're only interested in the vector
float x = cos(latRad) * cos(lngRad);
float y = cos(latRad) * sin(lngRad);
float z = sin(latRad);
// To find mean values shortly
xAvg += x;
yAvg += y;
zAvg += z;
positionCount++;
}
// All NodeDB processed, find mean values
xAvg /= positionCount;
yAvg /= positionCount;
zAvg /= positionCount;
// Longitude from cartesian coords
// (Angle from 3D coords describing a point of globe's surface)
/*
UK
/-------\
(Top View) /- -\
/- (You) -\
/- . -\
/- . X -\
Asia - ... - USA
\- Y -/
\- -/
\- -/
\- -/
\- -----/
Pacific
*/
*lng = atan2(yAvg, xAvg) * RAD_TO_DEG;
// Latitude from cartesian coords
// (Angle from 3D coords describing a point on the globe's surface)
// As latitude increases, distance from the Earth's north-south axis out to our surface point decreases.
// Means we need to first find the hypotenuse which becomes base of our triangle in the second step
/*
UK North
/-------\ (Front View) /-------\
(Top View) /- -\ /- -\
/- (You) -\ /-(You) -\
/- /. -\ /- . -\
/- √X²+Y²/ . X -\ /- Z . -\
Asia - /... - USA - ..... -
\- Y -/ \- √X²+Y² -/
\- -/ \- -/
\- -/ \- -/
\- -/ \- -/
\- -----/ \- -----/
Pacific South
*/
float hypotenuse = sqrt((xAvg * xAvg) + (yAvg * yAvg)); // Distance from globe's north-south axis to surface intersect
*lat = atan2(zAvg, hypotenuse) * RAD_TO_DEG;
}
// Use either our node position, or the mean fallback as the center
latCenter = *lat;
lngCenter = *lng;
// ----------------------------------------------
// This has given us either:
// - our actual position (preferred), or
// - a mean position (fallback if we had no fix)
//
// What we actually want is to place our center so that our outermost nodes
// end up on the border of our map. The only real use of our "center" is to give
// us a reference frame: which direction is east, and which is west.
//------------------------------------------------
// Find furthest nodes from our center
// ========================================
float northernmost = latCenter;
float southernmost = latCenter;
float easternmost = lngCenter;
float westernmost = lngCenter;
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
// Skip if no position
if (!nodeDB->hasValidPosition(node))
continue;
// Skip if derived applet doesn't want to show this node on the map
if (!shouldDrawNode(node))
continue;
// Check for a new top or bottom latitude
float latNode = node->position.latitude_i * 1e-7;
northernmost = max(northernmost, latNode);
southernmost = min(southernmost, latNode);
// Longitude is trickier
float lngNode = node->position.longitude_i * 1e-7;
float degEastward = fmod(((lngNode - lngCenter) + 360), 360); // Degrees traveled east from lngCenter to reach node
float degWestward = abs(fmod(((lngNode - lngCenter) - 360), 360)); // Degrees traveled west from lngCenter to reach node
if (degEastward < degWestward)
easternmost = max(easternmost, lngCenter + degEastward);
else
westernmost = min(westernmost, lngCenter - degWestward);
}
// Todo: check for issues with map spans >180 deg. MQTT only..
latCenter = (northernmost + southernmost) / 2;
lngCenter = (westernmost + easternmost) / 2;
// In case our new center is west of -180, or east of +180, for some reason
lngCenter = fmod(lngCenter, 180);
}
// Size of map in meters
// Grown to fit the nodes furthest from map center
// Overridable if derived applet wants a custom map size (fixed size?)
void InkHUD::MapApplet::getMapSize(uint32_t *widthMeters, uint32_t *heightMeters)
{
// Reset the value
*widthMeters = 0;
*heightMeters = 0;
// Find the greatest distance horizontally and vertically from map center
for (Marker m : markers) {
*widthMeters = max(*widthMeters, (uint32_t)abs(m.eastMeters) * 2);
*heightMeters = max(*heightMeters, (uint32_t)abs(m.northMeters) * 2);
}
// Add padding
*widthMeters *= 1.1;
*heightMeters *= 1.1;
}
// Convert and store info we need for drawing a marker
// Lat / long to "meters relative to map center", for position on screen
// Info about hopsAway, for marker size
InkHUD::MapApplet::Marker InkHUD::MapApplet::calculateMarker(float lat, float lng, bool hasHopsAway, uint8_t hopsAway)
{
assert(lat != 0 || lng != 0); // Not null island. Applets should check this before calling.
// Bearing and distance from map center to node
float distanceFromCenter = GeoCoord::latLongToMeter(latCenter, lngCenter, lat, lng);
float bearingFromCenter = GeoCoord::bearing(latCenter, lngCenter, lat, lng); // in radians
// Split into meters north and meters east components (signed)
// - signedness of cos / sin automatically sets negative if south or west
float northMeters = cos(bearingFromCenter) * distanceFromCenter;
float eastMeters = sin(bearingFromCenter) * distanceFromCenter;
// Store this as a new marker
Marker m;
m.eastMeters = eastMeters;
m.northMeters = northMeters;
m.hasHopsAway = hasHopsAway;
m.hopsAway = hopsAway;
return m;
}
// Draw a marker on the map for a node, with a shortname label, and backing box
void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
{
// Find x and y position based on node's position in nodeDB
assert(nodeDB->hasValidPosition(node));
Marker m = calculateMarker(node->position.latitude_i * 1e-7, // Lat, converted from Meshtastic's internal int32 style
node->position.longitude_i * 1e-7, // Long, converted from Meshtastic's internal int32 style
node->has_hops_away, // Is the hopsAway number valid
node->hops_away // Hops away
);
// Convert to pixel coords
int16_t markerX = X(0.5) + (m.eastMeters * metersToPx);
int16_t markerY = Y(0.5) - (m.northMeters * metersToPx);
constexpr uint16_t paddingH = 2;
constexpr uint16_t paddingW = 4;
uint16_t paddingInnerW = 2; // Zero'd out if no text
constexpr uint16_t markerSizeMax = 12; // Size of cross (if marker uses a cross)
constexpr uint16_t markerSizeMin = 5;
int16_t textX;
int16_t textY;
uint16_t textW;
uint16_t textH;
int16_t labelX;
int16_t labelY;
uint16_t labelW;
uint16_t labelH;
uint8_t markerSize;
bool tooManyHops = node->hops_away > config.lora.hop_limit;
bool isOurNode = node->num == nodeDB->getNodeNum();
bool unknownHops = !node->has_hops_away && !isOurNode;
// Parse any non-ascii chars in the short name,
// and use last 4 instead if unknown / can't render
std::string shortName = parseShortName(node);
// We will draw a left or right hand variant, to place text towards screen center
// Hopefully avoid text spilling off screen
// Most values are the same, regardless of left-right handedness
// Pick emblem style
if (tooManyHops)
markerSize = getTextWidth("!");
else if (unknownHops)
markerSize = markerSizeMin;
else
markerSize = map(node->hops_away, 0, config.lora.hop_limit, markerSizeMax, markerSizeMin);
// Common dimensions (left or right variant)
textW = getTextWidth(shortName);
if (textW == 0)
paddingInnerW = 0; // If no text, no padding for text
textH = fontSmall.lineHeight();
labelH = paddingH + max((int16_t)(textH), (int16_t)markerSize) + paddingH;
labelY = markerY - (labelH / 2);
textY = markerY;
labelW = paddingW + markerSize + paddingInnerW + textW + paddingW; // Width is same whether right or left hand variant
// Left-side variant
if (markerX < width() / 2) {
labelX = markerX - (markerSize / 2) - paddingW;
textX = labelX + paddingW + markerSize + paddingInnerW;
}
// Right-side variant
else {
labelX = markerX - (markerSize / 2) - paddingInnerW - textW - paddingW;
textX = labelX + paddingW;
}
// Prevent overlap with scale bars and their labels
// Define a "safe zone" in the bottom-left where the scale bars and text are drawn
constexpr int16_t safeZoneHeight = 28; // adjust based on your label font height
constexpr int16_t safeZoneWidth = 60; // adjust based on horizontal label width zone
bool overlapsScale = (labelY + labelH > height() - safeZoneHeight) && (labelX < safeZoneWidth);
// If it overlaps, shift label upward slightly above the safe zone
if (overlapsScale) {
labelY = height() - safeZoneHeight - labelH - 2;
textY = labelY + (labelH / 2);
}
// Backing box
fillRect(labelX, labelY, labelW, labelH, WHITE);
drawRect(labelX, labelY, labelW, labelH, BLACK);
// Short name
printAt(textX, textY, shortName, LEFT, MIDDLE);
// If the label is for our own node,
// fade it by overdrawing partially with white
if (node == nodeDB->getMeshNode(nodeDB->getNodeNum()))
hatchRegion(labelX, labelY, labelW, labelH, 2, WHITE);
// Draw the marker emblem
// - after the fading, because hatching (own node) can align with cross and make it look weird
if (tooManyHops)
printAt(markerX, markerY, "!", CENTER, MIDDLE);
else
drawCross(markerX, markerY, markerSize); // The fewer the hops, the larger the marker. Also handles unknownHops
}
// Check if we actually have enough nodes which would be shown on the map
// Need at least two, to draw a sensible map
bool InkHUD::MapApplet::enoughMarkers()
{
size_t count = 0;
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
// Count nodes
if (nodeDB->hasValidPosition(node) && shouldDrawNode(node))
count++;
// We need to find two
if (count == 2)
return true; // Two nodes is enough for a sensible map
}
return false; // No nodes would be drawn (or just the one, uselessly at 0,0)
}
// Calculate how far north and east of map center each node is
// Derived applets can control which nodes to calculate (and later, draw) by overriding MapApplet::shouldDrawNode
void InkHUD::MapApplet::calculateAllMarkers()
{
// Clear old markers
markers.clear();
// For each node in db
for (uint32_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
// Skip if no position
if (!nodeDB->hasValidPosition(node))
continue;
// Skip if derived applet doesn't want to show this node on the map
if (!shouldDrawNode(node))
continue;
// Skip if our own node
// - special handling in render()
if (node->num == nodeDB->getNodeNum())
continue;
// Calculate marker and store it
markers.push_back(
calculateMarker(node->position.latitude_i * 1e-7, // Lat, converted from Meshtastic's internal int32 style
node->position.longitude_i * 1e-7, // Long, converted from Meshtastic's internal int32 style
node->has_hops_away, // Is the hopsAway number valid
node->hops_away // Hops away
));
}
}
// Determine the conversion factor between metres, and pixels on screen
// May be overriden by derived applet, if custom scale required (fixed map size?)
void InkHUD::MapApplet::calculateMapScale()
{
// Aspect ratio of map and screen
// - larger = wide, smaller = tall
// - used to set scale, so that widest map dimension fits in applet
float mapAspectRatio = (float)widthMeters / heightMeters;
float appletAspectRatio = (float)width() / height();
// "Shrink to fit"
// Scale the map so that the largest dimension is fully displayed
// Because aspect ratio will be maintained, the other dimension will appear "padded"
if (mapAspectRatio > appletAspectRatio)
metersToPx = (float)width() / widthMeters; // Too wide for applet. Constrain to fit width.
else
metersToPx = (float)height() / heightMeters; // Too tall for applet. Constrain to fit height.
}
// Draw an x, centered on a specific point
// Most markers will draw with this method
void InkHUD::MapApplet::drawCross(int16_t x, int16_t y, uint8_t size)
{
int16_t x0 = x - (size / 2);
int16_t y0 = y - (size / 2);
int16_t x1 = x0 + size - 1;
int16_t y1 = y0 + size - 1;
drawLine(x0, y0, x1, y1, BLACK);
drawLine(x0, y1, x1, y0, BLACK);
}
#endif
@@ -0,0 +1,65 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
Base class for Applets which show nodes on a map
Plots position of for a selection of nodes, with north facing up.
Size of cross represents hops away.
Our own node is identified with a faded label.
The base applet doesn't handle any events; this is left to the derived applets.
*/
#pragma once
#include "configuration.h"
#include "graphics/niche/InkHUD/Applet.h"
#include "MeshModule.h"
#include "gps/GeoCoord.h"
namespace NicheGraphics::InkHUD
{
class MapApplet : public Applet
{
public:
void onRender() override;
protected:
virtual bool shouldDrawNode(meshtastic_NodeInfoLite *node) { return true; } // Allow derived applets to filter the nodes
virtual void getMapCenter(float *lat, float *lng);
virtual void getMapSize(uint32_t *widthMeters, uint32_t *heightMeters);
bool enoughMarkers(); // Anything to draw?
void drawLabeledMarker(meshtastic_NodeInfoLite *node); // Highlight a specific marker
private:
// Position and size of a marker to be drawn
struct Marker {
float eastMeters = 0; // Meters east of map center. Negative if west.
float northMeters = 0; // Meters north of map center. Negative if south.
bool hasHopsAway = false;
uint8_t hopsAway = 0; // Determines marker size
};
Marker calculateMarker(float lat, float lng, bool hasHopsAway, uint8_t hopsAway);
void calculateAllMarkers();
void calculateMapScale(); // Conversion factor for meters to pixels
void drawCross(int16_t x, int16_t y, uint8_t size); // Draw the X used for most markers
float metersToPx = 0; // Conversion factor for meters to pixels
float latCenter = 0; // Map center: latitude
float lngCenter = 0; // Map center: longitude
std::list<Marker> markers;
uint32_t widthMeters = 0; // Map width: meters
uint32_t heightMeters = 0; // Map height: meters
};
} // namespace NicheGraphics::InkHUD
#endif
@@ -0,0 +1,291 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "RTC.h"
#include "GeoCoord.h"
#include "NodeDB.h"
#include "./NodeListApplet.h"
using namespace NicheGraphics;
InkHUD::NodeListApplet::NodeListApplet(const char *name) : MeshModule(name)
{
// We only need to be promiscuous in order to hear NodeInfo, apparently. See NodeInfoModule
// For all other packets, we manually act as if isPromiscuous=false, in wantPacket
MeshModule::isPromiscuous = true;
}
// Do we want to process this packet with handleReceived()?
bool InkHUD::NodeListApplet::wantPacket(const meshtastic_MeshPacket *p)
{
// Only interested if:
return isActive() // Applet is active
&& !isFromUs(p) // Packet is incoming (not outgoing)
&& (isToUs(p) || isBroadcast(p->to) || // Either: intended for us,
p->decoded.portnum == meshtastic_PortNum_NODEINFO_APP); // or nodeinfo
// To match the behavior seen in the client apps:
// - NodeInfoModule's ProtoBufModule base is "promiscuous"
// - All other activity is *not* promiscuous
// To achieve this, our MeshModule *is* promiscuous, and we're manually reimplementing non-promiscuous behavior here,
// to match the code in MeshModule::callModules
}
// MeshModule packets arrive here
// Extract the info and pass it to the derived applet
// Derived applet will store the CardInfo, and perform any required sorting of the CardInfo collection
// Derived applet might also need to keep other tallies (active nodes count?)
ProcessMessage InkHUD::NodeListApplet::handleReceived(const meshtastic_MeshPacket &mp)
{
// Abort if applet fully deactivated
// Already handled by wantPacket in this case, but good practice for all applets, as some *do* require this early return
if (!isActive())
return ProcessMessage::CONTINUE;
// Assemble info: from this event
CardInfo c;
c.nodeNum = mp.from;
c.signal = getSignalStrength(mp.rx_snr, mp.rx_rssi);
// Assemble info: from nodeDB (needed to detect changes)
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(c.nodeNum);
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
if (node) {
if (node->has_hops_away)
c.hopsAway = node->hops_away;
if (nodeDB->hasValidPosition(node) && nodeDB->hasValidPosition(ourNode)) {
// Get lat and long as float
// Meshtastic stores these as integers internally
float ourLat = ourNode->position.latitude_i * 1e-7;
float ourLong = ourNode->position.longitude_i * 1e-7;
float theirLat = node->position.latitude_i * 1e-7;
float theirLong = node->position.longitude_i * 1e-7;
c.distanceMeters = (int32_t)GeoCoord::latLongToMeter(theirLat, theirLong, ourLat, ourLong);
}
}
// Pass to the derived applet
// Derived applet is responsible for requesting update, if justified
// That request will eventually trigger our class' onRender method
handleParsed(c);
return ProcessMessage::CONTINUE; // Let others look at this message also if they want
}
// Calculate maximum number of cards we may ever need to render, in our tallest layout config
// Number might be slightly in excess of the true value: applet header text not accounted for
uint8_t InkHUD::NodeListApplet::maxCards()
{
// Cache result. Shouldn't change during execution
static uint8_t cards = 0;
if (!cards) {
const uint16_t height = Tile::maxDisplayDimension();
// Use a loop instead of arithmetic, because it's easier for my brain to follow
// Add cards one by one, until the latest card extends below screen
uint16_t y = cardH; // First card: no margin above
cards = 1;
while (y < height) {
y += cardMarginH;
y += cardH;
cards++;
}
}
return cards;
}
// Draw, using info which derived applet placed into NodeListApplet::cards for us
void InkHUD::NodeListApplet::onRender()
{
// ================================
// Draw the standard applet header
// ================================
drawHeader(getHeaderText()); // Ask derived applet for the title
// Dimensions of the header
int16_t headerDivY = getHeaderHeight() - 1;
constexpr uint16_t padDivH = 2;
// ========================
// Draw the main node list
// ========================
// Imaginary vertical line dividing left-side and right-side info
// Long-name will crop here
const uint16_t dividerX = (width() - 1) - getTextWidth("X Hops");
// Y value (top) of the current card. Increases as we draw.
uint16_t cardTopY = headerDivY + padDivH;
// Clean up deleted nodes before drawing
cards.erase(
std::remove_if(cards.begin(), cards.end(), [](const CardInfo &c) { return nodeDB->getMeshNode(c.nodeNum) == nullptr; }),
cards.end());
// -- Each node in list --
for (auto card = cards.begin(); card != cards.end(); ++card) {
// Gather info
// ========================================
NodeNum &nodeNum = card->nodeNum;
SignalStrength &signal = card->signal;
std::string longName; // handled below
std::string shortName; // handled below
std::string distance; // handled below;
uint8_t &hopsAway = card->hopsAway;
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeNum);
// Skip deleted nodes
if (!node) {
continue;
}
// -- Shortname --
// Parse special chars in the short name
// Use "?" if unknown
if (node)
shortName = parseShortName(node);
else
shortName = "?";
// -- Longname --
// Parse special chars in long name
// Use node id if unknown
if (node && node->has_user)
longName = parse(node->user.long_name); // Found in nodeDB
else {
// Not found in nodeDB, show a hex nodeid instead
longName = hexifyNodeNum(nodeNum);
}
// -- Distance --
if (card->distanceMeters != CardInfo::DISTANCE_UNKNOWN)
distance = localizeDistance(card->distanceMeters);
// Draw the info
// ====================================
// Define two lines of text for the card
// We will center our text on these lines
uint16_t lineAY = cardTopY + (fontMedium.lineHeight() / 2);
uint16_t lineBY = cardTopY + fontMedium.lineHeight() + (fontSmall.lineHeight() / 2);
// Print the short name
setFont(fontMedium);
printAt(0, lineAY, shortName, LEFT, MIDDLE);
// Print the distance
setFont(fontSmall);
printAt(width() - 1, lineBY, distance, RIGHT, MIDDLE);
// If we have a direct connection to the node, draw the signal indicator
if (hopsAway == 0 && signal != SIGNAL_UNKNOWN) {
uint16_t signalW = getTextWidth("Xkm"); // Indicator should be similar width to distance label
uint16_t signalH = fontMedium.lineHeight() * 0.75;
int16_t signalY = lineAY + (fontMedium.lineHeight() / 2) - (fontMedium.lineHeight() * 0.75);
int16_t signalX = width() - signalW;
drawSignalIndicator(signalX, signalY, signalW, signalH, signal);
}
// Otherwise, print "hops away" info, if available
else if (hopsAway != CardInfo::HOPS_UNKNOWN && node) {
std::string hopString = to_string(node->hops_away);
hopString += " Hop";
if (node->hops_away != 1)
hopString += "s"; // Append s for "Hops", rather than "Hop"
printAt(width() - 1, lineAY, hopString, RIGHT, MIDDLE);
}
// Print the long name, cropping to prevent overflow onto the right-side info
setCrop(0, 0, dividerX - 1, height());
printAt(0, lineBY, longName, LEFT, MIDDLE);
// GFX effect: "hatch" the right edge of longName area
// If a longName has been cropped, it will appear to fade out,
// creating a soft barrier with the right-side info
const int16_t hatchLeft = dividerX - 1 - (fontSmall.lineHeight());
const int16_t hatchWidth = fontSmall.lineHeight();
hatchRegion(hatchLeft, cardTopY, hatchWidth, cardH, 2, WHITE);
// Prepare to draw the next card
resetCrop();
cardTopY += cardH;
// Once we've run out of screen, stop drawing cards
// Depending on tiles / rotation, this may be before we hit maxCards
if (cardTopY > height())
break;
}
}
// Draw element: a "mobile phone" style signal indicator
// We will calculate values as floats, then "rasterize" at the last moment, relative to x and w, etc
// This prevents issues with premature rounding when rendering tiny elements
void InkHUD::NodeListApplet::drawSignalIndicator(int16_t x, int16_t y, uint16_t w, uint16_t h, SignalStrength strength)
{
/*
+-------------------------------------------+
| |
| |
| barHeightRelative=1.0
| +--+ ^ |
| gutterW +--+ | | | |
| <--> +--+ | | | | | |
| +--+ | | | | | | | |
| | | | | | | | | | |
| <-> +--+ +--+ +--+ +--+ v |
| paddingW ^ |
| paddingH | |
| v |
+-------------------------------------------+
*/
constexpr float paddingW = 0.1; // Either side
constexpr float paddingH = 0.1; // Above and below
constexpr float gutterW = 0.1; // Between bars
constexpr float barHRel[] = {0.3, 0.5, 0.7, 1.0}; // Heights of the signal bars, relative to the tallest
constexpr uint8_t barCount = 4; // How many bars we draw. Reference only: changing value won't change the count.
// Dynamically calculate the width of the bars, and height of the rightmost, relative to other dimensions
float barW = (1.0 - (paddingW + ((barCount - 1) * gutterW) + paddingW)) / barCount;
float barHMax = 1.0 - (paddingH + paddingH);
// Draw signal bar rectangles, then placeholder lines once strength reached
for (uint8_t i = 0; i < barCount; i++) {
// Coords for this specific bar
float barH = barHMax * barHRel[i];
float barX = paddingW + (i * (gutterW + barW));
float barY = paddingH + (barHMax - barH);
// Rasterize to px coords at the last moment
int16_t rX = (x + (w * barX)) + 0.5;
int16_t rY = (y + (h * barY)) + 0.5;
uint16_t rW = (w * barW) + 0.5;
uint16_t rH = (h * barH) + 0.5;
// Draw signal bars, until we are displaying the correct "signal strength", then just draw placeholder lines
if (i <= strength)
drawRect(rX, rY, rW, rH, BLACK);
else {
// Just draw a placeholder line
float lineY = barY + barH;
uint16_t rLineY = (y + (h * lineY)) + 0.5; // Rasterize
drawLine(rX, rLineY, rX + rW - 1, rLineY, BLACK);
}
}
}
#endif
@@ -0,0 +1,74 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
Base class for Applets which display a list of nodes
Used by the "Recents" and "Heard" applets. Possibly more in future?
+-------------------------------+
| | |
| SHRT . | | |
| Long name 50km |
| |
| ABCD 2 Hops |
| abcdedfghijk 30km |
| |
+-------------------------------+
*/
#pragma once
#include "configuration.h"
#include "graphics/niche/InkHUD/Applet.h"
#include "main.h"
namespace NicheGraphics::InkHUD
{
class NodeListApplet : public Applet, public MeshModule
{
protected:
// Info needed to draw a node card to the list
// - generated each time we hear a node
struct CardInfo {
static constexpr uint8_t HOPS_UNKNOWN = -1;
static constexpr uint32_t DISTANCE_UNKNOWN = -1;
NodeNum nodeNum = 0;
SignalStrength signal = SignalStrength::SIGNAL_UNKNOWN;
uint32_t distanceMeters = DISTANCE_UNKNOWN;
uint8_t hopsAway = HOPS_UNKNOWN;
};
public:
NodeListApplet(const char *name);
void onRender() override;
bool wantPacket(const meshtastic_MeshPacket *p) override;
ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
protected:
virtual void handleParsed(CardInfo c) = 0; // Tell derived applet that we heard a node
virtual std::string getHeaderText() = 0; // Ask derived class what the applet's title should be
uint8_t maxCards(); // Max number of cards which could ever fit on screen
std::deque<CardInfo> cards; // Cards to be rendered. Derived applet fills this.
private:
void drawSignalIndicator(int16_t x, int16_t y, uint16_t w, uint16_t h,
SignalStrength signal); // Draw a "mobile phone" style signal indicator
// Card Dimensions
// - for rendering and for maxCards calc
uint8_t cardMarginH = fontSmall.lineHeight() / 2; // Gap between cards
uint16_t cardH = fontMedium.lineHeight() + fontSmall.lineHeight() + cardMarginH; // Height of card
};
} // namespace NicheGraphics::InkHUD
#endif
@@ -0,0 +1,20 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./BasicExampleApplet.h"
using namespace NicheGraphics;
// All drawing happens here
// Our basic example doesn't do anything useful. It just passively prints some text.
void InkHUD::BasicExampleApplet::onRender()
{
printAt(0, 0, "Hello, World!");
// If text might contain "special characters", is needs parsing first
// This applies to data such as text-messages and and node names
// std::string greeting = parse("Grüezi mitenand!");
// printAt(0, 0, greeting);
}
#endif
@@ -0,0 +1,36 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
A bare-minimum example of an InkHUD applet.
Only prints Hello World.
In variants/<your device>/nicheGraphics.h:
- include this .h file
- add the following line of code:
windowManager->addApplet("Basic", new InkHUD::BasicExampleApplet);
*/
#pragma once
#include "configuration.h"
#include "graphics/niche/InkHUD/Applet.h"
namespace NicheGraphics::InkHUD
{
class BasicExampleApplet : public Applet
{
public:
// You must have an onRender() method
// All drawing happens here
void onRender() override;
};
} // namespace NicheGraphics::InkHUD
#endif
@@ -0,0 +1,53 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./NewMsgExampleApplet.h"
using namespace NicheGraphics;
// We configured the Module API to call this method when we receive a new text message
ProcessMessage InkHUD::NewMsgExampleApplet::handleReceived(const meshtastic_MeshPacket &mp)
{
// Abort if applet fully deactivated
// Don't waste time: we wouldn't be rendered anyway
if (!isActive())
return ProcessMessage::CONTINUE;
// Check that this is an incoming message
// Outgoing messages (sent by us) will also call handleReceived
if (!isFromUs(&mp)) {
// Store the sender's nodenum
// We need to keep this information, so we can re-use it anytime render() is called
haveMessage = true;
fromWho = mp.from;
// Tell InkHUD that we have something new to show on the screen
requestUpdate();
}
// Tell Module API to continue informing other firmware components about this message
// We're not the only component which is interested in new text messages
return ProcessMessage::CONTINUE;
}
// All drawing happens here
// We can trigger a render by calling requestUpdate()
// Render might be called by some external source
// We should always be ready to draw
void InkHUD::NewMsgExampleApplet::onRender()
{
printAt(0, 0, "Example: NewMsg", LEFT, TOP); // Print top-left corner of text at (0,0)
int16_t centerX = X(0.5); // Same as width() / 2
int16_t centerY = Y(0.5); // Same as height() / 2
if (haveMessage) {
printAt(centerX, centerY, "New Message", CENTER, BOTTOM);
printAt(centerX, centerY, "From: " + hexifyNodeNum(fromWho), CENTER, TOP);
} else {
printAt(centerX, centerY, "No Message", CENTER, MIDDLE); // Place center of string at (centerX, centerY)
}
}
#endif
@@ -0,0 +1,61 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
An example of an InkHUD applet.
Tells us when a new text message arrives.
This applet makes use of the Module API to detect new messages,
which is a general part of the Meshtastic firmware, and not part of InkHUD.
In variants/<your device>/nicheGraphics.h:
- include this .h file
- add the following line of code:
windowManager->addApplet("New Msg", new InkHUD::NewMsgExampleApplet);
*/
#pragma once
#include "configuration.h"
#include "graphics/niche/InkHUD/Applet.h"
#include "mesh/SinglePortModule.h"
namespace NicheGraphics::InkHUD
{
class NewMsgExampleApplet : public Applet, public SinglePortModule
{
public:
// The MeshModule API requires us to have a constructor, to specify that we're interested in Text Messages.
NewMsgExampleApplet() : SinglePortModule("NewMsgExampleApplet", meshtastic_PortNum_TEXT_MESSAGE_APP) {}
// All drawing happens here
void onRender() override;
// Your applet might also want to use some of these
// Useful for setting up or tidying up
/*
void onActivate(); // When started
void onDeactivate(); // When stopped
void onForeground(); // When shown by short-press
void onBackground(); // When hidden by short-press
*/
private:
// Called when we receive new text messages
// Part of the MeshModule API
ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
// Store info from handleReceived
bool haveMessage = false;
NodeNum fromWho = 0;
};
} // namespace NicheGraphics::InkHUD
#endif
@@ -0,0 +1,101 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./BatteryIconApplet.h"
using namespace NicheGraphics;
InkHUD::BatteryIconApplet::BatteryIconApplet()
{
// Show at boot, if user has previously enabled the feature
if (settings->optionalFeatures.batteryIcon)
bringToForeground();
// Register to our have BatteryIconApplet::onPowerStatusUpdate method called when new power info is available
// This happens whether or not the battery icon feature is enabled
powerStatusObserver.observe(&powerStatus->onNewStatus);
}
// We handle power status' even when the feature is disabled,
// so that we have up to date data ready if the feature is enabled later.
// Otherwise could be 30s before new status update, with weird battery value displayed
int InkHUD::BatteryIconApplet::onPowerStatusUpdate(const meshtastic::Status *status)
{
// System applets are always active
assert(isActive());
// This method should only receive power statuses
// If we get a different type of status, something has gone weird elsewhere
assert(status->getStatusType() == STATUS_TYPE_POWER);
meshtastic::PowerStatus *powerStatus = (meshtastic::PowerStatus *)status;
// Get the new state of charge %, and round to the nearest 10%
uint8_t newSocRounded = ((powerStatus->getBatteryChargePercent() + 5) / 10) * 10;
// If rounded value has changed, trigger a display update
// It's okay to requestUpdate before we store the new value, as the update won't run until next loop()
// Don't trigger an update if the feature is disabled
if (this->socRounded != newSocRounded && settings->optionalFeatures.batteryIcon)
requestUpdate();
// Store the new value
this->socRounded = newSocRounded;
return 0; // Tell Observable to continue informing other observers
}
void InkHUD::BatteryIconApplet::onRender()
{
// Fill entire tile
// - size of icon controlled by size of tile
int16_t l = 0;
int16_t t = 0;
uint16_t w = width();
int16_t h = height();
// Clear the region beneath the tile
// Most applets are drawing onto an empty frame buffer and don't need to do this
// We do need to do this with the battery though, as it is an "overlay"
fillRect(l, t, w, h, WHITE);
// Vertical centerline
const int16_t m = t + (h / 2);
// =====================
// Draw battery outline
// =====================
// Positive terminal "bump"
const int16_t &bumpL = l;
const uint16_t bumpH = h / 2;
const int16_t bumpT = m - (bumpH / 2);
constexpr uint16_t bumpW = 2;
fillRect(bumpL, bumpT, bumpW, bumpH, BLACK);
// Main body of battery
const int16_t bodyL = bumpL + bumpW;
const int16_t &bodyT = t;
const int16_t &bodyH = h;
const int16_t bodyW = w - bumpW;
drawRect(bodyL, bodyT, bodyW, bodyH, BLACK);
// Erase join between bump and body
drawLine(bodyL, bumpT, bodyL, bumpT + bumpH - 1, WHITE);
// ===================
// Draw battery level
// ===================
constexpr int16_t slicePad = 2;
const int16_t sliceL = bodyL + slicePad;
const int16_t sliceT = bodyT + slicePad;
const uint16_t sliceH = bodyH - (slicePad * 2);
uint16_t sliceW = bodyW - (slicePad * 2);
sliceW = (sliceW * socRounded) / 100; // Apply percentage
hatchRegion(sliceL, sliceT, sliceW, sliceH, 2, BLACK);
drawRect(sliceL, sliceT, sliceW, sliceH, BLACK);
}
#endif
@@ -0,0 +1,39 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
This applet floats top-left, giving a graphical representation of battery remaining
It should be optional, enabled by the on-screen menu
*/
#pragma once
#include "configuration.h"
#include "graphics/niche/InkHUD/SystemApplet.h"
#include "PowerStatus.h"
namespace NicheGraphics::InkHUD
{
class BatteryIconApplet : public SystemApplet
{
public:
BatteryIconApplet();
void onRender() override;
int onPowerStatusUpdate(const meshtastic::Status *status); // Called when new info about battery is available
private:
// Get informed when new information about the battery is available (via onPowerStatusUpdate method)
CallbackObserver<BatteryIconApplet, const meshtastic::Status *> powerStatusObserver =
CallbackObserver<BatteryIconApplet, const meshtastic::Status *>(this, &BatteryIconApplet::onPowerStatusUpdate);
uint8_t socRounded = 0; // Battery state of charge, rounded to nearest 10%
};
} // namespace NicheGraphics::InkHUD
#endif
@@ -0,0 +1,177 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./LogoApplet.h"
#include "mesh/NodeDB.h"
using namespace NicheGraphics;
InkHUD::LogoApplet::LogoApplet() : concurrency::OSThread("LogoApplet")
{
OSThread::setIntervalFromNow(8 * 1000UL);
OSThread::enabled = true;
// During onboarding, show the default short name as well as the version string
// This behavior assists manufacturers during mass production, and should not be modified without good reason
if (!settings->tips.safeShutdownSeen) {
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
fontTitle = fontMedium;
textLeft = xstr(APP_VERSION_SHORT);
textRight = parseShortName(ourNode);
textTitle = "Meshtastic";
} else {
fontTitle = fontSmall;
textLeft = "";
textRight = "";
textTitle = xstr(APP_VERSION_SHORT);
}
bringToForeground();
// This is then drawn with a FULL refresh by Renderer::begin
}
void InkHUD::LogoApplet::onRender()
{
// Size of the region which the logo should "scale to fit"
uint16_t logoWLimit = X(0.8);
uint16_t logoHLimit = Y(0.5);
// Get the max width and height we can manage within the region, while still maintaining aspect ratio
uint16_t logoW = getLogoWidth(logoWLimit, logoHLimit);
uint16_t logoH = getLogoHeight(logoWLimit, logoHLimit);
// Where to place the center of the logo
int16_t logoCX = X(0.5);
int16_t logoCY = Y(0.5 - 0.05);
// Invert colors if black-on-white
// Used during shutdown, to resport display health
// Todo: handle this in InkHUD::Renderer instead
if (inverted) {
fillScreen(BLACK);
setTextColor(WHITE);
}
#ifdef USERPREFS_OEM_IMAGE_DATA // Custom boot screen, if defined in userPrefs.jsonc
// Only show the custom screen at startup
// This allows us to draw the usual Meshtastic logo at shutdown
// The effect is similar to the two-stage userPrefs boot screen used by BaseUI
if (millis() < 10 * 1000UL) {
// Draw the custom logo
const uint8_t logo[] = USERPREFS_OEM_IMAGE_DATA;
drawXBitmap(logoCX - (USERPREFS_OEM_IMAGE_WIDTH / 2), // Left
logoCY - (USERPREFS_OEM_IMAGE_HEIGHT / 2), // Top
logo, // XBM data
USERPREFS_OEM_IMAGE_WIDTH, // Width
USERPREFS_OEM_IMAGE_HEIGHT, // Height
inverted ? WHITE : BLACK // Color
);
// Select the largest font which will still comfortably fit the custom text
setFont(fontLarge);
if (getTextWidth(USERPREFS_OEM_TEXT) > 0.8 * width())
setFont(fontMedium);
if (getTextWidth(USERPREFS_OEM_TEXT) > 0.8 * width())
setFont(fontSmall);
// Draw custom text below logo
int16_t logoB = logoCY + (USERPREFS_OEM_IMAGE_HEIGHT / 2); // Bottom of the logo
printAt(X(0.5), logoB + Y(0.1), USERPREFS_OEM_TEXT, CENTER, TOP);
// Don't draw the normal boot screen, we've already drawn our custom version
return;
}
#endif
drawLogo(logoCX, logoCY, logoW, logoH, inverted ? WHITE : BLACK);
if (!textLeft.empty()) {
setFont(fontSmall);
printAt(0, 0, textLeft, LEFT, TOP);
}
if (!textRight.empty()) {
setFont(fontSmall);
printAt(X(1), 0, textRight, RIGHT, TOP);
}
if (!textTitle.empty()) {
int16_t logoB = logoCY + (logoH / 2); // Bottom of the logo
setFont(fontTitle);
printAt(X(0.5), logoB + Y(0.1), textTitle, CENTER, TOP);
}
}
void InkHUD::LogoApplet::onForeground()
{
SystemApplet::lockRendering = true;
SystemApplet::lockRequests = true;
SystemApplet::handleInput = true; // We don't actually use this input. Just blocking other applets from using it.
}
void InkHUD::LogoApplet::onBackground()
{
SystemApplet::lockRendering = false;
SystemApplet::lockRequests = false;
SystemApplet::handleInput = false;
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
}
// Begin displaying the screen which is shown at shutdown
void InkHUD::LogoApplet::onShutdown()
{
bringToForeground();
textLeft = "";
textRight = "";
textTitle = "Shutting Down...";
fontTitle = fontSmall;
// Draw a shutting down screen, twice.
// Once white on black, once black on white.
// Intention is to restore display health.
inverted = true;
inkhud->forceUpdate(Drivers::EInk::FULL, false);
delay(1000); // Cooldown. Back to back updates aren't great for health.
inverted = false;
inkhud->forceUpdate(Drivers::EInk::FULL, false);
delay(1000); // Cooldown
// Prepare for the powered-off screen now
// We can change these values because the initial "shutting down" screen has already rendered at this point
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
textLeft = "";
textRight = "";
textTitle = parseShortName(ourNode);
fontTitle = fontMedium;
// This is then drawn by InkHUD::Events::onShutdown, with a blocking FULL update, after InkHUD's flash write is complete
}
void InkHUD::LogoApplet::onReboot()
{
bringToForeground();
textLeft = "";
textRight = "";
textTitle = "Rebooting...";
fontTitle = fontSmall;
inkhud->forceUpdate(Drivers::EInk::FULL, false);
// Perform the update right now, waiting here until complete
}
int32_t InkHUD::LogoApplet::runOnce()
{
sendToBackground();
return OSThread::disable();
}
#endif
@@ -0,0 +1,42 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
Shows the Meshtastic logo fullscreen, with accompanying text
Used for boot and shutdown
*/
#pragma once
#include "configuration.h"
#include "concurrency/OSThread.h"
#include "graphics/niche/InkHUD/SystemApplet.h"
namespace NicheGraphics::InkHUD
{
class LogoApplet : public SystemApplet, public concurrency::OSThread
{
public:
LogoApplet();
void onRender() override;
void onForeground() override;
void onBackground() override;
void onShutdown() override;
void onReboot() override;
protected:
int32_t runOnce() override;
std::string textLeft;
std::string textRight;
std::string textTitle;
AppletFont fontTitle;
bool inverted = false; // Invert colors. Used during shutdown, to restore display health.
};
} // namespace NicheGraphics::InkHUD
#endif
@@ -0,0 +1,42 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
Set of end-point actions for the Menu Applet
Added as menu entries in MenuApplet::showPage
Behaviors assigned in MenuApplet::execute
*/
#pragma once
#include "configuration.h"
namespace NicheGraphics::InkHUD
{
enum MenuAction {
NO_ACTION,
SEND_PING,
STORE_CANNEDMESSAGE_SELECTION,
SEND_CANNEDMESSAGE,
SHUTDOWN,
NEXT_TILE,
TOGGLE_BACKLIGHT,
TOGGLE_GPS,
ENABLE_BLUETOOTH,
TOGGLE_APPLET,
TOGGLE_AUTOSHOW_APPLET,
SET_RECENTS,
ROTATE,
LAYOUT,
TOGGLE_BATTERY_ICON,
TOGGLE_NOTIFICATIONS,
TOGGLE_INVERT_COLOR,
TOGGLE_12H_CLOCK,
};
} // namespace NicheGraphics::InkHUD
#endif
@@ -0,0 +1,799 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./MenuApplet.h"
#include "RTC.h"
#include "MeshService.h"
#include "Router.h"
#include "airtime.h"
#include "main.h"
#include "power.h"
#if !MESHTASTIC_EXCLUDE_GPS
#include "GPS.h"
#endif
using namespace NicheGraphics;
static constexpr uint8_t MENU_TIMEOUT_SEC = 60; // How many seconds before menu auto-closes
// Options for the "Recents" menu
// These are offered to users as possible values for settings.recentlyActiveSeconds
static constexpr uint8_t RECENTS_OPTIONS_MINUTES[] = {2, 5, 10, 30, 60, 120};
InkHUD::MenuApplet::MenuApplet() : concurrency::OSThread("MenuApplet")
{
// No timer tasks at boot
OSThread::disable();
// Note: don't get instance if we're not actually using the backlight,
// or else you will unintentionally instantiate it
if (settings->optionalMenuItems.backlight) {
backlight = Drivers::LatchingBacklight::getInstance();
}
// Initialize the Canned Message store
// This is a shared nicheGraphics component
// - handles loading & parsing the canned messages
// - handles setting / getting of canned messages via apps (Client API Admin Messages)
cm.store = CannedMessageStore::getInstance();
}
void InkHUD::MenuApplet::onForeground()
{
// We do need this before we render, but we can optimize by just calculating it once now
systemInfoPanelHeight = getSystemInfoPanelHeight();
// Display initial menu page
showPage(MenuPage::ROOT);
// If device has a backlight which isn't controlled by aux button:
// backlight on always when menu opens.
// Courtesy to T-Echo users who removed the capacitive touch button
if (settings->optionalMenuItems.backlight) {
assert(backlight);
if (!backlight->isOn())
backlight->peek();
}
// Prevent user applets requesting update while menu is open
// Handle button input with this applet
SystemApplet::lockRequests = true;
SystemApplet::handleInput = true;
// Begin the auto-close timeout
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
OSThread::enabled = true;
// Upgrade the refresh to FAST, for guaranteed responsiveness
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
}
void InkHUD::MenuApplet::onBackground()
{
// Discard any data we generated while selecting a canned message
// Frees heap mem
freeCannedMessageResources();
// If device has a backlight which isn't controlled by aux button:
// Item in options submenu allows keeping backlight on after menu is closed
// If this item is deselected we will turn backlight off again, now that menu is closing
if (settings->optionalMenuItems.backlight) {
assert(backlight);
if (!backlight->isLatched())
backlight->off();
}
// Stop the auto-timeout
OSThread::disable();
// Resume normal rendering and button behavior of user applets
SystemApplet::lockRequests = false;
SystemApplet::handleInput = false;
// Restore the user applet whose tile we borrowed
if (borrowedTileOwner)
borrowedTileOwner->bringToForeground();
Tile *t = getTile();
t->assignApplet(borrowedTileOwner); // Break our link with the tile, (and relink it with real owner, if it had one)
borrowedTileOwner = nullptr;
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
// We're only updating here to upgrade from UNSPECIFIED to FAST, to ensure responsiveness when exiting menu
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
}
// Open the menu
// Parameter specifies which user-tile the menu will use
// The user applet originally on this tile will be restored when the menu closes
void InkHUD::MenuApplet::show(Tile *t)
{
// Remember who *really* owns this tile
borrowedTileOwner = t->getAssignedApplet();
// Hide the owner, if it is a valid applet
if (borrowedTileOwner)
borrowedTileOwner->sendToBackground();
// Break the owner's link with tile
// Relink it to menu applet
t->assignApplet(this);
// Show menu
bringToForeground();
}
// Auto-exit the menu applet after a period of inactivity
// The values shown on the root menu are only a snapshot: they are not re-rendered while the menu remains open.
// By exiting the menu, we prevent users mistakenly believing that the data will update.
int32_t InkHUD::MenuApplet::runOnce()
{
// runOnce's interval is pushed back when a button is pressed
// If we do actually run, it means no button input occurred within MENU_TIMEOUT_SEC,
// so we close the menu.
showPage(EXIT);
// Timer should disable after firing
// This is redundant, as onBackground() will also disable
return OSThread::disable();
}
// Perform action for a menu item, then change page
// Behaviors for MenuActions are defined here
void InkHUD::MenuApplet::execute(MenuItem item)
{
// Perform an action
// ------------------
switch (item.action) {
// Open a submenu without performing any action
// Also handles exit
case NO_ACTION:
break;
case NEXT_TILE:
inkhud->nextTile();
break;
case SEND_PING:
service->refreshLocalMeshNode();
service->trySendPosition(NODENUM_BROADCAST, true);
// Force the next refresh to use FULL, to protect the display, as some users will probably spam this button
inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FULL);
break;
case STORE_CANNEDMESSAGE_SELECTION:
cm.selectedMessageItem = &cm.messageItems.at(cursor - 1); // Minus one: offset for the initial "Send Ping" entry
break;
case SEND_CANNEDMESSAGE:
cm.selectedRecipientItem = &cm.recipientItems.at(cursor);
sendText(cm.selectedRecipientItem->dest, cm.selectedRecipientItem->channelIndex, cm.selectedMessageItem->rawText.c_str());
inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FULL); // Next refresh should be FULL. Lots of button pressing to get here
break;
case ROTATE:
inkhud->rotate();
break;
case LAYOUT:
// Todo: smarter incrementing of tile count
settings->userTiles.count++;
if (settings->userTiles.count == 3) // Skip 3 tiles: not done yet
settings->userTiles.count++;
if (settings->userTiles.count > settings->userTiles.maxCount) // Loop around if tile count now too high
settings->userTiles.count = 1;
inkhud->updateLayout();
break;
case TOGGLE_APPLET:
settings->userApplets.active[cursor] = !settings->userApplets.active[cursor];
inkhud->updateAppletSelection();
break;
case TOGGLE_AUTOSHOW_APPLET:
// Toggle settings.userApplets.autoshow[] value, via MenuItem::checkState pointer set in populateAutoshowPage()
*items.at(cursor).checkState = !(*items.at(cursor).checkState);
break;
case TOGGLE_NOTIFICATIONS:
settings->optionalFeatures.notifications = !settings->optionalFeatures.notifications;
break;
case TOGGLE_INVERT_COLOR:
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED)
config.display.displaymode = meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT;
else
config.display.displaymode = meshtastic_Config_DisplayConfig_DisplayMode_INVERTED;
nodeDB->saveToDisk(SEGMENT_CONFIG);
break;
case SET_RECENTS:
// Set value of settings.recentlyActiveSeconds
// Uses menu cursor to read RECENTS_OPTIONS_MINUTES array (defined at top of this file)
assert(cursor < sizeof(RECENTS_OPTIONS_MINUTES) / sizeof(RECENTS_OPTIONS_MINUTES[0]));
settings->recentlyActiveSeconds = RECENTS_OPTIONS_MINUTES[cursor] * 60; // Menu items are in minutes
break;
case SHUTDOWN:
LOG_INFO("Shutting down from menu");
shutdownAtMsec = millis();
// Menu is then sent to background via onShutdown
break;
case TOGGLE_BATTERY_ICON:
inkhud->toggleBatteryIcon();
break;
case TOGGLE_BACKLIGHT:
// Note: backlight is already on in this situation
// We're marking that it should *remain* on once menu closes
assert(backlight);
if (backlight->isLatched())
backlight->off();
else
backlight->latch();
break;
case TOGGLE_12H_CLOCK:
config.display.use_12h_clock = !config.display.use_12h_clock;
nodeDB->saveToDisk(SEGMENT_CONFIG);
break;
case TOGGLE_GPS:
gps->toggleGpsMode();
nodeDB->saveToDisk(SEGMENT_CONFIG);
break;
case ENABLE_BLUETOOTH:
// This helps users recover from a bad wifi config
LOG_INFO("Enabling Bluetooth");
config.network.wifi_enabled = false;
config.bluetooth.enabled = true;
nodeDB->saveToDisk();
rebootAtMsec = millis() + 2000;
break;
default:
LOG_WARN("Action not implemented");
}
// Move to next page, as defined for the MenuItem
showPage(item.nextPage);
}
// Display a new page of MenuItems
// May reload same page, or exit menu applet entirely
// Fills the MenuApplet::items vector
void InkHUD::MenuApplet::showPage(MenuPage page)
{
items.clear();
items.shrink_to_fit();
switch (page) {
case ROOT:
// Optional: next applet
if (settings->optionalMenuItems.nextTile && settings->userTiles.count > 1)
items.push_back(MenuItem("Next Tile", MenuAction::NEXT_TILE, MenuPage::ROOT)); // Only if multiple applets shown
items.push_back(MenuItem("Send", MenuPage::SEND));
items.push_back(MenuItem("Options", MenuPage::OPTIONS));
// items.push_back(MenuItem("Display Off", MenuPage::EXIT)); // TODO
items.push_back(MenuItem("Save & Shut Down", MenuAction::SHUTDOWN));
items.push_back(MenuItem("Exit", MenuPage::EXIT));
break;
case SEND:
populateSendPage();
break;
case CANNEDMESSAGE_RECIPIENT:
populateRecipientPage();
break;
case OPTIONS:
// Optional: backlight
if (settings->optionalMenuItems.backlight)
items.push_back(MenuItem(backlight->isLatched() ? "Backlight Off" : "Keep Backlight On", // Label
MenuAction::TOGGLE_BACKLIGHT, // Action
MenuPage::EXIT // Exit once complete
));
// Optional: GPS
if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_DISABLED)
items.push_back(MenuItem("Enable GPS", MenuAction::TOGGLE_GPS, MenuPage::EXIT));
if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED)
items.push_back(MenuItem("Disable GPS", MenuAction::TOGGLE_GPS, MenuPage::EXIT));
// Optional: Enable Bluetooth, in case of lost wifi connection
if (!config.bluetooth.enabled || config.network.wifi_enabled)
items.push_back(MenuItem("Enable Bluetooth", MenuAction::ENABLE_BLUETOOTH, MenuPage::EXIT));
items.push_back(MenuItem("Applets", MenuPage::APPLETS));
items.push_back(MenuItem("Auto-show", MenuPage::AUTOSHOW));
items.push_back(MenuItem("Recents Duration", MenuPage::RECENTS));
if (settings->userTiles.maxCount > 1)
items.push_back(MenuItem("Layout", MenuAction::LAYOUT, MenuPage::OPTIONS));
items.push_back(MenuItem("Rotate", MenuAction::ROTATE, MenuPage::OPTIONS));
items.push_back(MenuItem("Notifications", MenuAction::TOGGLE_NOTIFICATIONS, MenuPage::OPTIONS,
&settings->optionalFeatures.notifications));
items.push_back(MenuItem("Battery Icon", MenuAction::TOGGLE_BATTERY_ICON, MenuPage::OPTIONS,
&settings->optionalFeatures.batteryIcon));
invertedColors = (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED);
items.push_back(MenuItem("Invert Color", MenuAction::TOGGLE_INVERT_COLOR, MenuPage::OPTIONS, &invertedColors));
items.push_back(
MenuItem("12-Hour Clock", MenuAction::TOGGLE_12H_CLOCK, MenuPage::OPTIONS, &config.display.use_12h_clock));
items.push_back(MenuItem("Exit", MenuPage::EXIT));
break;
case APPLETS:
populateAppletPage();
items.push_back(MenuItem("Exit", MenuPage::EXIT));
break;
case AUTOSHOW:
populateAutoshowPage();
items.push_back(MenuItem("Exit", MenuPage::EXIT));
break;
case RECENTS:
populateRecentsPage();
break;
case EXIT:
sendToBackground(); // Menu applet dismissed, allow normal behavior to resume
break;
default:
LOG_WARN("Page not implemented");
}
// Reset the cursor, unless reloading same page
// (or now out-of-bounds)
if (page != currentPage || cursor >= items.size()) {
cursor = 0;
// ROOT menu has special handling: unselected at first, to emphasise the system info panel
if (page == ROOT)
cursorShown = false;
}
// Remember which page we are on now
currentPage = page;
}
void InkHUD::MenuApplet::onRender()
{
if (items.size() == 0)
LOG_ERROR("Empty Menu");
// Dimensions for the slots where we will draw menuItems
const float padding = 0.05;
const uint16_t itemH = fontSmall.lineHeight() * 2;
const int16_t itemW = width() - X(padding) - X(padding);
const int16_t itemL = X(padding);
const int16_t itemR = X(1 - padding);
int16_t itemT = 0; // Top (y px of current slot). Incremented as we draw. Adjusted to fit system info panel on ROOT menu.
// How many full menuItems will fit on screen
uint8_t slotCount = (height() - itemT) / itemH;
// System info panel at the top of the menu
// =========================================
uint16_t &siH = systemInfoPanelHeight; // System info - height. Calculated at onForeground
const uint8_t slotsObscured = ceilf(siH / (float)itemH); // How many slots are obscured by system info panel
// System info - top
// Remain at 0px, until cursor reaches bottom of screen, then begin to scroll off screen.
// This is the same behavior we expect from the non-root menus.
// Implementing this with the systemp panel is slightly annoying though,
// and required adding the MenuApplet::getSystemInfoPanelHeight method
int16_t siT;
if (cursor < slotCount - slotsObscured - 1) // (Minus 1: comparing zero based index with a count)
siT = 0;
else
siT = 0 - ((cursor - (slotCount - slotsObscured - 1)) * itemH);
// If showing ROOT menu,
// and the panel isn't yet scrolled off screen top
if (currentPage == ROOT) {
drawSystemInfoPanel(0, siT, width()); // Draw the panel.
itemT = max(siT + siH, 0); // Offset the first menu entry, so menu starts below the system info panel
}
// Draw menu items
// ===================
// Which item will be drawn to the top-most slot?
// Initially, this is the item 0, but may increase once we begin scrolling
uint8_t firstItem;
if (cursor < slotCount)
firstItem = 0;
else
firstItem = cursor - (slotCount - 1);
// Which item will be drawn to the bottom-most slot?
// This may be beyond the slot-count, to draw a partially off-screen item below the bottom-most slow
// This may be less than the slot-count, if we are reaching the end of the menuItems
uint8_t lastItem = min((uint8_t)firstItem + slotCount, (uint8_t)items.size() - 1);
// -- Loop: draw each (visible) menu item --
for (uint8_t i = firstItem; i <= lastItem; i++) {
// Grab the menuItem
MenuItem item = items.at(i);
// Center-line for the text
int16_t center = itemT + (itemH / 2);
// Box, if currently selected
if (cursorShown && i == cursor)
drawRect(itemL, itemT, itemW, itemH, BLACK);
// Item's text
printAt(itemL + X(padding), center, item.label, LEFT, MIDDLE);
// Checkbox, if relevant
if (item.checkState) {
const uint16_t cbWH = fontSmall.lineHeight(); // Checkbox: width / height
const int16_t cbL = itemR - X(padding) - cbWH; // Checkbox: left
const int16_t cbT = center - (cbWH / 2); // Checkbox : top
// Checkbox ticked
if (*(item.checkState)) {
drawRect(cbL, cbT, cbWH, cbWH, BLACK);
// First point of tick: pen down
const int16_t t1Y = center;
const int16_t t1X = cbL + 3;
// Second point of tick: base
const int16_t t2Y = center + (cbWH / 2) - 2;
const int16_t t2X = cbL + (cbWH / 2);
// Third point of tick: end of tail
const int16_t t3Y = center - (cbWH / 2) - 2;
const int16_t t3X = cbL + cbWH + 2;
// Draw twice: faux bold
drawLine(t1X, t1Y, t2X, t2Y, BLACK);
drawLine(t2X, t2Y, t3X, t3Y, BLACK);
drawLine(t1X + 1, t1Y, t2X + 1, t2Y, BLACK);
drawLine(t2X + 1, t2Y, t3X + 1, t3Y, BLACK);
}
// Checkbox ticked
else
drawRect(cbL, cbT, cbWH, cbWH, BLACK);
}
// Increment the y value (top) as we go
itemT += itemH;
}
}
void InkHUD::MenuApplet::onButtonShortPress()
{
// Push the auto-close timer back
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
// Move menu cursor to next entry, then update
if (cursorShown)
cursor = (cursor + 1) % items.size();
else
cursorShown = true;
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
}
void InkHUD::MenuApplet::onButtonLongPress()
{
// Push the auto-close timer back
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
if (cursorShown)
execute(items.at(cursor));
else
showPage(MenuPage::EXIT); // Special case: Peek at root-menu; longpress again to close
// If we didn't already request a specialized update, when handling a menu action,
// then perform the usual fast update.
// FAST keeps things responsive: important because we're dealing with user input
if (!wantsToRender())
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
}
// Dynamically create MenuItem entries for activating / deactivating Applets, for the "Applet Selection" submenu
void InkHUD::MenuApplet::populateAppletPage()
{
assert(items.size() == 0);
for (uint8_t i = 0; i < inkhud->userApplets.size(); i++) {
const char *name = inkhud->userApplets.at(i)->name;
bool *isActive = &(settings->userApplets.active[i]);
items.push_back(MenuItem(name, MenuAction::TOGGLE_APPLET, MenuPage::APPLETS, isActive));
}
}
// Dynamically create MenuItem entries for selecting which applets will automatically come to foreground when they have new data
// We only populate this menu page with applets which are actually active
// We use the MenuItem::checkState pointer to toggle the setting in MenuApplet::execute. Bit of a hack, but convenient.
void InkHUD::MenuApplet::populateAutoshowPage()
{
assert(items.size() == 0);
for (uint8_t i = 0; i < inkhud->userApplets.size(); i++) {
// Only add a menu item if applet is active
if (settings->userApplets.active[i]) {
const char *name = inkhud->userApplets.at(i)->name;
bool *isActive = &(settings->userApplets.autoshow[i]);
items.push_back(MenuItem(name, MenuAction::TOGGLE_AUTOSHOW_APPLET, MenuPage::AUTOSHOW, isActive));
}
}
}
// Create MenuItem entries to select our definition of "Recent"
// Controls how long data will remain in any "Recents" flavored applets
void InkHUD::MenuApplet::populateRecentsPage()
{
// How many values are shown for use to choose from
constexpr uint8_t optionCount = sizeof(RECENTS_OPTIONS_MINUTES) / sizeof(RECENTS_OPTIONS_MINUTES[0]);
// Create an entry for each item in RECENTS_OPTIONS_MINUTES array
// (Defined at top of this file)
for (uint8_t i = 0; i < optionCount; i++) {
std::string label = to_string(RECENTS_OPTIONS_MINUTES[i]) + " mins";
items.push_back(MenuItem(label.c_str(), MenuAction::SET_RECENTS, MenuPage::EXIT));
}
}
// MenuItem entries for the "send" page
// Dynamically creates menu items based on available canned messages
void InkHUD::MenuApplet::populateSendPage()
{
// Position / NodeInfo packet
items.push_back(MenuItem("Ping", MenuAction::SEND_PING, MenuPage::EXIT));
// One menu item for each canned message
uint8_t count = cm.store->size();
for (uint8_t i = 0; i < count; i++) {
// Gather the information for this item
CannedMessages::MessageItem messageItem;
messageItem.rawText = cm.store->at(i);
messageItem.label = parse(messageItem.rawText);
// Store the item (until the menu closes)
cm.messageItems.push_back(messageItem);
// Create a menu item
const char *itemText = cm.messageItems.back().label.c_str();
items.push_back(MenuItem(itemText, MenuAction::STORE_CANNEDMESSAGE_SELECTION, MenuPage::CANNEDMESSAGE_RECIPIENT));
}
items.push_back(MenuItem("Exit", MenuPage::EXIT));
}
// Dynamically create MenuItem entries for possible canned message destinations
// All available channels are shown
// Favorite nodes are shown, provided we don't have an *excessive* amount
void InkHUD::MenuApplet::populateRecipientPage()
{
// Create recipient data (and menu items) for any channels
// --------------------------------------------------------
for (uint8_t i = 0; i < MAX_NUM_CHANNELS; i++) {
// Get the channel, and check if it's enabled
meshtastic_Channel &channel = channels.getByIndex(i);
if (!channel.has_settings || channel.role == meshtastic_Channel_Role_DISABLED)
continue;
CannedMessages::RecipientItem r;
// Set index
r.channelIndex = channel.index;
// Set a label for the menu item
r.label = "Ch " + to_string(i) + ": ";
if (channel.role == meshtastic_Channel_Role_PRIMARY)
r.label += "Primary";
else
r.label += parse(channel.settings.name);
// Add to the list of recipients
cm.recipientItems.push_back(r);
// Add a menu item for this recipient
const char *itemText = cm.recipientItems.back().label.c_str();
items.push_back(MenuItem(itemText, SEND_CANNEDMESSAGE, MenuPage::EXIT));
}
// Create recipient data (and menu items) for favorite nodes
// ---------------------------------------------------------
uint32_t nodeCount = nodeDB->getNumMeshNodes();
uint32_t favoriteCount = 0;
// Count favorites
for (uint32_t i = 0; i < nodeCount; i++) {
if (nodeDB->getMeshNodeByIndex(i)->is_favorite)
favoriteCount++;
}
// Only add favorites if the number is reasonable
// Don't want some monstrous list that takes 100 clicks to reach exit
if (favoriteCount < 20) {
for (uint32_t i = 0; i < nodeCount; i++) {
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
// Skip node if not a favorite
if (!node->is_favorite)
continue;
CannedMessages::RecipientItem r;
r.dest = node->num;
r.channelIndex = nodeDB->getMeshNodeChannel(node->num); // Channel index only relevant if encrypted DM not possible(?)
// Set a label for the menu item
r.label = "DM: ";
if (node->has_user)
r.label += parse(node->user.long_name);
else
r.label += hexifyNodeNum(node->num); // Unsure if it's possible to favorite a node without NodeInfo?
// Add to the list of recipients
cm.recipientItems.push_back(r);
// Add a menu item for this recipient
const char *itemText = cm.recipientItems.back().label.c_str();
items.push_back(MenuItem(itemText, SEND_CANNEDMESSAGE, MenuPage::EXIT));
}
}
items.push_back(MenuItem("Exit", MenuPage::EXIT));
}
// Renders the panel shown at the top of the root menu.
// Displays the clock, and several other pieces of instantaneous system info,
// which we'd prefer not to have displayed in a normal applet, as they update too frequently.
void InkHUD::MenuApplet::drawSystemInfoPanel(int16_t left, int16_t top, uint16_t width, uint16_t *renderedHeight)
{
// Reset the height
// We'll add to this as we add elements
uint16_t height = 0;
// Clock (potentially)
// ====================
std::string clockString = getTimeString();
if (clockString.length() > 0) {
setFont(fontMedium);
printAt(width / 2, top, clockString, CENTER, TOP);
height += fontMedium.lineHeight();
height += fontMedium.lineHeight() * 0.1; // Padding below clock
}
// Stats
// ===================
setFont(fontSmall);
// Position of the label row for the system info
const int16_t labelT = top + height;
height += fontSmall.lineHeight() * 1.1; // Slightly increased spacing
// Position of the data row for the system info
const int16_t valT = top + height;
height += fontSmall.lineHeight() * 1.1; // Slightly increased spacing (between bottom line and divider)
// Position of divider between the info panel and the menu entries
const int16_t divY = top + height;
height += fontSmall.lineHeight() * 0.2; // Padding *below* the divider. (Above first menu item)
// Create a variable number of columns
// Either 3 or 4, depending on whether we have GPS
// Todo
constexpr uint8_t N_COL = 3;
int16_t colL[N_COL];
int16_t colC[N_COL];
int16_t colR[N_COL];
for (uint8_t i = 0; i < N_COL; i++) {
colL[i] = left + ((width / N_COL) * i);
colC[i] = colL[i] + ((width / N_COL) / 2);
colR[i] = colL[i] + (width / N_COL);
}
// Info blocks, left to right
// Voltage
float voltage = powerStatus->getBatteryVoltageMv() / 1000.0;
char voltageStr[6]; // "XX.XV"
sprintf(voltageStr, "%.2fV", voltage);
printAt(colC[0], labelT, "Bat", CENTER, TOP);
printAt(colC[0], valT, voltageStr, CENTER, TOP);
// Divider
for (int16_t y = valT; y <= divY; y += 3)
drawPixel(colR[0], y, BLACK);
// Channel Util
char chUtilStr[4]; // "XX%"
sprintf(chUtilStr, "%2.f%%", airTime->channelUtilizationPercent());
printAt(colC[1], labelT, "Ch", CENTER, TOP);
printAt(colC[1], valT, chUtilStr, CENTER, TOP);
// Divider
for (int16_t y = valT; y <= divY; y += 3)
drawPixel(colR[1], y, BLACK);
// Duty Cycle (AirTimeTx)
char dutyUtilStr[4]; // "XX%"
sprintf(dutyUtilStr, "%2.f%%", airTime->utilizationTXPercent());
printAt(colC[2], labelT, "Duty", CENTER, TOP);
printAt(colC[2], valT, dutyUtilStr, CENTER, TOP);
/*
// Divider
for (int16_t y = valT; y <= divY; y += 3)
drawPixel(colR[2], y, BLACK);
// GPS satellites - todo
printAt(colC[3], labelT, "Sats", CENTER, TOP);
printAt(colC[3], valT, "ToDo", CENTER, TOP);
*/
// Horizontal divider, at bottom of system info panel
for (int16_t x = 0; x < width; x += 2) // Divider, centered in the padding between first system panel and first item
drawPixel(x, divY, BLACK);
if (renderedHeight != nullptr)
*renderedHeight = height;
}
// Get the height of the the panel drawn at the top of the menu
// This is inefficient, as we do actually have to render the panel to determine the height
// It solves a catch-22 situation, where slotCount needs to know panel height, and panel height needs to know slotCount
uint16_t InkHUD::MenuApplet::getSystemInfoPanelHeight()
{
// Render *far* off screen
uint16_t height = 0;
drawSystemInfoPanel(INT16_MIN, INT16_MIN, 1, &height);
return height;
}
// Send a text message to the mesh
// Used to send our canned messages
void InkHUD::MenuApplet::sendText(NodeNum dest, ChannelIndex channel, const char *message)
{
meshtastic_MeshPacket *p = router->allocForSending();
p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP;
p->to = dest;
p->channel = channel;
p->want_ack = true;
p->decoded.payload.size = strlen(message);
memcpy(p->decoded.payload.bytes, message, p->decoded.payload.size);
// Tack on a bell character if requested
if (moduleConfig.canned_message.send_bell && p->decoded.payload.size < meshtastic_Constants_DATA_PAYLOAD_LEN) {
p->decoded.payload.bytes[p->decoded.payload.size] = 7; // Bell character
p->decoded.payload.bytes[p->decoded.payload.size + 1] = '\0'; // Append Null Terminator
p->decoded.payload.size++;
}
LOG_INFO("Send message id=%d, dest=%x, msg=%.*s", p->id, p->to, p->decoded.payload.size, p->decoded.payload.bytes);
service->sendToMesh(p, RX_SRC_LOCAL, true); // Send to mesh, cc to phone
}
// Free up any heap mmemory we'd used while selecting / sending canned messages
void InkHUD::MenuApplet::freeCannedMessageResources()
{
cm.selectedMessageItem = nullptr;
cm.selectedRecipientItem = nullptr;
cm.messageItems.clear();
cm.recipientItems.clear();
}
#endif
@@ -0,0 +1,100 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "configuration.h"
#include "graphics/niche/Drivers/Backlight/LatchingBacklight.h"
#include "graphics/niche/InkHUD/InkHUD.h"
#include "graphics/niche/InkHUD/Persistence.h"
#include "graphics/niche/InkHUD/SystemApplet.h"
#include "graphics/niche/Utils/CannedMessageStore.h"
#include "./MenuItem.h"
#include "./MenuPage.h"
#include "Channels.h"
#include "concurrency/OSThread.h"
namespace NicheGraphics::InkHUD
{
class Applet;
class MenuApplet : public SystemApplet, public concurrency::OSThread
{
public:
MenuApplet();
void onForeground() override;
void onBackground() override;
void onButtonShortPress() override;
void onButtonLongPress() override;
void onRender() override;
void show(Tile *t); // Open the menu, onto a user tile
protected:
Drivers::LatchingBacklight *backlight = nullptr; // Convenient access to the backlight singleton
int32_t runOnce() override;
void execute(MenuItem item); // Perform the MenuAction associated with a MenuItem, if any
void showPage(MenuPage page); // Load and display a MenuPage
void populateSendPage(); // Dynamically create MenuItems including canned messages
void populateRecipientPage(); // Dynamically create a page of possible destinations for a canned message
void populateAppletPage(); // Dynamically create MenuItems for toggling loaded applets
void populateAutoshowPage(); // Dynamically create MenuItems for selecting which applets can autoshow
void populateRecentsPage(); // Create menu items: a choice of values for settings.recentlyActiveSeconds
uint16_t getSystemInfoPanelHeight();
void drawSystemInfoPanel(int16_t left, int16_t top, uint16_t width,
uint16_t *height = nullptr); // Info panel at top of root menu
void sendText(NodeNum dest, ChannelIndex channel, const char *message); // Send a text message to mesh
void freeCannedMessageResources(); // Clear MenuApplet's canned message processing data
MenuPage currentPage = MenuPage::ROOT;
uint8_t cursor = 0; // Which menu item is currently highlighted
bool cursorShown = false; // Is *any* item highlighted? (Root menu: no initial selection)
uint16_t systemInfoPanelHeight = 0; // Need to know before we render
std::vector<MenuItem> items; // MenuItems for the current page. Filled by ShowPage
// Data for selecting and sending canned messages via the menu
// Placed into a sub-class for organization only
class CannedMessages
{
public:
// Share NicheGraphics component
// Handles loading, getting, setting
CannedMessageStore *store;
// One canned message
// Links the menu item to the true message text
struct MessageItem {
std::string label; // Shown in menu. Prefixed, and UTF-8 chars parsed
std::string rawText; // The message which will be sent, if this item is selected
} *selectedMessageItem;
// One possible destination for a canned message
// Links the menu item to the intended recipient
// May represent either broadcast or DM
struct RecipientItem {
std::string label; // Shown in menu
NodeNum dest = NODENUM_BROADCAST;
uint8_t channelIndex = 0;
} *selectedRecipientItem;
// These lists are generated when the menu page is populated
// Cleared onBackground (when MenuApplet closes)
std::vector<MessageItem> messageItems;
std::vector<RecipientItem> recipientItems;
} cm;
Applet *borrowedTileOwner = nullptr; // Which applet we have temporarily replaced while displaying menu
bool invertedColors = false; // Helper to display current state of config.display.displaymode in InkHUD options
};
} // namespace NicheGraphics::InkHUD
#endif
@@ -0,0 +1,47 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
One item of a MenuPage, in InkHUD::MenuApplet
Added to MenuPages in InkHUD::showPage
- May open a submenu or exit
- May perform an action
- May toggle a bool value, shown by a checkbox
*/
#pragma once
#include "configuration.h"
#include "./MenuAction.h"
#include "./MenuPage.h"
namespace NicheGraphics::InkHUD
{
// One item of a MenuPage
class MenuItem
{
public:
std::string label;
MenuAction action = NO_ACTION;
MenuPage nextPage = EXIT;
bool *checkState = nullptr;
// Various constructors, depending on the intended function of the item
MenuItem(const char *label, MenuPage nextPage) : label(label), nextPage(nextPage) {}
MenuItem(const char *label, MenuAction action) : label(label), action(action) {}
MenuItem(const char *label, MenuAction action, MenuPage nextPage) : label(label), action(action), nextPage(nextPage) {}
MenuItem(const char *label, MenuAction action, MenuPage nextPage, bool *checkState)
: label(label), action(action), nextPage(nextPage), checkState(checkState)
{
}
};
} // namespace NicheGraphics::InkHUD
#endif
@@ -0,0 +1,31 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
Sub-menu for InkHUD::MenuApplet
Structure of the menu is defined in InkHUD::showPage
*/
#pragma once
#include "configuration.h"
namespace NicheGraphics::InkHUD
{
// Sub-menu for MenuApplet
enum MenuPage : uint8_t {
ROOT, // Initial menu page
SEND,
CANNEDMESSAGE_RECIPIENT, // Select destination for a canned message
OPTIONS,
APPLETS,
AUTOSHOW,
RECENTS, // Select length of "recentlyActiveSeconds"
EXIT, // Dismiss the menu applet
};
} // namespace NicheGraphics::InkHUD
#endif
@@ -0,0 +1,40 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
A notification which might be displayed by the NotificationApplet
An instance of this class is offered to Applets via Applet::approveNotification, in case they want to veto the notification.
An Applet should veto a notification if it is already displaying the same info which the notification would convey.
*/
#pragma once
#include "configuration.h"
namespace NicheGraphics::InkHUD
{
class Notification
{
public:
enum Type : uint8_t { NOTIFICATION_MESSAGE_BROADCAST, NOTIFICATION_MESSAGE_DIRECT, NOTIFICATION_BATTERY } type;
uint32_t timestamp;
uint8_t getChannel() { return channel; }
uint32_t getSender() { return sender; }
uint8_t getBatteryPercentage() { return batteryPercentage; }
friend class NotificationApplet;
protected:
uint8_t channel;
uint32_t sender;
uint8_t batteryPercentage;
};
} // namespace NicheGraphics::InkHUD
#endif
@@ -0,0 +1,243 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./NotificationApplet.h"
#include "./Notification.h"
#include "graphics/niche/InkHUD/Persistence.h"
#include "meshUtils.h"
#include "modules/TextMessageModule.h"
#include "RTC.h"
using namespace NicheGraphics;
InkHUD::NotificationApplet::NotificationApplet()
{
textMessageObserver.observe(textMessageModule);
}
// Collect meta-info about the text message, and ask for approval for the notification
// No need to save the message itself; we can use the cached InkHUD::latestMessage data during render()
int InkHUD::NotificationApplet::onReceiveTextMessage(const meshtastic_MeshPacket *p)
{
// System applets are always active
assert(isActive());
// Abort if feature disabled
// This is a bit clumsy, but avoids complicated handling when the feature is enabled / disabled
if (!settings->optionalFeatures.notifications)
return 0;
// Abort if this is an outgoing message
if (getFrom(p) == nodeDB->getNodeNum())
return 0;
Notification n;
n.timestamp = getValidTime(RTCQuality::RTCQualityDevice, true); // Current RTC time
// Gather info: in-channel message
if (isBroadcast(p->to)) {
n.type = Notification::Type::NOTIFICATION_MESSAGE_BROADCAST;
n.channel = p->channel;
}
// Gather info: DM
else {
n.type = Notification::Type::NOTIFICATION_MESSAGE_DIRECT;
n.sender = p->from;
}
// Close an old notification, if shown
dismiss();
// Check if we should display the notification
// A foreground applet might already be displaying this info
hasNotification = true;
currentNotification = n;
if (isApproved()) {
bringToForeground();
inkhud->forceUpdate();
} else
hasNotification = false; // Clear the pending notification: it was rejected
// Return zero: no issues here, carry on notifying other observers!
return 0;
}
void InkHUD::NotificationApplet::onRender()
{
// Clear the region beneath the tile
// Most applets are drawing onto an empty frame buffer and don't need to do this
// We do need to do this with the battery though, as it is an "overlay"
fillRect(0, 0, width(), height(), WHITE);
// Padding (horizontal)
const uint16_t padW = 4;
// Main border
drawRect(0, 0, width(), height(), BLACK);
// drawRect(1, 1, width() - 2, height() - 2, BLACK);
// Timestamp (potentially)
// ====================
std::string ts = getTimeString(currentNotification.timestamp);
uint16_t tsW = 0;
int16_t divX = 0;
// Timestamp available
if (ts.length() > 0) {
tsW = getTextWidth(ts);
divX = padW + tsW + padW;
hatchRegion(0, 0, divX, height(), 2, BLACK); // Fill with a dark background
drawLine(divX, 0, divX, height() - 1, BLACK); // Draw divider between timestamp and main text
setCrop(1, 1, divX - 1, height() - 2);
// Drop shadow
setTextColor(WHITE);
printThick(padW + (tsW / 2), height() / 2, ts, 4, 4);
// Bold text
setTextColor(BLACK);
printThick(padW + (tsW / 2), height() / 2, ts, 2, 1);
}
// Main text
// =====================
// Background fill
// - medium dark (1/3)
hatchRegion(divX, 0, width() - divX - 1, height(), 3, BLACK);
uint16_t availableWidth = width() - divX - padW;
std::string text = getNotificationText(availableWidth);
int16_t textM = divX + padW + (getTextWidth(text) / 2);
// Restrict area for printing
// - don't overlap border, or divider
setCrop(divX + 1, 1, (width() - (divX + 1) - 1), height() - 2);
// Drop shadow
// - thick white text
setTextColor(WHITE);
printThick(textM, height() / 2, text, 4, 4);
// Main text
// - faux bold: double width
setTextColor(BLACK);
printThick(textM, height() / 2, text, 2, 1);
}
void InkHUD::NotificationApplet::onForeground()
{
handleInput = true; // Intercept the button input for our applet, so we can dismiss the notification
}
void InkHUD::NotificationApplet::onBackground()
{
handleInput = false;
}
void InkHUD::NotificationApplet::onButtonShortPress()
{
dismiss();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
}
void InkHUD::NotificationApplet::onButtonLongPress()
{
dismiss();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
}
// Ask the WindowManager to check whether any displayed applets are already displaying the info from this notification
// Called internally when we first get a "notifiable event", and then again before render,
// in case autoshow swapped which applet was displayed
bool InkHUD::NotificationApplet::isApproved()
{
// Instead of an assert
if (!hasNotification) {
LOG_WARN("No notif to approve");
return false;
}
// Ask all visible user applets for approval
for (Applet *ua : inkhud->userApplets) {
if (ua->isForeground() && !ua->approveNotification(currentNotification))
return false;
}
return true;
}
// Mark that the notification should no-longer be rendered
// In addition to calling thing method, code needs to request a re-render of all applets
void InkHUD::NotificationApplet::dismiss()
{
sendToBackground();
hasNotification = false;
// Not requesting update directly from this method,
// as it is used to dismiss notifications which have been made redundant by autoshow settings, before they are ever drawn
}
// Get a string for the main body text of a notification
// Formatted to suit screen width
// Takes info from InkHUD::currentNotification
std::string InkHUD::NotificationApplet::getNotificationText(uint16_t widthAvailable)
{
assert(hasNotification);
std::string text;
// Text message
// ==============
if (IS_ONE_OF(currentNotification.type, Notification::Type::NOTIFICATION_MESSAGE_DIRECT,
Notification::Type::NOTIFICATION_MESSAGE_BROADCAST)) {
// Although we are handling DM and broadcast notifications together, we do need to treat them slightly differently
bool isBroadcast = currentNotification.type == Notification::Type::NOTIFICATION_MESSAGE_BROADCAST;
// Pick source of message
MessageStore::Message *message =
isBroadcast ? &inkhud->persistence->latestMessage.broadcast : &inkhud->persistence->latestMessage.dm;
// Find info about the sender
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(message->sender);
// Leading tag (channel vs. DM)
text += isBroadcast ? "From:" : "DM: ";
// Sender id
if (node && node->has_user)
text += parseShortName(node);
else
text += hexifyNodeNum(message->sender);
// Check if text fits
// - use a longer string, if we have the space
if (getTextWidth(text) < widthAvailable * 0.5) {
text.clear();
// Leading tag (channel vs. DM)
text += isBroadcast ? "Msg from " : "DM from ";
// Sender id
if (node && node->has_user)
text += parseShortName(node);
else
text += hexifyNodeNum(message->sender);
text += ": ";
text += message->text;
}
}
// Parse any non-ascii characters and return
return parse(text);
}
#endif
@@ -0,0 +1,53 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
Pop-up notification bar, on screen top edge
Displays information we feel is important, but which is not shown on currently focused applet(s)
E.g.: messages, while viewing map, etc
Feature should be optional; enable disable via on-screen menu
*/
#pragma once
#include "configuration.h"
#include "concurrency/OSThread.h"
#include "graphics/niche/InkHUD/SystemApplet.h"
namespace NicheGraphics::InkHUD
{
class NotificationApplet : public SystemApplet
{
public:
NotificationApplet();
void onRender() override;
void onForeground() override;
void onBackground() override;
void onButtonShortPress() override;
void onButtonLongPress() override;
int onReceiveTextMessage(const meshtastic_MeshPacket *p);
bool isApproved(); // Does a foreground applet make notification redundant?
void dismiss(); // Close the Notification Popup
protected:
// Get notified when a new text message arrives
CallbackObserver<NotificationApplet, const meshtastic_MeshPacket *> textMessageObserver =
CallbackObserver<NotificationApplet, const meshtastic_MeshPacket *>(this, &NotificationApplet::onReceiveTextMessage);
std::string getNotificationText(uint16_t widthAvailable); // Get text for notification, to suit screen width
bool hasNotification = false; // Only used for assert. Todo: remove?
Notification currentNotification = Notification(); // Set when something notification-worthy happens. Used by render()
};
} // namespace NicheGraphics::InkHUD
#endif
@@ -0,0 +1,77 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./PairingApplet.h"
using namespace NicheGraphics;
InkHUD::PairingApplet::PairingApplet()
{
bluetoothStatusObserver.observe(&bluetoothStatus->onNewStatus);
}
void InkHUD::PairingApplet::onRender()
{
// Header
setFont(fontMedium);
printAt(X(0.5), Y(0.25), "Bluetooth", CENTER, BOTTOM);
setFont(fontSmall);
printAt(X(0.5), Y(0.25), "Enter this code", CENTER, TOP);
// Passkey
setFont(fontMedium);
printThick(X(0.5), Y(0.5), passkey.substr(0, 3) + " " + passkey.substr(3), 3, 2);
// Device's bluetooth name, if it will fit
setFont(fontSmall);
std::string name = "Name: " + parse(getDeviceName());
if (getTextWidth(name) > width()) // Too wide, try without the leading "Name: "
name = parse(getDeviceName());
if (getTextWidth(name) < width()) // Does it fit?
printAt(X(0.5), Y(0.75), name, CENTER, MIDDLE);
}
void InkHUD::PairingApplet::onForeground()
{
// Prevent most other applets from requesting update, and skip their rendering entirely
// Another system applet with a higher precedence can potentially ignore this
SystemApplet::lockRendering = true;
SystemApplet::lockRequests = true;
}
void InkHUD::PairingApplet::onBackground()
{
// Allow normal update behavior to resume
SystemApplet::lockRendering = false;
SystemApplet::lockRequests = false;
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
}
int InkHUD::PairingApplet::onBluetoothStatusUpdate(const meshtastic::Status *status)
{
// The standard Meshtastic convention is to pass these "generic" Status objects,
// check their type, and then cast them.
// We'll mimic that behavior, just to keep in line with the other Statuses,
// even though I'm not sure what the original reason for jumping through these extra hoops was.
assert(status->getStatusType() == STATUS_TYPE_BLUETOOTH);
meshtastic::BluetoothStatus *bluetoothStatus = (meshtastic::BluetoothStatus *)status;
// When pairing begins
if (bluetoothStatus->getConnectionState() == meshtastic::BluetoothStatus::ConnectionState::PAIRING) {
// Store the passkey for rendering
passkey = bluetoothStatus->getPasskey();
// Show pairing screen
bringToForeground();
}
// When pairing ends
// or rather, when something changes, and we shouldn't be showing the pairing screen
else if (isForeground())
sendToBackground();
return 0; // No special result to report back to Observable
}
#endif
@@ -0,0 +1,41 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
Shows the Bluetooth passkey during pairing
*/
#pragma once
#include "configuration.h"
#include "graphics/niche/InkHUD/SystemApplet.h"
#include "main.h"
namespace NicheGraphics::InkHUD
{
class PairingApplet : public SystemApplet
{
public:
PairingApplet();
void onRender() override;
void onForeground() override;
void onBackground() override;
int onBluetoothStatusUpdate(const meshtastic::Status *status);
protected:
// Get notified when status of the Bluetooth connection changes
CallbackObserver<PairingApplet, const meshtastic::Status *> bluetoothStatusObserver =
CallbackObserver<PairingApplet, const meshtastic::Status *>(this, &PairingApplet::onBluetoothStatusUpdate);
std::string passkey = ""; // Passkey. Six digits, possibly with leading zeros
};
} // namespace NicheGraphics::InkHUD
#endif
@@ -0,0 +1,13 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./PlaceholderApplet.h"
using namespace NicheGraphics;
void InkHUD::PlaceholderApplet::onRender()
{
// This placeholder applet fills its area with sparse diagonal lines
hatchRegion(0, 0, width(), height(), 8, BLACK);
}
#endif
@@ -0,0 +1,29 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
Shown when a tile doesn't have any other valid Applets
Fills the area with diagonal lines
*/
#include "configuration.h"
#include "graphics/niche/InkHUD/SystemApplet.h"
namespace NicheGraphics::InkHUD
{
class PlaceholderApplet : public SystemApplet
{
public:
void onRender() override;
// Note: onForeground, onBackground, and wantsToRender are not meaningful for this applet.
// The window manager decides when and where it should be rendered
// It may be drawn to several different tiles during an Renderer::render call
};
} // namespace NicheGraphics::InkHUD
#endif
@@ -0,0 +1,235 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./TipsApplet.h"
#include "graphics/niche/InkHUD/Persistence.h"
#include "main.h"
using namespace NicheGraphics;
InkHUD::TipsApplet::TipsApplet()
{
// Decide which tips (if any) should be shown to user after the boot screen
// Welcome screen
if (settings->tips.firstBoot)
tipQueue.push_back(Tip::WELCOME);
// Antenna, region, timezone
// Shown at boot if region not yet set
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET)
tipQueue.push_back(Tip::FINISH_SETUP);
// Shutdown info
// Shown until user performs one valid shutdown
if (!settings->tips.safeShutdownSeen)
tipQueue.push_back(Tip::SAFE_SHUTDOWN);
// Using the UI
if (settings->tips.firstBoot) {
tipQueue.push_back(Tip::CUSTOMIZATION);
tipQueue.push_back(Tip::BUTTONS);
}
// Catch an incorrect attempt at rotating display
if (config.display.flip_screen)
tipQueue.push_back(Tip::ROTATION);
// Applet is foreground immediately at boot, but is obscured by LogoApplet, which is also foreground
// LogoApplet can be considered to have a higher Z-index, because it is placed before TipsApplet in the systemApplets vector
if (!tipQueue.empty())
bringToForeground();
}
void InkHUD::TipsApplet::onRender()
{
switch (tipQueue.front()) {
case Tip::WELCOME:
renderWelcome();
break;
case Tip::FINISH_SETUP: {
setFont(fontMedium);
printAt(0, 0, "Tip: Finish Setup");
setFont(fontSmall);
int16_t cursorY = fontMedium.lineHeight() * 1.5;
printAt(0, cursorY, "- connect antenna");
cursorY += fontSmall.lineHeight() * 1.2;
printAt(0, cursorY, "- connect a client app");
// Only if region not set
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
cursorY += fontSmall.lineHeight() * 1.2;
printAt(0, cursorY, "- set region");
}
// Only if tz not set
if (!(*config.device.tzdef && config.device.tzdef[0] != 0)) {
cursorY += fontSmall.lineHeight() * 1.2;
printAt(0, cursorY, "- set timezone");
}
cursorY += fontSmall.lineHeight() * 1.5;
printAt(0, cursorY, "More info at meshtastic.org");
setFont(fontSmall);
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
} break;
case Tip::SAFE_SHUTDOWN: {
setFont(fontMedium);
printAt(0, 0, "Tip: Shutdown");
setFont(fontSmall);
std::string shutdown;
shutdown += "Before removing power, please shut down from InkHUD menu, or a client app. \n";
shutdown += "\n";
shutdown += "This ensures data is saved.";
printWrapped(0, fontMedium.lineHeight() * 1.5, width(), shutdown);
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
} break;
case Tip::CUSTOMIZATION: {
setFont(fontMedium);
printAt(0, 0, "Tip: Customization");
setFont(fontSmall);
printWrapped(0, fontMedium.lineHeight() * 1.5, width(),
"Configure & control display with the InkHUD menu. Optional features, layout, rotation, and more.");
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
} break;
case Tip::BUTTONS: {
setFont(fontMedium);
printAt(0, 0, "Tip: Buttons");
setFont(fontSmall);
int16_t cursorY = fontMedium.lineHeight() * 1.5;
printAt(0, cursorY, "User Button");
cursorY += fontSmall.lineHeight() * 1.2;
printAt(0, cursorY, "- short press: next");
cursorY += fontSmall.lineHeight() * 1.2;
printAt(0, cursorY, "- long press: select / open menu");
cursorY += fontSmall.lineHeight() * 1.5;
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
} break;
case Tip::ROTATION: {
setFont(fontMedium);
printAt(0, 0, "Tip: Rotation");
setFont(fontSmall);
printWrapped(0, fontMedium.lineHeight() * 1.5, width(),
"To rotate the display, use the InkHUD menu. Long-press the user button > Options > Rotate.");
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
// Revert the "flip screen" setting, preventing this message showing again
config.display.flip_screen = false;
nodeDB->saveToDisk(SEGMENT_DEVICESTATE);
} break;
}
}
// This tip has its own render method, only because it's a big block of code
// Didn't want to clutter up the switch in onRender too much
void InkHUD::TipsApplet::renderWelcome()
{
uint16_t padW = X(0.05);
// Block 1 - logo & title
// ========================
// Logo size
uint16_t logoWLimit = X(0.3);
uint16_t logoHLimit = Y(0.3);
uint16_t logoW = getLogoWidth(logoWLimit, logoHLimit);
uint16_t logoH = getLogoHeight(logoWLimit, logoHLimit);
// Title size
setFont(fontMedium);
std::string title;
if (width() >= 200) // Future proofing: hide if *tiny* display
title = "meshtastic.org";
uint16_t titleW = getTextWidth(title);
// Center the block
// Desired effect: equal margin from display edge for logo left and title right
int16_t block1Y = Y(0.3);
int16_t block1CX = X(0.5) + (logoW / 2) - (titleW / 2);
int16_t logoCX = block1CX - (logoW / 2) - (padW / 2);
int16_t titleCX = block1CX + (titleW / 2) + (padW / 2);
// Draw block
drawLogo(logoCX, block1Y, logoW, logoH);
printAt(titleCX, block1Y, title, CENTER, MIDDLE);
// Block 2 - subtitle
// =======================
setFont(fontSmall);
std::string subtitle = "InkHUD";
if (width() >= 200)
subtitle += " - A Heads-Up Display"; // Future proofing: narrower for tiny display
printAt(X(0.5), Y(0.6), subtitle, CENTER, MIDDLE);
// Block 3 - press to continue
// ============================
printAt(X(0.5), Y(1), "Press button to continue", CENTER, BOTTOM);
}
void InkHUD::TipsApplet::onForeground()
{
// Prevent most other applets from requesting update, and skip their rendering entirely
// Another system applet with a higher precedence can potentially ignore this
SystemApplet::lockRendering = true;
SystemApplet::lockRequests = true;
SystemApplet::handleInput = true; // Our applet should handle button input (unless another system applet grabs it first)
}
void InkHUD::TipsApplet::onBackground()
{
// Allow normal update behavior to resume
SystemApplet::lockRendering = false;
SystemApplet::lockRequests = false;
SystemApplet::handleInput = false;
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
}
// While our SystemApplet::handleInput flag is true
void InkHUD::TipsApplet::onButtonShortPress()
{
tipQueue.pop_front();
// All tips done
if (tipQueue.empty()) {
// Record that user has now seen the "tutorial" set of tips
// Don't show them on subsequent boots
if (settings->tips.firstBoot) {
settings->tips.firstBoot = false;
inkhud->persistence->saveSettings();
}
// Close applet, and full refresh to clean the screen
// Need to force update, because our request would be ignored otherwise, as we are now background
sendToBackground();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
}
// More tips left
else
requestUpdate();
}
#endif
@@ -0,0 +1,50 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
Shows info on how to use InkHUD
- tutorial at first boot
- additional tips in certain situation (e.g. bad shutdown, region unset)
*/
#pragma once
#include "configuration.h"
#include "graphics/niche/InkHUD/SystemApplet.h"
namespace NicheGraphics::InkHUD
{
class TipsApplet : public SystemApplet
{
protected:
enum class Tip {
WELCOME,
FINISH_SETUP,
SAFE_SHUTDOWN,
CUSTOMIZATION,
BUTTONS,
ROTATION,
};
public:
TipsApplet();
void onRender() override;
void onForeground() override;
void onBackground() override;
void onButtonShortPress() override;
protected:
void renderWelcome(); // Very first screen of tutorial
std::deque<Tip> tipQueue; // List of tips to show, one after another
WindowManager *windowManager = nullptr; // For convenience. Set in constructor.
};
} // namespace NicheGraphics::InkHUD
#endif
@@ -0,0 +1,139 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./AllMessageApplet.h"
using namespace NicheGraphics;
void InkHUD::AllMessageApplet::onActivate()
{
textMessageObserver.observe(textMessageModule);
}
void InkHUD::AllMessageApplet::onDeactivate()
{
textMessageObserver.unobserve(textMessageModule);
}
// We're not consuming the data passed to this method;
// we're just just using it to trigger a render
int InkHUD::AllMessageApplet::onReceiveTextMessage(const meshtastic_MeshPacket *p)
{
// Abort if applet fully deactivated
// Already handled by onActivate and onDeactivate, but good practice for all applets
if (!isActive())
return 0;
// Abort if this is an outgoing message
if (getFrom(p) == nodeDB->getNodeNum())
return 0;
requestAutoshow(); // Want to become foreground, if permitted
requestUpdate(); // Want to update display, if applet is foreground
// Return zero: no issues here, carry on notifying other observers!
return 0;
}
void InkHUD::AllMessageApplet::onRender()
{
// Find newest message, regardless of whether DM or broadcast
MessageStore::Message *message;
if (latestMessage->wasBroadcast)
message = &latestMessage->broadcast;
else
message = &latestMessage->dm;
// Short circuit: no text message
if (!message->sender) {
printAt(X(0.5), Y(0.5), "No Message", CENTER, MIDDLE);
return;
}
// ===========================
// Header (sender, timestamp)
// ===========================
// Y position for divider
// - between header text and messages
std::string header;
// RX Time
// - if valid
std::string timeString = getTimeString(message->timestamp);
if (timeString.length() > 0) {
header += timeString;
header += ": ";
}
// Sender's id
// - short name and long name, if available, or
// - node id
meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(message->sender);
if (sender && sender->has_user) {
header += parseShortName(sender); // May be last-four of node if unprintable (emoji, etc)
header += " (";
header += parse(sender->user.long_name);
header += ")";
} else
header += hexifyNodeNum(message->sender);
// Draw a "standard" applet header
drawHeader(header);
// Fade the right edge of the header, if text spills over edge
uint8_t wF = getFont().lineHeight() / 2; // Width of fade effect
uint8_t hF = getHeaderHeight(); // Height of fade effect
if (getCursorX() > width())
hatchRegion(width() - wF - 1, 1, wF, hF, 2, WHITE);
// Dimensions of the header
constexpr int16_t padDivH = 2;
const int16_t headerDivY = Applet::getHeaderHeight() - 1;
// ===================
// Print message text
// ===================
// Parse any non-ascii chars in the message
std::string text = parse(message->text);
// Extra gap below the header
int16_t textTop = headerDivY + padDivH;
// Attempt to print with fontLarge
uint32_t textHeight;
setFont(fontLarge);
textHeight = getWrappedTextHeight(0, width(), text);
if (textHeight <= (uint32_t)height()) {
printWrapped(0, textTop, width(), text);
return;
}
// Fallback (too large): attempt to print with fontMedium
setFont(fontMedium);
textHeight = getWrappedTextHeight(0, width(), text);
if (textHeight <= (uint32_t)height()) {
printWrapped(0, textTop, width(), text);
return;
}
// Fallback (too large): print with fontSmall
setFont(fontSmall);
printWrapped(0, textTop, width(), text);
}
// Don't show notifications for text messages when our applet is displayed
bool InkHUD::AllMessageApplet::approveNotification(Notification &n)
{
if (n.type == Notification::Type::NOTIFICATION_MESSAGE_BROADCAST)
return false;
else if (n.type == Notification::Type::NOTIFICATION_MESSAGE_DIRECT)
return false;
else
return true;
}
#endif
@@ -0,0 +1,49 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
Shows the latest incoming text message, as well as sender.
Both broadcast and direct messages will be shown here, from all channels.
This module doesn't doesn't use the devicestate.rx_text_message,' as this is overwritten to contain outgoing messages
This module doesn't collect its own text message. Instead, the WindowManager stores the most recent incoming text message.
This is available to any interested modules (SingeMessageApplet, NotificationApplet etc.) via InkHUD::latestMessage
We do still receive notifications from the text message module though,
to know when a new message has arrived, and trigger the update.
*/
#pragma once
#include "configuration.h"
#include "graphics/niche/InkHUD/Applet.h"
#include "modules/TextMessageModule.h"
namespace NicheGraphics::InkHUD
{
class Applet;
class AllMessageApplet : public Applet
{
public:
void onRender() override;
void onActivate() override;
void onDeactivate() override;
int onReceiveTextMessage(const meshtastic_MeshPacket *p);
bool approveNotification(Notification &n) override; // Which notifications to suppress
protected:
// Used to register our text message callback
CallbackObserver<AllMessageApplet, const meshtastic_MeshPacket *> textMessageObserver =
CallbackObserver<AllMessageApplet, const meshtastic_MeshPacket *>(this, &AllMessageApplet::onReceiveTextMessage);
};
} // namespace NicheGraphics::InkHUD
#endif
@@ -0,0 +1,132 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./DMApplet.h"
using namespace NicheGraphics;
void InkHUD::DMApplet::onActivate()
{
textMessageObserver.observe(textMessageModule);
}
void InkHUD::DMApplet::onDeactivate()
{
textMessageObserver.unobserve(textMessageModule);
}
// We're not consuming the data passed to this method;
// we're just just using it to trigger a render
int InkHUD::DMApplet::onReceiveTextMessage(const meshtastic_MeshPacket *p)
{
// Abort if applet fully deactivated
// Already handled by onActivate and onDeactivate, but good practice for all applets
if (!isActive())
return 0;
// If DM (not broadcast)
if (!isBroadcast(p->to)) {
// Want to update display, if applet is foreground
requestUpdate();
// If this was an incoming message, suggest that our applet becomes foreground, if permitted
if (getFrom(p) != nodeDB->getNodeNum())
requestAutoshow();
}
// Return zero: no issues here, carry on notifying other observers!
return 0;
}
void InkHUD::DMApplet::onRender()
{
// Abort if no text message
if (!latestMessage->dm.sender) {
printAt(X(0.5), Y(0.5), "No DMs", CENTER, MIDDLE);
return;
}
// ===========================
// Header (sender, timestamp)
// ===========================
// Y position for divider
// - between header text and messages
std::string header;
// RX Time
// - if valid
std::string timeString = getTimeString(latestMessage->dm.timestamp);
if (timeString.length() > 0) {
header += timeString;
header += ": ";
}
// Sender's id
// - shortname and long name, if available, or
// - node id
meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(latestMessage->dm.sender);
if (sender && sender->has_user) {
header += parseShortName(sender); // May be last-four of node if unprintable (emoji, etc)
header += " (";
header += parse(sender->user.long_name);
header += ")";
} else
header += hexifyNodeNum(latestMessage->dm.sender);
// Draw a "standard" applet header
drawHeader(header);
// Fade the right edge of the header, if text spills over edge
uint8_t wF = getFont().lineHeight() / 2; // Width of fade effect
uint8_t hF = getHeaderHeight(); // Height of fade effect
if (getCursorX() > width())
hatchRegion(width() - wF - 1, 1, wF, hF, 2, WHITE);
// Dimensions of the header
constexpr int16_t padDivH = 2;
const int16_t headerDivY = Applet::getHeaderHeight() - 1;
// ===================
// Print message text
// ===================
// Parse any non-ascii chars in the message
std::string text = parse(latestMessage->dm.text);
// Extra gap below the header
int16_t textTop = headerDivY + padDivH;
// Attempt to print with fontLarge
uint32_t textHeight;
setFont(fontLarge);
textHeight = getWrappedTextHeight(0, width(), text);
if (textHeight <= (uint32_t)height()) {
printWrapped(0, textTop, width(), text);
return;
}
// Fallback (too large): attempt to print with fontMedium
setFont(fontMedium);
textHeight = getWrappedTextHeight(0, width(), text);
if (textHeight <= (uint32_t)height()) {
printWrapped(0, textTop, width(), text);
return;
}
// Fallback (too large): print with fontSmall
setFont(fontSmall);
printWrapped(0, textTop, width(), text);
}
// Don't show notifications for direct messages when our applet is displayed
bool InkHUD::DMApplet::approveNotification(Notification &n)
{
if (n.type == Notification::Type::NOTIFICATION_MESSAGE_DIRECT)
return false;
else
return true;
}
#endif
@@ -0,0 +1,49 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
Shows the latest incoming *Direct Message* (DM), as well as sender.
This compliments the threaded message applets
This module doesn't doesn't use the devicestate.rx_text_message,' as this is overwritten to contain outgoing messages
This module doesn't collect its own text message. Instead, the WindowManager stores the most recent incoming text message.
This is available to any interested modules (SingeMessageApplet, NotificationApplet etc.) via InkHUD::latestMessage
We do still receive notifications from the text message module though,
to know when a new message has arrived, and trigger the update.
*/
#pragma once
#include "configuration.h"
#include "graphics/niche/InkHUD/Applet.h"
#include "modules/TextMessageModule.h"
namespace NicheGraphics::InkHUD
{
class Applet;
class DMApplet : public Applet
{
public:
void onRender() override;
void onActivate() override;
void onDeactivate() override;
int onReceiveTextMessage(const meshtastic_MeshPacket *p);
bool approveNotification(Notification &n) override; // Which notifications to suppress
protected:
// Used to register our text message callback
CallbackObserver<DMApplet, const meshtastic_MeshPacket *> textMessageObserver =
CallbackObserver<DMApplet, const meshtastic_MeshPacket *>(this, &DMApplet::onReceiveTextMessage);
};
} // namespace NicheGraphics::InkHUD
#endif
@@ -0,0 +1,124 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "RTC.h"
#include "gps/GeoCoord.h"
#include "./HeardApplet.h"
using namespace NicheGraphics;
void InkHUD::HeardApplet::onActivate()
{
// When applet begins, pre-fill with stale info from NodeDB
populateFromNodeDB();
}
void InkHUD::HeardApplet::onDeactivate()
{
// Avoid an unlikely situation where frequent activation / deactivation populates duplicate info from node DB
cards.clear();
}
// When base applet hears a new packet, it extracts the info and passes it to us as CardInfo
// We need to store it (at front to sort recent), and request display update if our list has visibly changed as a result
void InkHUD::HeardApplet::handleParsed(CardInfo c)
{
// Grab the previous entry.
// To check if the new data is different enough to justify re-render
// Need to cache now, before we manipulate the deque
CardInfo previous;
if (!cards.empty())
previous = cards.at(0);
// If we're updating an existing entry, remove the old one. Will reinsert at front
for (auto it = cards.begin(); it != cards.end(); ++it) {
if (it->nodeNum == c.nodeNum) {
cards.erase(it);
break;
}
}
cards.push_front(c); // Insert into base class' card collection
cards.resize(min(maxCards(), (uint8_t)cards.size())); // Don't keep more cards than we could *ever* fit on screen
cards.shrink_to_fit();
// Our rendered image needs to change if:
if (previous.nodeNum != c.nodeNum // Different node
|| previous.signal != c.signal // or different signal strength
|| previous.distanceMeters != c.distanceMeters // or different position
|| previous.hopsAway != c.hopsAway) // or different hops away
{
requestAutoshow();
requestUpdate();
}
}
// When applet is activated, pre-fill with stale data from NodeDB
// We're sorting using the last_heard value. Susceptible to weirdness if node's RTC changes.
// No SNR is available in node db, so we can't calculate signal either
// These initial cards from node db will be gradually pushed out by new packets which originate from out base applet instead
void InkHUD::HeardApplet::populateFromNodeDB()
{
// Fill a collection with pointers to each node in db
std::vector<meshtastic_NodeInfoLite *> ordered;
for (auto mn = nodeDB->meshNodes->begin(); mn != nodeDB->meshNodes->end(); ++mn) {
// Only copy if valid, and not our own node
if (mn->num != 0 && mn->num != nodeDB->getNodeNum())
ordered.push_back(&*mn);
}
// Sort the collection by age
std::sort(ordered.begin(), ordered.end(), [](meshtastic_NodeInfoLite *top, meshtastic_NodeInfoLite *bottom) -> bool {
return (top->last_heard > bottom->last_heard);
});
// Keep the most recent entries only
// Just enough to fill the screen
if (ordered.size() > maxCards())
ordered.resize(maxCards());
// Create card info for these (stale) node observations
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
for (meshtastic_NodeInfoLite *node : ordered) {
CardInfo c;
c.nodeNum = node->num;
if (node->has_hops_away)
c.hopsAway = node->hops_away;
if (nodeDB->hasValidPosition(node) && nodeDB->hasValidPosition(ourNode)) {
// Get lat and long as float
// Meshtastic stores these as integers internally
float ourLat = ourNode->position.latitude_i * 1e-7;
float ourLong = ourNode->position.longitude_i * 1e-7;
float theirLat = node->position.latitude_i * 1e-7;
float theirLong = node->position.longitude_i * 1e-7;
c.distanceMeters = (int32_t)GeoCoord::latLongToMeter(theirLat, theirLong, ourLat, ourLong);
}
// Insert into the card collection (member of base class)
cards.push_back(c);
}
}
// Text drawn in the usual applet header
// Handled by base class: ChronoListApplet
std::string InkHUD::HeardApplet::getHeaderText()
{
uint16_t nodeCount = nodeDB->getNumMeshNodes() - 1; // Don't count our own node
std::string text = "Heard: ";
// Print node count, if nodeDB not yet nearing full
if (nodeCount < MAX_NUM_NODES) {
text += to_string(nodeCount); // Max nodes
text += " ";
text += (nodeCount == 1) ? "node" : "nodes";
}
return text;
}
#endif
@@ -0,0 +1,35 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
Shows a list of all nodes (recently heard or not), sorted by time last heard.
Most of the work is done by the InkHUD::NodeListApplet base class
*/
#pragma once
#include "configuration.h"
#include "graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.h"
namespace NicheGraphics::InkHUD
{
class HeardApplet : public NodeListApplet
{
public:
HeardApplet() : NodeListApplet("HeardApplet") {}
void onActivate() override;
void onDeactivate() override;
protected:
void handleParsed(CardInfo c) override; // Store new info, and update display if needed
std::string getHeaderText() override; // Set title for this applet
void populateFromNodeDB(); // Pre-fill the CardInfo collection from NodeDB
};
} // namespace NicheGraphics::InkHUD
#endif
@@ -0,0 +1,110 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./PositionsApplet.h"
using namespace NicheGraphics;
void InkHUD::PositionsApplet::onRender()
{
// Draw the usual map applet first
MapApplet::onRender();
// Draw our latest "node of interest" as a special marker
// -------------------------------------------------------
// We might be rendering because we got a position packet from them
// We might be rendering because our own position updated
// Either way, we still highlight which node most recently sent us a position packet
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(lastFrom);
if (node && nodeDB->hasValidPosition(node) && enoughMarkers())
drawLabeledMarker(node);
}
// Determine if we need to redraw the map, when we receive a new position packet
ProcessMessage InkHUD::PositionsApplet::handleReceived(const meshtastic_MeshPacket &mp)
{
// If applet is not active, we shouldn't be handling any data
// It's good practice for all applets to implement an early return like this
// for PositionsApplet, this is **required** - it's where we're handling active vs deactive
if (!isActive())
return ProcessMessage::CONTINUE;
// Try decode a position from the packet
bool hasPosition = false;
float lat;
float lng;
if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.decoded.portnum == meshtastic_PortNum_POSITION_APP) {
meshtastic_Position position = meshtastic_Position_init_default;
if (pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, &meshtastic_Position_msg, &position)) {
if (position.has_latitude_i && position.has_longitude_i // Actually has position
&& (position.latitude_i != 0 || position.longitude_i != 0)) // Position isn't "null island"
{
hasPosition = true;
lat = position.latitude_i * 1e-7; // Convert from Meshtastic's internal int32_t format
lng = position.longitude_i * 1e-7;
}
}
}
// Skip if we didn't get a valid position
if (!hasPosition)
return ProcessMessage::CONTINUE;
bool hasHopsAway = (mp.hop_start != 0 && mp.hop_limit <= mp.hop_start); // From NodeDB::updateFrom
uint8_t hopsAway = mp.hop_start - mp.hop_limit;
// Determine if the position packet would change anything on-screen
// -----------------------------------------------------------------
bool somethingChanged = false;
// If our own position
if (isFromUs(&mp)) {
// We get frequent position updates from connected phone
// Only update if we're travelled some distance, for rate limiting
// Todo: smarter detection of position changes
if (GeoCoord::latLongToMeter(ourLastLat, ourLastLng, lat, lng) > 50) {
somethingChanged = true;
ourLastLat = lat;
ourLastLng = lng;
}
}
// If someone else's position
else {
// Check if this position is from someone different than our previous position packet
if (mp.from != lastFrom) {
somethingChanged = true;
lastFrom = mp.from;
lastLat = lat;
lastLng = lng;
lastHopsAway = hopsAway;
}
// Same sender: check if position changed
// Todo: smarter detection of position changes
else if (GeoCoord::latLongToMeter(lastLat, lastLng, lat, lng) > 10) {
somethingChanged = true;
lastLat = lat;
lastLng = lng;
}
// Same sender, same position: check if hops changed
// Only pay attention if the hopsAway value is valid
else if (hasHopsAway && (hopsAway != lastHopsAway)) {
somethingChanged = true;
lastHopsAway = hopsAway;
}
}
// Decision reached
// -----------------
if (somethingChanged) {
requestAutoshow(); // Todo: only request this in some situations?
requestUpdate();
}
return ProcessMessage::CONTINUE;
}
#endif
@@ -0,0 +1,43 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
Plots position of all nodes from DB, with North facing up.
Scaled to fit the most distant node.
Size of cross represents hops away.
The node which has most recently sent a position will be labeled.
*/
#pragma once
#include "configuration.h"
#include "graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.h"
#include "SinglePortModule.h"
namespace NicheGraphics::InkHUD
{
class PositionsApplet : public MapApplet, public SinglePortModule
{
public:
PositionsApplet() : SinglePortModule("PositionsApplet", meshtastic_PortNum_POSITION_APP) {}
void onRender() override;
protected:
ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
NodeNum lastFrom = 0; // Sender of most recent (non-local) position packet
float lastLat = 0.0;
float lastLng = 0.0;
float lastHopsAway = 0;
float ourLastLat = 0.0; // Info about the most recent (non-local) position packet
float ourLastLng = 0.0; // Info about most recent *local* position
};
} // namespace NicheGraphics::InkHUD
#endif
@@ -0,0 +1,153 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./RecentsListApplet.h"
#include "RTC.h"
using namespace NicheGraphics;
InkHUD::RecentsListApplet::RecentsListApplet() : NodeListApplet("RecentsListApplet"), concurrency::OSThread("RecentsListApplet")
{
// No scheduled tasks initially
OSThread::disable();
}
void InkHUD::RecentsListApplet::onActivate()
{
// When the applet is activated, begin scheduled purging of any nodes which are no longer "active"
OSThread::enabled = true;
OSThread::setIntervalFromNow(60 * 1000UL); // Every minute
}
void InkHUD::RecentsListApplet::onDeactivate()
{
// Halt scheduled purging
OSThread::disable();
}
int32_t InkHUD::RecentsListApplet::runOnce()
{
prune(); // Remove CardInfo and Age record for nodes which we haven't heard recently
return OSThread::interval;
}
// When base applet hears a new packet, it extracts the info and passes it to us as CardInfo
// We need to store it (at front to sort recent), and request display update if our list has visibly changed as a result
// We also need to record the current time against the nodenum, so we know when it becomes inactive
void InkHUD::RecentsListApplet::handleParsed(CardInfo c)
{
// Grab the previous entry.
// To check if the new data is different enough to justify re-render
// Need to cache now, before we manipulate the deque
CardInfo previous;
if (!cards.empty())
previous = cards.at(0);
// If we're updating an existing entry, remove the old one. Will reinsert at front
for (auto it = cards.begin(); it != cards.end(); ++it) {
if (it->nodeNum == c.nodeNum) {
cards.erase(it);
break;
}
}
cards.push_front(c); // Store this CardInfo
cards.resize(min(maxCards(), (uint8_t)cards.size())); // Don't keep more cards than we could *ever* fit on screen
cards.shrink_to_fit();
// Record the time of this observation
// Used to count active nodes, and to know when to prune inactive nodes
seenNow(c.nodeNum);
// Our rendered image needs to change if:
if (previous.nodeNum != c.nodeNum // Different node
|| previous.signal != c.signal // or different signal strength
|| previous.distanceMeters != c.distanceMeters // or different position
|| previous.hopsAway != c.hopsAway) // or different hops away
{
prune(); // Take the opportunity now to remove inactive nodes
requestAutoshow();
requestUpdate();
}
}
// Record the time (millis, right now) that we hear a node
// If we do not hear from a node for a while, its card and age info will be removed by the purge method, which runs regularly
void InkHUD::RecentsListApplet::seenNow(NodeNum nodeNum)
{
// If we're updating an existing entry, remove the old one. Will reinsert at front
for (auto it = ages.begin(); it != ages.end(); ++it) {
if (it->nodeNum == nodeNum) {
ages.erase(it);
break;
}
}
Age a;
a.nodeNum = nodeNum;
a.seenAtMs = millis();
ages.push_front(a);
}
// Remove Card and Age info for any nodes which are now inactive
// Determined by when a node was last heard, in our internal record (not from nodeDB)
void InkHUD::RecentsListApplet::prune()
{
// Iterate age records from newest to oldest
for (uint16_t i = 0; i < ages.size(); i++) {
// Found the first record which is too old
if (!isActive(ages.at(i).seenAtMs)) {
// Drop this item, and all others behind it
ages.resize(i);
ages.shrink_to_fit();
cards.resize(i);
cards.shrink_to_fit();
// Request an update, if pruning did modify our data
// Required if pruning was scheduled. Redundant if pruning was prior to rendering.
requestAutoshow();
requestUpdate();
break;
}
}
// Push next scheduled pruning back
// Pruning may be called from by handleParsed, immediately prior to rendering
// In that case, we can slightly delay our scheduled pruning
OSThread::setIntervalFromNow(60 * 1000UL);
}
// Is a timestamp old enough that it would make a node inactive, and in need of purging?
bool InkHUD::RecentsListApplet::isActive(uint32_t seenAtMs)
{
uint32_t now = millis();
uint32_t secsAgo = (now - seenAtMs) / 1000UL; // millis() overflow safe
return (secsAgo < settings->recentlyActiveSeconds);
}
// Text to be shown at top of applet
// ChronoListApplet base class allows us to set this dynamically
// Might want to adjust depending on node count, RTC status, etc
std::string InkHUD::RecentsListApplet::getHeaderText()
{
std::string text;
// Print the length of our "Recents" time-window
text += "Last ";
text += to_string(settings->recentlyActiveSeconds / 60);
text += " mins";
// Print the node count
const uint16_t nodeCount = ages.size();
text += ": ";
text += to_string(nodeCount);
text += " ";
text += (nodeCount == 1) ? "node" : "nodes";
return text;
}
#endif
@@ -0,0 +1,52 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
Shows a list of nodes which have been recently active
The length of this "recently active" window is configurable using the onscreen menu
Most of the work is done by the shared InkHUD::NodeListApplet base class
*/
#pragma once
#include "configuration.h"
#include "graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.h"
namespace NicheGraphics::InkHUD
{
class RecentsListApplet : public NodeListApplet, public concurrency::OSThread
{
protected:
// Used internally to count the number of active nodes
// We count for ourselves, instead of using the value provided by NodeDB,
// as the values occasionally differ, due to the timing of our Applet's purge method
struct Age {
uint32_t nodeNum;
uint32_t seenAtMs;
};
public:
RecentsListApplet();
void onActivate() override;
void onDeactivate() override;
protected:
int32_t runOnce() override;
void handleParsed(CardInfo c) override; // Store new info, update active count, update display if needed
std::string getHeaderText() override; // Set title for this applet
void seenNow(NodeNum nodeNum); // Record that we have just seen this node, for active node count
void prune(); // Remove cards for nodes which we haven't seen recently
bool isActive(uint32_t seenAtMillis); // Is a node still active, based on when we last heard it?
std::deque<Age> ages; // Information about when we last heard nodes. Independent of NodeDB
};
} // namespace NicheGraphics::InkHUD
#endif
@@ -0,0 +1,268 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./ThreadedMessageApplet.h"
#include "RTC.h"
#include "mesh/NodeDB.h"
using namespace NicheGraphics;
// Hard limits on how much message data to write to flash
// Avoid filling the storage if something goes wrong
// Normal usage should be well below this size
constexpr uint8_t MAX_MESSAGES_SAVED = 10;
constexpr uint32_t MAX_MESSAGE_SIZE = 250;
InkHUD::ThreadedMessageApplet::ThreadedMessageApplet(uint8_t channelIndex)
: SinglePortModule("ThreadedMessageApplet", meshtastic_PortNum_TEXT_MESSAGE_APP), channelIndex(channelIndex)
{
// Create the message store
// Will shortly attempt to load messages from RAM, if applet is active
// Label (filename in flash) is set from channel index
store = new MessageStore("ch" + to_string(channelIndex));
}
void InkHUD::ThreadedMessageApplet::onRender()
{
// =============
// Draw a header
// =============
// Header text
std::string headerText;
headerText += "Channel ";
headerText += to_string(channelIndex);
headerText += ": ";
if (channels.isDefaultChannel(channelIndex))
headerText += "Public";
else
headerText += channels.getByIndex(channelIndex).settings.name;
// Draw a "standard" applet header
drawHeader(headerText);
// Y position for divider
const int16_t dividerY = Applet::getHeaderHeight() - 1;
// ==================
// Draw each message
// ==================
// Restrict drawing area
// - don't overdraw the header
// - small gap below divider
setCrop(0, dividerY + 2, width(), height() - (dividerY + 2));
// Set padding
// - separates text from the vertical line which marks its edge
constexpr uint16_t padW = 2;
constexpr int16_t msgL = padW;
const int16_t msgR = (width() - 1) - padW;
const uint16_t msgW = (msgR - msgL) + 1;
int16_t msgB = height() - 1; // Vertical cursor for drawing. Messages are bottom-aligned to this value.
uint8_t i = 0; // Index of stored message
// Loop over messages
// - until no messages left, or
// - until no part of message fits on screen
while (msgB >= (0 - fontSmall.lineHeight()) && i < store->messages.size()) {
// Grab data for message
MessageStore::Message &m = store->messages.at(i);
bool outgoing = (m.sender == 0) || (m.sender == myNodeInfo.my_node_num); // Own NodeNum if canned message
std::string bodyText = parse(m.text); // Parse any non-ascii chars in the message
// Cache bottom Y of message text
// - Used when drawing vertical line alongside
const int16_t dotsB = msgB;
// Get dimensions for message text
uint16_t bodyH = getWrappedTextHeight(msgL, msgW, bodyText);
int16_t bodyT = msgB - bodyH;
// Print message
// - if incoming
if (!outgoing)
printWrapped(msgL, bodyT, msgW, bodyText);
// Print message
// - if outgoing
else {
if (getTextWidth(bodyText) < width()) // If short,
printAt(msgR, bodyT, bodyText, RIGHT); // print right align
else // If long,
printWrapped(msgL, bodyT, msgW, bodyText); // need printWrapped(), which doesn't support right align
}
// Move cursor up
// - above message text
msgB -= bodyH;
msgB -= getFont().lineHeight() * 0.2; // Padding between message and header
// Compose info string
// - shortname, if possible, or "me"
// - time received, if possible
std::string info;
if (outgoing)
info += "Me";
else {
// Check if sender is node db
meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(m.sender);
if (sender)
info += parseShortName(sender); // Handle any unprintable chars in short name
else
info += hexifyNodeNum(m.sender); // No node info at all. Print the node num
}
std::string timeString = getTimeString(m.timestamp);
if (timeString.length() > 0) {
info += " - ";
info += timeString;
}
// Print the info string
// - Faux bold: printed twice, shifted horizontally by one px
printAt(outgoing ? msgR : msgL, msgB, info, outgoing ? RIGHT : LEFT, BOTTOM);
printAt(outgoing ? msgR - 1 : msgL + 1, msgB, info, outgoing ? RIGHT : LEFT, BOTTOM);
// Underline the info string
const int16_t divY = msgB;
int16_t divL;
int16_t divR;
if (!outgoing) {
// Left side - incoming
divL = msgL;
divR = getTextWidth(info) + getFont().lineHeight() / 2;
} else {
// Right side - outgoing
divR = msgR;
divL = divR - getTextWidth(info) - getFont().lineHeight() / 2;
}
for (int16_t x = divL; x <= divR; x += 2)
drawPixel(x, divY, BLACK);
// Move cursor up: above info string
msgB -= fontSmall.lineHeight();
// Vertical line alongside message
for (int16_t y = msgB; y < dotsB; y += 1)
drawPixel(outgoing ? width() - 1 : 0, y, BLACK);
// Move cursor up: padding before next message
msgB -= fontSmall.lineHeight() * 0.5;
i++;
} // End of loop: drawing each message
// Fade effect:
// Area immediately below the divider. Overdraw with sparse white lines.
// Make text appear to pass behind the header
hatchRegion(0, dividerY + 1, width(), fontSmall.lineHeight() / 3, 2, WHITE);
// If we've run out of screen to draw messages, we can drop any leftover data from the queue
// Those messages have been pushed off the screen-top by newer ones
while (i < store->messages.size())
store->messages.pop_back();
}
// Code which runs when the applet begins running
// This might happen at boot, or if user enables the applet at run-time, via the menu
void InkHUD::ThreadedMessageApplet::onActivate()
{
loadMessagesFromFlash();
loopbackOk = true; // Allow us to handle messages generated on the node (canned messages)
}
// Code which runs when the applet stop running
// This might be at shutdown, or if the user disables the applet at run-time, via the menu
void InkHUD::ThreadedMessageApplet::onDeactivate()
{
loopbackOk = false; // Slightly reduce our impact if the applet is disabled
}
// Handle new text messages
// These might be incoming, from the mesh, or outgoing from phone
// Each instance of the ThreadMessageApplet will only listen on one specific channel
ProcessMessage InkHUD::ThreadedMessageApplet::handleReceived(const meshtastic_MeshPacket &mp)
{
// Abort if applet fully deactivated
if (!isActive())
return ProcessMessage::CONTINUE;
// Abort if wrong channel
if (mp.channel != this->channelIndex)
return ProcessMessage::CONTINUE;
// Abort if message was a DM
if (mp.to != NODENUM_BROADCAST)
return ProcessMessage::CONTINUE;
// Extract info into our slimmed-down "StoredMessage" type
MessageStore::Message newMessage;
newMessage.timestamp = getValidTime(RTCQuality::RTCQualityDevice, true); // Current RTC time
newMessage.sender = mp.from;
newMessage.channelIndex = mp.channel;
newMessage.text = std::string((const char *)mp.decoded.payload.bytes, mp.decoded.payload.size);
// Store newest message at front
// These records are used when rendering, and also stored in flash at shutdown
store->messages.push_front(newMessage);
// If this was an incoming message, suggest that our applet becomes foreground, if permitted
if (getFrom(&mp) != nodeDB->getNodeNum())
requestAutoshow();
// Redraw the applet, perhaps.
requestUpdate(); // Want to update display, if applet is foreground
// Tell Module API to continue informing other firmware components about this message
// We're not the only component which is interested in new text messages
return ProcessMessage::CONTINUE;
}
// Don't show notifications for text messages broadcast to our channel, when the applet is displayed
bool InkHUD::ThreadedMessageApplet::approveNotification(Notification &n)
{
if (n.type == Notification::Type::NOTIFICATION_MESSAGE_BROADCAST && n.getChannel() == channelIndex)
return false;
// None of our business. Allow the notification.
else
return true;
}
// Save several recent messages to flash
// Stores the contents of ThreadedMessageApplet::messages
// Just enough messages to fill the display
// Messages are packed "back-to-back", to minimize blocks of flash used
void InkHUD::ThreadedMessageApplet::saveMessagesToFlash()
{
// Create a label (will become the filename in flash)
std::string label = "ch" + to_string(channelIndex);
store->saveToFlash();
}
// Load recent messages to flash
// Fills ThreadedMessageApplet::messages with previous messages
// Just enough messages have been stored to cover the display
void InkHUD::ThreadedMessageApplet::loadMessagesFromFlash()
{
// Create a label (will become the filename in flash)
std::string label = "ch" + to_string(channelIndex);
store->loadFromFlash();
}
// Code to run when device is shutting down
// This is in addition to any onDeactivate() code, which will also run
// Todo: implement before a reboot also
void InkHUD::ThreadedMessageApplet::onShutdown()
{
// Save our current set of messages to flash, provided the applet isn't disabled
if (isActive())
saveMessagesToFlash();
}
#endif
@@ -0,0 +1,58 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
Displays a thread-view of incoming and outgoing message for a specific channel
The channel for this applet is set in the constructor,
when the applet is added to WindowManager in the setupNicheGraphics method.
Several messages are saved to flash at shutdown, to preseve applet between reboots.
This class has its own internal method for saving and loading to fs, which interacts directly with the FSCommon layer.
If the amount of flash usage is unacceptable, we could keep these in RAM only.
Multiple instances of this channel may be used. This must be done at buildtime.
Suggest a max of two channel, to minimize fs usage?
*/
#pragma once
#include "configuration.h"
#include "graphics/niche/InkHUD/Applet.h"
#include "graphics/niche/InkHUD/MessageStore.h"
#include "modules/TextMessageModule.h"
namespace NicheGraphics::InkHUD
{
class Applet;
class ThreadedMessageApplet : public Applet, public SinglePortModule
{
public:
explicit ThreadedMessageApplet(uint8_t channelIndex);
ThreadedMessageApplet() = delete;
void onRender() override;
void onActivate() override;
void onDeactivate() override;
void onShutdown() override;
ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
bool approveNotification(Notification &n) override; // Which notifications to suppress
protected:
void saveMessagesToFlash();
void loadMessagesFromFlash();
MessageStore *store; // Messages, held in RAM for use, ready to save to flash on shutdown
uint8_t channelIndex = 0;
};
} // namespace NicheGraphics::InkHUD
#endif
@@ -0,0 +1,176 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./DisplayHealth.h"
#include "DisplayHealth.h"
using namespace NicheGraphics;
// Timing for "maintenance"
// Paying off full-refresh debt with unprovoked updates, if the display is not very active
static constexpr uint32_t MAINTENANCE_MS_INITIAL = 60 * 1000UL;
static constexpr uint32_t MAINTENANCE_MS = 60 * 60 * 1000UL;
InkHUD::DisplayHealth::DisplayHealth() : concurrency::OSThread("Mediator")
{
// Timer disabled by default
OSThread::disable();
}
// Request which update type we would prefer, when the display image next changes
// DisplayHealth class will consider our suggestion, and weigh it against other requests
void InkHUD::DisplayHealth::requestUpdateType(Drivers::EInk::UpdateTypes type)
{
// Update our "working decision", to decide if this request is important enough to change our plan
if (!forced)
workingDecision = prioritize(workingDecision, type);
}
// Demand that a specific update type be used, when the display image next changes
// Note: multiple DisplayHealth::force calls should not be made,
// but if they are, the importance of the type will be weighed the same as if both calls were to DisplayHealth::request
void InkHUD::DisplayHealth::forceUpdateType(Drivers::EInk::UpdateTypes type)
{
if (!forced)
workingDecision = type;
else
workingDecision = prioritize(workingDecision, type);
forced = true;
}
// Find out which update type the DisplayHealth has chosen for us
// Calling this method consumes the result, and resets for the next update
Drivers::EInk::UpdateTypes InkHUD::DisplayHealth::decideUpdateType()
{
LOG_DEBUG("FULL-update debt:%f", debt);
// For convenience
typedef Drivers::EInk::UpdateTypes UpdateTypes;
// Grab our final decision for the update type, so we can reset now, for the next update
// We do this at top of the method, so we can return early
UpdateTypes finalDecision = workingDecision;
workingDecision = UpdateTypes::UNSPECIFIED;
forced = false;
// Check whether we've paid off enough debt to stop unprovoked refreshing (if in progress)
// This maintenance behavior will also have opportunity to halt itself when the timer next fires,
// but that could be an hour away, so we can stop it early here and free up resources
if (OSThread::enabled && debt == 0.0)
endMaintenance();
// Explicitly requested FULL
if (finalDecision == UpdateTypes::FULL) {
LOG_DEBUG("Explicit FULL");
debt = max(debt - 1.0, 0.0); // Record that we have paid back (some of) the FULL refresh debt
return UpdateTypes::FULL;
}
// Explicitly requested FAST
if (finalDecision == UpdateTypes::FAST) {
LOG_DEBUG("Explicit FAST");
// Add to the FULL refresh debt
if (debt < 1.0)
debt += 1.0 / fastPerFull;
else
debt += stressMultiplier * (1.0 / fastPerFull); // More debt if too many consecutive FAST refreshes
// If *significant debt*, begin occasionally refreshing *unprovoked*
// This maintenance behavior is only triggered here, by periods of user interaction
// Debt would otherwise not be able to climb above 1.0
if (debt >= 2.0)
beginMaintenance();
return UpdateTypes::FAST; // Give them what the asked for
}
// Handling UpdateTypes::UNSPECIFIED
// -----------------------------------
// In this case, the UI doesn't care which refresh we use
// Not much debt: suggest FAST
if (debt < 1.0) {
LOG_DEBUG("UNSPECIFIED: using FAST");
debt += 1.0 / fastPerFull;
return UpdateTypes::FAST;
}
// In debt: suggest FULL
else {
LOG_DEBUG("UNSPECIFIED: using FULL");
debt = max(debt - 1.0, 0.0); // Record that we have paid back (some of) the FULL refresh debt
// When maintenance begins, the first refresh happens shortly after user interaction ceases (a minute or so)
// If we *are* given an opportunity to refresh before that, we'll skip that initial maintenance refresh
// We were intending to use that initial refresh to redraw the screen as FULL, but we're doing that now, organically
if (OSThread::enabled && OSThread::interval == MAINTENANCE_MS_INITIAL)
OSThread::setInterval(MAINTENANCE_MS); // Note: not intervalFromNow
return UpdateTypes::FULL;
}
}
// Determine which of two update types is more important to honor
// Explicit FAST is more important than UNSPECIFIED - prioritize responsiveness
// Explicit FULL is more important than explicit FAST - prioritize image quality: explicit FULL is rare
// Used when multiple applets have all requested update simultaneously, each with their own preferred UpdateType
Drivers::EInk::UpdateTypes InkHUD::DisplayHealth::prioritize(Drivers::EInk::UpdateTypes type1, Drivers::EInk::UpdateTypes type2)
{
switch (type1) {
case Drivers::EInk::UpdateTypes::UNSPECIFIED:
return type2;
case Drivers::EInk::UpdateTypes::FAST:
return (type2 == Drivers::EInk::UpdateTypes::FULL) ? Drivers::EInk::UpdateTypes::FULL : Drivers::EInk::UpdateTypes::FAST;
case Drivers::EInk::UpdateTypes::FULL:
return type1;
}
return Drivers::EInk::UpdateTypes::UNSPECIFIED; // Suppress compiler warning only
}
// We're using the timer to perform "maintenance"
// If significant FULL-refresh debt has accumulated, we will occasionally run FULL refreshes unprovoked.
// This prevents gradual build-up of debt,
// in case we aren't doing enough UNSPECIFIED refreshes to pay the debt back organically.
// The first refresh takes place shortly after user finishes interacting with the device; this does the bulk of the restoration
// Subsequent refreshes take place *much* less frequently.
// Hopefully an applet will want to render before this, meaning we can cancel the maintenance.
int32_t InkHUD::DisplayHealth::runOnce()
{
if (debt > 0.0) {
LOG_DEBUG("debt=%f: performing maintenance", debt);
// Ask WindowManager to redraw everything, purely for the refresh
// Todo: optimize? Could update without re-rendering
InkHUD::getInstance()->forceUpdate(Drivers::EInk::UpdateTypes::FULL);
// Record that we have paid back (some of) the FULL refresh debt
debt = max(debt - 1.0, 0.0);
// Next maintenance refresh scheduled - long wait (an hour?)
return MAINTENANCE_MS;
}
else
return endMaintenance();
}
// Begin periodically refreshing the display, to repay FULL-refresh debt
// We do this in case user doesn't have enough activity to repay it organically, with UpdateTypes::UNSPECIFIED
// After an initial refresh, to redraw as FULL, we only perform these maintenance refreshes very infrequently
// This gives the display a chance to heal by evaluating UNSPECIFIED as FULL, which is preferable
void InkHUD::DisplayHealth::beginMaintenance()
{
OSThread::setIntervalFromNow(MAINTENANCE_MS_INITIAL);
OSThread::enabled = true;
}
// FULL-refresh debt is low enough that we no longer need to pay it back with periodic updates
int32_t InkHUD::DisplayHealth::endMaintenance()
{
return OSThread::disable();
}
#endif
@@ -0,0 +1,53 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
Responsible for maintaining display health, by optimizing the ratio of FAST vs FULL refreshes
- counts number of FULL vs FAST refresh
- suggests whether to use FAST or FULL, when not explicitly specified
- periodically requests update unprovoked, if required for display health
*/
#pragma once
#include "configuration.h"
#include "InkHUD.h"
#include "graphics/niche/Drivers/EInk/EInk.h"
namespace NicheGraphics::InkHUD
{
class DisplayHealth : protected concurrency::OSThread
{
public:
DisplayHealth();
void requestUpdateType(Drivers::EInk::UpdateTypes type);
void forceUpdateType(Drivers::EInk::UpdateTypes type);
Drivers::EInk::UpdateTypes decideUpdateType();
uint8_t fastPerFull = 5; // Ideal number of fast refreshes between full refreshes
float stressMultiplier = 2.0; // How bad for the display are extra fast refreshes beyond fastPerFull?
private:
int32_t runOnce() override;
void beginMaintenance(); // Excessive debt: begin unprovoked refreshing of display, for health
int32_t endMaintenance(); // End unprovoked refreshing: debt paid
Drivers::EInk::UpdateTypes
prioritize(Drivers::EInk::UpdateTypes type1,
Drivers::EInk::UpdateTypes type2); // Determine which of two update types is more important to honor
bool forced = false;
Drivers::EInk::UpdateTypes workingDecision = Drivers::EInk::UpdateTypes::UNSPECIFIED;
float debt = 0.0; // How many full refreshes are due
};
} // namespace NicheGraphics::InkHUD
#endif
@@ -0,0 +1,244 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./Events.h"
#include "RTC.h"
#include "buzz.h"
#include "modules/ExternalNotificationModule.h"
#include "modules/TextMessageModule.h"
#include "sleep.h"
#include "./Applet.h"
#include "./SystemApplet.h"
#include "graphics/niche/Utils/FlashData.h"
using namespace NicheGraphics;
InkHUD::Events::Events()
{
// Get convenient references
inkhud = InkHUD::getInstance();
settings = &inkhud->persistence->settings;
}
void InkHUD::Events::begin()
{
// Register our callbacks for the various events
deepSleepObserver.observe(&notifyDeepSleep);
rebootObserver.observe(&notifyReboot);
textMessageObserver.observe(textMessageModule);
#if !MESHTASTIC_EXCLUDE_ADMIN
adminMessageObserver.observe((Observable<AdminModule_ObserverData *> *)adminModule);
#endif
#ifdef ARCH_ESP32
lightSleepObserver.observe(&notifyLightSleep);
#endif
}
void InkHUD::Events::onButtonShort()
{
// Audio feedback (via buzzer)
// Short tone
playChirp();
// Cancel any beeping, buzzing, blinking
// Some button handling suppressed if we are dismissing an external notification (see below)
bool dismissedExt = dismissExternalNotification();
// Check which system applet wants to handle the button press (if any)
SystemApplet *consumer = nullptr;
for (SystemApplet *sa : inkhud->systemApplets) {
if (sa->handleInput) {
consumer = sa;
break;
}
}
// If no system applet is handling input, default behavior instead is to cycle applets
if (consumer)
consumer->onButtonShortPress();
else if (!dismissedExt) // Don't change applet if this button press silenced the external notification module
inkhud->nextApplet();
}
void InkHUD::Events::onButtonLong()
{
// Audio feedback (via buzzer)
// Slightly longer than playChirp
playBoop();
// Check which system applet wants to handle the button press (if any)
SystemApplet *consumer = nullptr;
for (SystemApplet *sa : inkhud->systemApplets) {
if (sa->handleInput) {
consumer = sa;
break;
}
}
// If no system applet is handling input, default behavior instead is to open the menu
if (consumer)
consumer->onButtonLongPress();
else
inkhud->openMenu();
}
// Callback for deepSleepObserver
// Returns 0 to signal that we agree to sleep now
int InkHUD::Events::beforeDeepSleep(void *unused)
{
// If a previous display update is in progress, wait for it to complete.
inkhud->awaitUpdate();
// Notify all applets that we're shutting down
for (Applet *ua : inkhud->userApplets) {
ua->onDeactivate();
ua->onShutdown();
}
for (SystemApplet *sa : inkhud->systemApplets) {
// Note: no onDeactivate. System applets are always active.
sa->onShutdown();
}
// User has successful executed a safe shutdown
// We don't need to nag at boot anymore
settings->tips.safeShutdownSeen = true;
inkhud->persistence->saveSettings();
inkhud->persistence->saveLatestMessage();
// LogoApplet::onShutdown attempted to heal the display by drawing a "shutting down" screen twice,
// then prepared a final powered-off screen for us, which shows device shortname.
// We're updating to show that one now.
inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FULL, false);
delay(1000); // Cooldown, before potentially yanking display power
// InkHUD shutdown complete
// Firmware shutdown continues for several seconds more; flash write still pending
playShutdownMelody();
return 0; // We agree: deep sleep now
}
// Callback for rebootObserver
// Same as shutdown, without drawing the logoApplet
// Makes sure we don't lose message history / InkHUD config
int InkHUD::Events::beforeReboot(void *unused)
{
// Notify all applets that we're "shutting down"
// They don't need to know that it's really a reboot
for (Applet *a : inkhud->userApplets) {
a->onDeactivate();
a->onShutdown();
}
for (SystemApplet *sa : inkhud->systemApplets) {
// Note: no onDeactivate. System applets are always active.
sa->onReboot();
}
// Save settings to flash, or erase if factory reset in progress
if (!eraseOnReboot) {
inkhud->persistence->saveSettings();
inkhud->persistence->saveLatestMessage();
} else {
NicheGraphics::clearFlashData();
}
// Note: no forceUpdate call here
// We don't have any final screen to draw, although LogoApplet::onReboot did already display a "rebooting" screen
return 0; // No special status to report. Ignored anyway by this Observable
}
// Callback when a new text message is received
// Caches the most recently received message, for use by applets
// Rx does not trigger a save to flash, however the data *will* be saved alongside other during shutdown, etc.
// Note: this is different from devicestate.rx_text_message, which may contain an *outgoing* message
int InkHUD::Events::onReceiveTextMessage(const meshtastic_MeshPacket *packet)
{
// Short circuit: don't store outgoing messages
if (getFrom(packet) == nodeDB->getNodeNum())
return 0;
// Determine whether the message is broadcast or a DM
// Store this info to prevent confusion after a reboot
// Avoids need to compare timestamps, because of situation where "future" messages block newly received, if time not set
inkhud->persistence->latestMessage.wasBroadcast = isBroadcast(packet->to);
// Pick the appropriate variable to store the message in
MessageStore::Message *storedMessage = inkhud->persistence->latestMessage.wasBroadcast
? &inkhud->persistence->latestMessage.broadcast
: &inkhud->persistence->latestMessage.dm;
// Store nodenum of the sender
// Applets can use this to fetch user data from nodedb, if they want
storedMessage->sender = packet->from;
// Store the time (epoch seconds) when message received
storedMessage->timestamp = getValidTime(RTCQuality::RTCQualityDevice, true); // Current RTC time
// Store the channel
// - (potentially) used to determine whether notification shows
// - (potentially) used to determine which applet to focus
storedMessage->channelIndex = packet->channel;
// Store the text
// Need to specify manually how many bytes, because source not null-terminated
storedMessage->text =
std::string(&packet->decoded.payload.bytes[0], &packet->decoded.payload.bytes[packet->decoded.payload.size]);
return 0; // Tell caller to continue notifying other observers. (No reason to abort this event)
}
int InkHUD::Events::onAdminMessage(AdminModule_ObserverData *data)
{
switch (data->request->which_payload_variant) {
// Factory reset
// Two possible messages. One preserves BLE bonds, other wipes. Both should clear InkHUD data.
case meshtastic_AdminMessage_factory_reset_device_tag:
case meshtastic_AdminMessage_factory_reset_config_tag:
eraseOnReboot = true;
*data->result = AdminMessageHandleResult::HANDLED;
break;
default:
break;
}
return 0; // Tell caller to continue notifying other observers. (No reason to abort this event)
}
#ifdef ARCH_ESP32
// Callback for lightSleepObserver
// Make sure the display is not partway through an update when we begin light sleep
// This is because some displays require active input from us to terminate the update process, and protect the panel hardware
int InkHUD::Events::beforeLightSleep(void *unused)
{
inkhud->awaitUpdate();
return 0; // No special status to report. Ignored anyway by this Observable
}
#endif
// Silence all ongoing beeping, blinking, buzzing, coming from the external notification module
// Returns true if an external notification was active, and we dismissed it
// Button handling changes depending on our result
bool InkHUD::Events::dismissExternalNotification()
{
// Abort if not using external notifications
if (!moduleConfig.external_notification.enabled)
return false;
// Abort if nothing to dismiss
if (!externalNotificationModule->nagging())
return false;
// Stop the beep buzz blink
externalNotificationModule->stopNow();
// Inform that we did indeed dismiss an external notification
return true;
}
#endif
@@ -0,0 +1,74 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#pragma once
/*
Handles non-specific events for InkHUD
Individual applets are responsible for listening for their own events via the module api etc,
however this class handles general events which concern InkHUD as a whole, e.g. shutdown
*/
#include "configuration.h"
#include "modules/AdminModule.h"
#include "./InkHUD.h"
#include "./Persistence.h"
namespace NicheGraphics::InkHUD
{
class Events
{
public:
Events();
void begin();
void onButtonShort(); // User button: short press
void onButtonLong(); // User button: long press
int beforeDeepSleep(void *unused); // Prepare for shutdown
int beforeReboot(void *unused); // Prepare for reboot
int onReceiveTextMessage(const meshtastic_MeshPacket *packet); // Store most recent text message
int onAdminMessage(AdminModule_ObserverData *data); // Handle incoming admin messages
#ifdef ARCH_ESP32
int beforeLightSleep(void *unused); // Prepare for light sleep
#endif
private:
// For convenience
InkHUD *inkhud = nullptr;
Persistence::Settings *settings = nullptr;
// Get notified when the system is shutting down
CallbackObserver<Events, void *> deepSleepObserver = CallbackObserver<Events, void *>(this, &Events::beforeDeepSleep);
// Get notified when the system is rebooting
CallbackObserver<Events, void *> rebootObserver = CallbackObserver<Events, void *>(this, &Events::beforeReboot);
// Cache *incoming* text messages, for use by applets
CallbackObserver<Events, const meshtastic_MeshPacket *> textMessageObserver =
CallbackObserver<Events, const meshtastic_MeshPacket *>(this, &Events::onReceiveTextMessage);
// Get notified of incoming admin messages, and handle any which are relevant to InkHUD
CallbackObserver<Events, AdminModule_ObserverData *> adminMessageObserver =
CallbackObserver<Events, AdminModule_ObserverData *>(this, &Events::onAdminMessage);
#ifdef ARCH_ESP32
// Get notified when the system is entering light sleep
CallbackObserver<Events, void *> lightSleepObserver = CallbackObserver<Events, void *>(this, &Events::beforeLightSleep);
#endif
// End any externalNotification beeping, buzzing, blinking etc
bool dismissExternalNotification();
// If set, InkHUD's data will be erased during onReboot
bool eraseOnReboot = false;
};
} // namespace NicheGraphics::InkHUD
#endif
@@ -0,0 +1,218 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./InkHUD.h"
#include "./Applet.h"
#include "./Events.h"
#include "./Persistence.h"
#include "./Renderer.h"
#include "./SystemApplet.h"
#include "./Tile.h"
#include "./WindowManager.h"
using namespace NicheGraphics;
// Get or create the singleton
InkHUD::InkHUD *InkHUD::InkHUD::getInstance()
{
// Create the singleton instance of our class, if not yet done
static InkHUD *instance = nullptr;
if (!instance) {
instance = new InkHUD;
instance->persistence = new Persistence;
instance->windowManager = new WindowManager;
instance->renderer = new Renderer;
instance->events = new Events;
}
return instance;
}
// Connect the (fully set-up) E-Ink driver to InkHUD
// Should happen in your variant's nicheGraphics.h file, before InkHUD::begin is called
void InkHUD::InkHUD::setDriver(Drivers::EInk *driver)
{
renderer->setDriver(driver);
}
// Set the target number of FAST display updates in a row, before a FULL update is used for display health
// This value applies only to updates with an UNSPECIFIED update type
// If explicitly requested FAST updates exceed this target, the stressMultiplier parameter determines how many
// subsequent FULL updates will be performed, in an attempt to restore the display's health
void InkHUD::InkHUD::setDisplayResilience(uint8_t fastPerFull, float stressMultiplier)
{
renderer->setDisplayResilience(fastPerFull, stressMultiplier);
}
// Register a user applet with InkHUD
// A variant's nicheGraphics.h file should instantiate your chosen applets, then pass them to this method
// Passing an applet to this method is all that is required to make it available to the user in your InkHUD build
void InkHUD::InkHUD::addApplet(const char *name, Applet *a, bool defaultActive, bool defaultAutoshow, uint8_t onTile)
{
windowManager->addApplet(name, a, defaultActive, defaultAutoshow, onTile);
}
// Start InkHUD!
// Call this only after you have configured InkHUD
void InkHUD::InkHUD::begin()
{
persistence->loadSettings();
persistence->loadLatestMessage();
windowManager->begin();
events->begin();
renderer->begin();
// LogoApplet shows boot screen here
}
// Call this when your user button gets a short press
// Should be connected to an input source in nicheGraphics.h (NicheGraphics::Inputs::TwoButton?)
void InkHUD::InkHUD::shortpress()
{
events->onButtonShort();
}
// Call this when your user button gets a long press
// Should be connected to an input source in nicheGraphics.h (NicheGraphics::Inputs::TwoButton?)
void InkHUD::InkHUD::longpress()
{
events->onButtonLong();
}
// Cycle the next user applet to the foreground
// Only activated applets are cycled
// If user has a multi-applet layout, the applets will cycle on the "focused tile"
void InkHUD::InkHUD::nextApplet()
{
windowManager->nextApplet();
}
// Show the menu (on the the focused tile)
// The applet previously displayed there will be restored once the menu closes
void InkHUD::InkHUD::openMenu()
{
windowManager->openMenu();
}
// In layouts where multiple applets are shown at once, change which tile is focused
// The focused tile in the one which cycles applets on button short press, and displays menu on long press
void InkHUD::InkHUD::nextTile()
{
windowManager->nextTile();
}
// Rotate the display image by 90 degrees
void InkHUD::InkHUD::rotate()
{
windowManager->rotate();
}
// Show / hide the battery indicator in top-right
void InkHUD::InkHUD::toggleBatteryIcon()
{
windowManager->toggleBatteryIcon();
}
// An applet asking for the display to be updated
// This does not occur immediately
// Instead, rendering is scheduled ASAP, for the next Renderer::runOnce call
// This allows multiple applets to observe the same event, and then share the same opportunity to update
// Applets should requestUpdate, whether or not they are currently displayed ("foreground")
// This is because they *might* be automatically brought to foreground by WindowManager::autoshow
void InkHUD::InkHUD::requestUpdate()
{
renderer->requestUpdate();
}
// Demand that the display be updated
// Ignores all diplomacy:
// - the display *will* update
// - the specified update type *will* be used
// If the async parameter is false, code flow is blocked while the update takes place
void InkHUD::InkHUD::forceUpdate(EInk::UpdateTypes type, bool async)
{
renderer->forceUpdate(type, async);
}
// Wait for any in-progress display update to complete before continuing
void InkHUD::InkHUD::awaitUpdate()
{
renderer->awaitUpdate();
}
// Ask the window manager to potentially bring a different user applet to foreground
// An applet will be brought to foreground if it has just received new and relevant info
// For Example: AllMessagesApplet has just received a new text message
// Permission for this autoshow behavior is granted by the user, on an applet-by-applet basis
// If autoshow brings an applet to foreground, an InkHUD notification will not be generated for the same event
void InkHUD::InkHUD::autoshow()
{
windowManager->autoshow();
}
// Tell the window manager that the Persistence::Settings value for applet activation has changed,
// and that it should reconfigure accordingly.
// This is triggered at boot, or when the user enables / disabled applets via the on-screen menu
void InkHUD::InkHUD::updateAppletSelection()
{
windowManager->changeActivatedApplets();
}
// Tell the window manager that the Persistence::Settings value for layout or rotation has changed,
// and that it should reconfigure accordingly.
// This is triggered at boot, or by rotate / layout options in the on-screen menu
void InkHUD::InkHUD::updateLayout()
{
windowManager->changeLayout();
}
// Width of the display, in the context of the current rotation
uint16_t InkHUD::InkHUD::width()
{
return renderer->width();
}
// Height of the display, in the context of the current rotation
uint16_t InkHUD::InkHUD::height()
{
return renderer->height();
}
// A collection of any user tiles which do not have a valid user applet
// This can occur in various situations, such as when a user enables fewer applets than their layout has tiles
// The tiles (and which regions the occupy) are private information of the window manager
// The renderer needs to know which regions (if any) are empty,
// in order to fill them with a "placeholder" pattern.
// -- There may be a tidier way to accomplish this --
std::vector<InkHUD::Tile *> InkHUD::InkHUD::getEmptyTiles()
{
return windowManager->getEmptyTiles();
}
// Get a system applet by its name
// This isn't particularly elegant, but it does avoid:
// - passing around a big set of references
// - having two sets of references (systemApplet vector for iteration)
InkHUD::SystemApplet *InkHUD::InkHUD::getSystemApplet(const char *name)
{
for (SystemApplet *sa : systemApplets) {
if (strcmp(name, sa->name) == 0)
return sa;
}
assert(false); // Invalid name
}
// Place a pixel into the image buffer
// The x and y coordinates are in the context of the current display rotation
// - Applets pass "relative" pixels to tiles
// - Tiles pass translated pixels to this method
// - this methods (Renderer) places rotated pixels into the image buffer
// This method provides the final formatting step required. The image buffer is suitable for writing to display
void InkHUD::InkHUD::drawPixel(int16_t x, int16_t y, Color c)
{
renderer->handlePixel(x, y, c);
}
#endif
@@ -0,0 +1,110 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
InkHUD's main class
- singleton
- mediator between the various components
*/
#pragma once
#include "configuration.h"
#include "graphics/niche/Drivers/EInk/EInk.h"
#include "./AppletFont.h"
#include <vector>
namespace NicheGraphics::InkHUD
{
// Color, understood by display controller IC (as bit values)
// Also suitable for use as AdafruitGFX colors
enum Color : uint8_t {
BLACK = 0,
WHITE = 1,
};
class Applet;
class Events;
class Persistence;
class Renderer;
class SystemApplet;
class Tile;
class WindowManager;
class InkHUD
{
public:
static InkHUD *getInstance(); // Access to this singleton class
// Configuration
// - before InkHUD::begin, in variant nicheGraphics.h,
void setDriver(Drivers::EInk *driver);
void setDisplayResilience(uint8_t fastPerFull = 5, float stressMultiplier = 2.0);
void addApplet(const char *name, Applet *a, bool defaultActive = false, bool defaultAutoshow = false, uint8_t onTile = -1);
void begin();
// Handle user-button press
// - connected to an input source, in variant nicheGraphics.h
void shortpress();
void longpress();
// Trigger UI changes
// - called by various InkHUD components
// - suitable(?) for use by aux button, connected in variant nicheGraphics.h
void nextApplet();
void openMenu();
void nextTile();
void rotate();
void toggleBatteryIcon();
// Updating the display
// - called by various InkHUD components
void requestUpdate();
void forceUpdate(Drivers::EInk::UpdateTypes type = Drivers::EInk::UpdateTypes::UNSPECIFIED, bool async = true);
void awaitUpdate();
// (Re)configuring WindowManager
void autoshow(); // Bring an applet to foreground
void updateAppletSelection(); // Change which applets are active
void updateLayout(); // Change multiplexing (count, rotation)
// Information passed between components
uint16_t width(); // From E-Ink driver
uint16_t height(); // From E-Ink driver
std::vector<Tile *> getEmptyTiles(); // From WindowManager
// Applets
SystemApplet *getSystemApplet(const char *name);
std::vector<Applet *> userApplets;
std::vector<SystemApplet *> systemApplets;
// Pass drawing output to Renderer
void drawPixel(int16_t x, int16_t y, Color c);
// Shared data which persists between boots
Persistence *persistence = nullptr;
private:
InkHUD() {} // Constructor made private to force use of InkHUD::getInstance
Events *events = nullptr; // Handle non-specific firmware events
Renderer *renderer = nullptr; // Co-ordinate display updates
WindowManager *windowManager = nullptr; // Multiplexing of applets
};
} // namespace NicheGraphics::InkHUD
#endif
@@ -0,0 +1,153 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./MessageStore.h"
#include "SafeFile.h"
using namespace NicheGraphics;
// Hard limits on how much message data to write to flash
// Avoid filling the storage if something goes wrong
// Normal usage should be well below this size
constexpr uint8_t MAX_MESSAGES_SAVED = 10;
constexpr uint32_t MAX_MESSAGE_SIZE = 250;
InkHUD::MessageStore::MessageStore(std::string label)
{
filename = "";
filename += "/NicheGraphics";
filename += "/";
filename += label;
filename += ".msgs";
}
// Write the contents of the MessageStore::messages object to flash
// Takes the firmware's SPI lock during FS operations. Implemented for consistency, but only relevant when using SD card.
// Need to lock and unlock around specific FS methods, as the SafeFile class takes the lock for itself internally
void InkHUD::MessageStore::saveToFlash()
{
assert(!filename.empty());
#ifdef FSCom
// Make the directory, if doesn't already exist
// This is the same directory accessed by NicheGraphics::FlashData
spiLock->lock();
FSCom.mkdir("/NicheGraphics");
spiLock->unlock();
// Open or create the file
// No "full atomic": don't save then rename
auto f = SafeFile(filename.c_str(), false);
LOG_INFO("Saving messages in %s", filename.c_str());
// Take firmware's SPI Lock while writing
spiLock->lock();
// 1st byte: how many messages will be written to store
f.write(messages.size());
// For each message
for (uint8_t i = 0; i < messages.size() && i < MAX_MESSAGES_SAVED; i++) {
Message &m = messages.at(i);
f.write((uint8_t *)&m.timestamp, sizeof(m.timestamp)); // Write timestamp. 4 bytes
f.write((uint8_t *)&m.sender, sizeof(m.sender)); // Write sender NodeId. 4 Bytes
f.write((uint8_t *)&m.channelIndex, sizeof(m.channelIndex)); // Write channel index. 1 Byte
f.write((uint8_t *)m.text.c_str(), min(MAX_MESSAGE_SIZE, m.text.size())); // Write message text. Variable length
f.write('\0'); // Append null term
LOG_DEBUG("Wrote message %u, length %u, text \"%s\"", (uint32_t)i, min(MAX_MESSAGE_SIZE, m.text.size()), m.text.c_str());
}
// Release firmware's SPI lock, because SafeFile::close needs it
spiLock->unlock();
bool writeSucceeded = f.close();
if (!writeSucceeded) {
LOG_ERROR("Can't write data!");
}
#else
LOG_ERROR("ERROR: Filesystem not implemented\n");
#endif
}
// Attempt to load the previous contents of the MessageStore:message deque from flash.
// Filename is controlled by the "label" parameter
// Takes the firmware's SPI lock during FS operations. Implemented for consistency, but only relevant when using SD card.
void InkHUD::MessageStore::loadFromFlash()
{
// Hopefully redundant. Initial intention is to only load / save once per boot.
messages.clear();
#ifdef FSCom
// Take the firmware's SPI Lock, in case filesystem is on SD card
concurrency::LockGuard guard(spiLock);
// Check that the file *does* actually exist
if (!FSCom.exists(filename.c_str())) {
LOG_WARN("'%s' not found. Using default values", filename.c_str());
return;
}
// Check that the file *does* actually exist
if (!FSCom.exists(filename.c_str())) {
LOG_INFO("'%s' not found.", filename.c_str());
return;
}
// Open the file
auto f = FSCom.open(filename.c_str(), FILE_O_READ);
if (f.size() == 0) {
LOG_INFO("%s is empty", filename.c_str());
f.close();
return;
}
// If opened, start reading
if (f) {
LOG_INFO("Loading threaded messages '%s'", filename.c_str());
// First byte: how many messages are in the flash store
uint8_t flashMessageCount = 0;
f.readBytes((char *)&flashMessageCount, 1);
LOG_DEBUG("Messages available: %u", (uint32_t)flashMessageCount);
// For each message
for (uint8_t i = 0; i < flashMessageCount && i < MAX_MESSAGES_SAVED; i++) {
Message m;
// Read meta data (fixed width)
f.readBytes((char *)&m.timestamp, sizeof(m.timestamp));
f.readBytes((char *)&m.sender, sizeof(m.sender));
f.readBytes((char *)&m.channelIndex, sizeof(m.channelIndex));
// Read characters until we find a null term
char c;
while (m.text.size() < MAX_MESSAGE_SIZE) {
f.readBytes(&c, 1);
if (c != '\0')
m.text += c;
else
break;
}
// Store in RAM
messages.push_back(m);
LOG_DEBUG("#%u, timestamp=%u, sender(num)=%u, text=\"%s\"", (uint32_t)i, m.timestamp, m.sender, m.text.c_str());
}
f.close();
} else {
LOG_ERROR("Could not open / read %s", filename.c_str());
}
#else
LOG_ERROR("Filesystem not implemented");
state = LoadFileState::NO_FILESYSTEM;
#endif
return;
}
#endif
@@ -0,0 +1,47 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
We hold a few recent messages, for features like the threaded message applet.
This class contains a struct for storing those messages,
and methods for serializing them to flash.
*/
#pragma once
#include "configuration.h"
#include <deque>
#include "mesh/MeshTypes.h"
namespace NicheGraphics::InkHUD
{
class MessageStore
{
public:
// A stored message
struct Message {
uint32_t timestamp; // Epoch seconds
NodeNum sender = 0;
uint8_t channelIndex;
std::string text;
};
MessageStore() = delete;
explicit MessageStore(std::string label); // Label determines filename in flash
void saveToFlash();
void loadFromFlash();
std::deque<Message> messages; // Interact with this object!
private:
std::string filename;
};
} // namespace NicheGraphics::InkHUD
#endif
@@ -0,0 +1,83 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./Persistence.h"
using namespace NicheGraphics;
// Load settings and latestMessage data
void InkHUD::Persistence::loadSettings()
{
// Load the InkHUD settings from flash, and check version number
// We should only consider the version number if the InkHUD flashdata component reports that we *did* actually load flash data
Settings loadedSettings;
bool loadSucceeded = FlashData<Settings>::load(&loadedSettings, "settings");
if (loadSucceeded && loadedSettings.meta.version == SETTINGS_VERSION && loadedSettings.meta.version != 0)
settings = loadedSettings; // Version matched, replace the defaults with the loaded values
else
LOG_WARN("Settings version changed. Using defaults");
}
// Load settings and latestMessage data
void InkHUD::Persistence::loadLatestMessage()
{
// Load previous "latestMessages" data from flash
MessageStore store("latest");
store.loadFromFlash();
// Place into latestMessage struct, for convenient access
// Number of strings loaded determines whether last message was broadcast or dm
if (store.messages.size() == 1) {
latestMessage.dm = store.messages.at(0);
latestMessage.wasBroadcast = false;
} else if (store.messages.size() == 2) {
latestMessage.dm = store.messages.at(0);
latestMessage.broadcast = store.messages.at(1);
latestMessage.wasBroadcast = true;
}
}
// Save the InkHUD settings to flash
void InkHUD::Persistence::saveSettings()
{
FlashData<Settings>::save(&settings, "settings");
}
// Save latestMessage data to flash
void InkHUD::Persistence::saveLatestMessage()
{
// Number of strings saved determines whether last message was broadcast or dm
MessageStore store("latest");
store.messages.push_back(latestMessage.dm);
if (latestMessage.wasBroadcast)
store.messages.push_back(latestMessage.broadcast);
store.saveToFlash();
}
/*
void InkHUD::Persistence::printSettings(Settings *settings)
{
if (SETTINGS_VERSION != 2)
LOG_WARN("Persistence::printSettings was written for SETTINGS_VERSION=2, current is %d", SETTINGS_VERSION);
LOG_DEBUG("meta.version=%d", settings->meta.version);
LOG_DEBUG("userTiles.count=%d", settings->userTiles.count);
LOG_DEBUG("userTiles.maxCount=%d", settings->userTiles.maxCount);
LOG_DEBUG("userTiles.focused=%d", settings->userTiles.focused);
for (uint8_t i = 0; i < MAX_TILES_GLOBAL; i++)
LOG_DEBUG("userTiles.displayedUserApplet[%d]=%d", i, settings->userTiles.displayedUserApplet[i]);
for (uint8_t i = 0; i < MAX_USERAPPLETS_GLOBAL; i++)
LOG_DEBUG("userApplets.active[%d]=%d", i, settings->userApplets.active[i]);
for (uint8_t i = 0; i < MAX_USERAPPLETS_GLOBAL; i++)
LOG_DEBUG("userApplets.autoshow[%d]=%d", i, settings->userApplets.autoshow[i]);
LOG_DEBUG("optionalFeatures.notifications=%d", settings->optionalFeatures.notifications);
LOG_DEBUG("optionalFeatures.batteryIcon=%d", settings->optionalFeatures.batteryIcon);
LOG_DEBUG("optionalMenuItems.nextTile=%d", settings->optionalMenuItems.nextTile);
LOG_DEBUG("optionalMenuItems.backlight=%d", settings->optionalMenuItems.backlight);
LOG_DEBUG("tips.firstBoot=%d", settings->tips.firstBoot);
LOG_DEBUG("tips.safeShutdownSeen=%d", settings->tips.safeShutdownSeen);
LOG_DEBUG("rotation=%d", settings->rotation);
LOG_DEBUG("recentlyActiveSeconds=%d", settings->recentlyActiveSeconds);
}
*/
#endif
@@ -0,0 +1,132 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
A quick and dirty alternative to storing "device only" settings using the protobufs
Convenient during development.
Potentially a polite option, to avoid polluting the generated code with values for obscure use cases like this.
The save / load mechanism is a shared NicheGraphics feature.
*/
#pragma once
#include "configuration.h"
#include "./InkHUD.h"
#include "graphics/niche/InkHUD/MessageStore.h"
#include "graphics/niche/Utils/FlashData.h"
namespace NicheGraphics::InkHUD
{
class Persistence
{
public:
static constexpr uint8_t MAX_TILES_GLOBAL = 4;
static constexpr uint8_t MAX_USERAPPLETS_GLOBAL = 16;
// Used to invalidate old settings, if needed
// Version 0 is reserved for testing, and will always load defaults
static constexpr uint32_t SETTINGS_VERSION = 2;
struct Settings {
struct Meta {
// Used to invalidate old savefiles, if we make breaking changes
uint32_t version = SETTINGS_VERSION;
} meta;
struct UserTiles {
// How many tiles are shown
uint8_t count = 1;
// Maximum amount of tiles for this display
uint8_t maxCount = 4;
// Which tile is focused (responding to user button input)
uint8_t focused = 0;
// Which applet is displayed on which tile
// Index of array: which tile, as indexed in WindowManager::userTiles
// Value of array: which applet, as indexed in InkHUD::userApplets
uint8_t displayedUserApplet[MAX_TILES_GLOBAL] = {0, 1, 2, 3};
} userTiles;
struct UserApplets {
// Which applets are running (either displayed, or in the background)
// Index of array: which applet, as indexed in InkHUD::userApplets
// Initial value is set by the "activeByDefault" parameter of InkHUD::addApplet, in setupNicheGraphics method
bool active[MAX_USERAPPLETS_GLOBAL]{false};
// Which user applets should be automatically shown when they have important data to show
// If none set, foreground applets should remain foreground without manual user input
// If multiple applets request this at once,
// priority is the order which they were passed to InkHUD::addApplets, in setupNicheGraphics method
bool autoshow[MAX_USERAPPLETS_GLOBAL]{false};
} userApplets;
// Features which the user can enable / disable via the on-screen menu
struct OptionalFeatures {
bool notifications = true;
bool batteryIcon = false;
} optionalFeatures;
// Some menu items may not be required, based on device / configuration
// We can enable them only when needed, to de-clutter the menu
struct OptionalMenuItems {
// If aux button is used to swap between tiles, we have no need for this menu item
bool nextTile = true;
// Used if backlight present, and not controlled by AUX button
// If this item is added to menu: backlight is always active when menu is open
// The added menu items then allows the user to "Keep Backlight On", globally.
bool backlight = false;
} optionalMenuItems;
// Allows tips to be run once only
struct Tips {
// Enables the longer "tutorial" shown only on first boot
// Once tutorial has been completed, it is no longer shown
bool firstBoot = true;
// User is advised to shut down before removing device power
// Once user executes a shutdown (either via menu or client app),
// this tip is no longer shown
bool safeShutdownSeen = false;
} tips;
// Rotation of the display
// Multiples of 90 degrees clockwise
// Most commonly: rotation is 0 when flex connector is oriented below display
uint8_t rotation = 0;
// How long do we consider another node to be "active"?
// Used when applets want to filter for "active nodes" only
uint32_t recentlyActiveSeconds = 2 * 60;
};
// Most recently received text message
// Value is updated by InkHUD::WindowManager, as a courtesy to applets
// Note: different from devicestate.rx_text_message,
// which may contain an *outgoing message* to broadcast
struct LatestMessage {
MessageStore::Message broadcast; // Most recent message received broadcast
MessageStore::Message dm; // Most recent received DM
bool wasBroadcast; // True if most recent broadcast is newer than most recent dm
};
void loadSettings();
void saveSettings();
void loadLatestMessage();
void saveLatestMessage();
// void printSettings(Settings *settings); // Debugging use only
Settings settings;
LatestMessage latestMessage;
};
} // namespace NicheGraphics::InkHUD
#endif
@@ -0,0 +1,11 @@
[inkhud]
build_src_filter =
+<graphics/niche/>; Include the nicheGraphics directory
build_flags =
-D MESHTASTIC_INCLUDE_NICHE_GRAPHICS ; Use NicheGraphics
-D MESHTASTIC_INCLUDE_INKHUD ; Use InkHUD (a NicheGraphics UI)
-D MESHTASTIC_EXCLUDE_SCREEN ; Suppress default Screen class
-D MESHTASTIC_EXCLUDE_INPUTBROKER ; Suppress default input handling
-D HAS_BUTTON=0 ; Suppress default ButtonThread
lib_deps =
https://github.com/ZinggJM/GFX_Root#2.0.0 ; Used by InkHUD as a "slimmer" version of AdafruitGFX
@@ -0,0 +1,419 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./Renderer.h"
#include "main.h"
#include "./Applet.h"
#include "./SystemApplet.h"
#include "./Tile.h"
using namespace NicheGraphics;
InkHUD::Renderer::Renderer() : concurrency::OSThread("Renderer")
{
// Nothing for the timer to do just yet
OSThread::disable();
// Convenient references
inkhud = InkHUD::getInstance();
settings = &inkhud->persistence->settings;
}
// Connect the (fully set-up) E-Ink driver to InkHUD
// Should happen in your variant's nicheGraphics.h file, before InkHUD::begin is called
void InkHUD::Renderer::setDriver(Drivers::EInk *driver)
{
// Make sure not already set
if (this->driver) {
LOG_ERROR("Driver already set");
delay(2000); // Wait for native serial..
assert(false);
}
// Store the driver which was created in setupNicheGraphics()
this->driver = driver;
// Determine the dimensions of the image buffer, in bytes.
// Along rows, pixels are stored 8 per byte.
// Not all display widths are divisible by 8. Need to make sure bytecount accommodates padding for these.
imageBufferWidth = ((driver->width - 1) / 8) + 1;
imageBufferHeight = driver->height;
// Allocate the image buffer
imageBuffer = new uint8_t[imageBufferWidth * imageBufferHeight];
}
// Set the target number of FAST display updates in a row, before a FULL update is used for display health
// This value applies only to updates with an UNSPECIFIED update type
// If explicitly requested FAST updates exceed this target, the stressMultiplier parameter determines how many
// subsequent FULL updates will be performed, in an attempt to restore the display's health
void InkHUD::Renderer::setDisplayResilience(uint8_t fastPerFull, float stressMultiplier)
{
displayHealth.fastPerFull = fastPerFull;
displayHealth.stressMultiplier = stressMultiplier;
}
void InkHUD::Renderer::begin()
{
forceUpdate(Drivers::EInk::UpdateTypes::FULL, false);
}
// Set a flag, which will be picked up by runOnce, ASAP.
// Quite likely, multiple applets will all want to respond to one event (Observable, etc)
// Each affected applet can independently call requestUpdate(), and all share the one opportunity to render, at next runOnce
void InkHUD::Renderer::requestUpdate()
{
requested = true;
// We will run the thread as soon as we loop(),
// after all Applets have had a chance to observe whatever event set this off
OSThread::setIntervalFromNow(0);
OSThread::enabled = true;
runASAP = true;
}
// requestUpdate will not actually update if no requests were made by applets which are actually visible
// This can occur, because applets requestUpdate even from the background,
// in case the user's autoshow settings permit them to be moved to foreground.
// Sometimes, however, we will want to trigger a display update manually, in the absence of any sort of applet event
// Display health, for example.
// In these situations, we use forceUpdate
void InkHUD::Renderer::forceUpdate(Drivers::EInk::UpdateTypes type, bool async)
{
requested = true;
forced = true;
displayHealth.forceUpdateType(type);
// Normally, we need to start the timer, in case the display is busy and we briefly defer the update
if (async) {
// We will run the thread as soon as we loop(),
// after all Applets have had a chance to observe whatever event set this off
OSThread::setIntervalFromNow(0);
OSThread::enabled = true;
runASAP = true;
}
// If the update is *not* asynchronous, we begin the render process directly here
// so that it can block code flow while running
else
render(false);
}
// Wait for any in-progress display update to complete before continuing
void InkHUD::Renderer::awaitUpdate()
{
if (driver->busy()) {
LOG_INFO("Waiting for display");
driver->await(); // Wait here for update to complete
}
}
// Set a ready-to-draw pixel into the image buffer
// All rotations / translations have already taken place: this buffer data is formatted ready for the driver
void InkHUD::Renderer::handlePixel(int16_t x, int16_t y, Color c)
{
rotatePixelCoords(&x, &y);
uint32_t byteNum = (y * imageBufferWidth) + (x / 8); // X data is 8 pixels per byte
uint8_t bitNum = 7 - (x % 8); // Invert order: leftmost bit (most significant) is leftmost pixel of byte.
bitWrite(imageBuffer[byteNum], bitNum, c);
}
// Width of the display, relative to rotation
uint16_t InkHUD::Renderer::width()
{
if (settings->rotation % 2)
return driver->height;
else
return driver->width;
}
// Height of the display, relative to rotation
uint16_t InkHUD::Renderer::height()
{
if (settings->rotation % 2)
return driver->width;
else
return driver->height;
}
// Runs at regular intervals
// - postponing render: until next loop(), allowing all applets to be notified of some Mesh event before render
// - queuing another render: while one is already is progress
int32_t InkHUD::Renderer::runOnce()
{
// If an applet asked to render, and hardware is able, lets try now
if (requested && !driver->busy()) {
render();
}
// If our render() call failed, try again shortly
// otherwise, stop our thread until next update due
if (requested)
return 250UL;
else
return OSThread::disable();
}
// Applies the system-wide rotation to pixel positions
// This step is applied to image data which has already been translated by a Tile object
// This is the final step before the pixel is placed into the image buffer
// No return: values of the *x and *y parameters are modified by the method
void InkHUD::Renderer::rotatePixelCoords(int16_t *x, int16_t *y)
{
// Apply a global rotation to pixel locations
int16_t x1 = 0;
int16_t y1 = 0;
switch (settings->rotation) {
case 0:
x1 = *x;
y1 = *y;
break;
case 1:
x1 = (driver->width - 1) - *y;
y1 = *x;
break;
case 2:
x1 = (driver->width - 1) - *x;
y1 = (driver->height - 1) - *y;
break;
case 3:
x1 = *y;
y1 = (driver->height - 1) - *x;
break;
}
*x = x1;
*y = y1;
}
// Make an attempt to gather image data from some / all applets, and update the display
// Might not be possible right now, if update already is progress.
void InkHUD::Renderer::render(bool async)
{
// Make sure the display is ready for a new update
if (async) {
// Previous update still running, Will try again shortly, via runOnce()
if (driver->busy())
return;
} else {
// Wait here for previous update to complete
driver->await();
}
// Determine if a system applet has requested exclusive rights to request an update,
// or exclusive rights to render
checkLocks();
// (Potentially) change applet to display new info,
// then check if this newly displayed applet makes a pending notification redundant
inkhud->autoshow();
// If an update is justified.
// We don't know this until after autoshow has run, as new applets may now be in foreground
if (shouldUpdate()) {
// Decide which technique the display will use to change image
// Done early, as rendering resets the Applets' requested types
Drivers::EInk::UpdateTypes updateType = decideUpdateType();
// Render the new image
clearBuffer();
renderUserApplets();
renderPlaceholders();
renderSystemApplets();
// Invert Buffer if set by user
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) {
for (size_t i = 0; i < imageBufferWidth * imageBufferHeight; ++i) {
imageBuffer[i] = ~imageBuffer[i];
}
}
// Tell display to begin process of drawing new image
LOG_INFO("Updating display");
driver->update(imageBuffer, updateType);
// If not async, wait here until the update is complete
if (!async)
driver->await();
}
// Our part is done now.
// If update is async, the display hardware is still performing the update process,
// but that's all handled by NicheGraphics::Drivers::EInk
// Tidy up, ready for a new request
requested = false;
forced = false;
}
// Manually fill the image buffer with WHITE
// Clears any old drawing
// Note: benchmarking revealed that this is *much* faster than setting pixels individually
// So much so that it's more efficient to re-render all applets,
// rather than rendering selectively, and manually blanking a portion of the display
void InkHUD::Renderer::clearBuffer()
{
memset(imageBuffer, 0xFF, imageBufferHeight * imageBufferWidth);
}
void InkHUD::Renderer::checkLocks()
{
lockRendering = nullptr;
lockRequests = nullptr;
for (SystemApplet *sa : inkhud->systemApplets) {
if (!lockRendering && sa->lockRendering && sa->isForeground()) {
lockRendering = sa;
}
if (!lockRequests && sa->lockRequests && sa->isForeground()) {
lockRequests = sa;
}
}
}
bool InkHUD::Renderer::shouldUpdate()
{
bool should = false;
// via forceUpdate
should |= forced;
// via a system applet (which has locked update requests)
if (lockRequests) {
should |= lockRequests->wantsToRender();
return should; // Early exit - no other requests considered
}
// via system applet (not locked)
for (SystemApplet *sa : inkhud->systemApplets) {
if (sa->wantsToRender() // This applet requested
&& sa->isForeground()) // This applet is currently shown
{
should = true;
break;
}
}
// via user applet
for (Applet *ua : inkhud->userApplets) {
if (ua // Tile has valid applet
&& ua->wantsToRender() // This applet requested display update
&& ua->isForeground()) // This applet is currently shown
{
should = true;
break;
}
}
return should;
}
// Determine which type of E-Ink update the display will perform, to change the image.
// Considers the needs of the various applets, then weighs against display health.
// An update type specified by forceUpdate will be granted with no further questioning.
Drivers::EInk::UpdateTypes InkHUD::Renderer::decideUpdateType()
{
// Ask applets which update type they would prefer
// Some update types take priority over others
// No need to consider the "requests" if somebody already forced an update
if (!forced) {
// User applets
for (Applet *ua : inkhud->userApplets) {
if (ua && ua->isForeground())
displayHealth.requestUpdateType(ua->wantsUpdateType());
}
// System Applets
for (SystemApplet *sa : inkhud->systemApplets) {
if (sa && sa->isForeground())
displayHealth.requestUpdateType(sa->wantsUpdateType());
}
}
return displayHealth.decideUpdateType();
}
// Run the drawing operations of any user applets which are currently displayed
// Pixel output is placed into the framebuffer, ready for handoff to the EInk driver
void InkHUD::Renderer::renderUserApplets()
{
// Don't render user applets if a system applet has demanded the whole display to itself
if (lockRendering)
return;
// Render any user applets which are currently visible
for (Applet *ua : inkhud->userApplets) {
if (ua && ua->isActive() && ua->isForeground()) {
uint32_t start = millis();
ua->render(); // Draw!
uint32_t stop = millis();
LOG_DEBUG("%s took %dms to render", ua->name, stop - start);
}
}
}
// Run the drawing operations of any system applets which are currently displayed
// Pixel output is placed into the framebuffer, ready for handoff to the EInk driver
void InkHUD::Renderer::renderSystemApplets()
{
SystemApplet *battery = inkhud->getSystemApplet("BatteryIcon");
SystemApplet *menu = inkhud->getSystemApplet("Menu");
SystemApplet *notifications = inkhud->getSystemApplet("Notification");
// Each system applet
for (SystemApplet *sa : inkhud->systemApplets) {
// Skip if not shown
if (!sa->isForeground())
continue;
// Skip if locked by another applet
if (lockRendering && lockRendering != sa)
continue;
// Don't draw the battery or notifications overtop the menu
// Todo: smarter way to handle this
if (menu->isForeground() && (sa == battery || sa == notifications))
continue;
assert(sa->getTile());
// uint32_t start = millis();
sa->render(); // Draw!
// uint32_t stop = millis();
// LOG_DEBUG("%s took %dms to render", sa->name, stop - start);
}
}
// In some situations (e.g. layout or applet selection changes),
// a user tile can end up without an assigned applet.
// In this case, we will fill the empty space with diagonal lines.
void InkHUD::Renderer::renderPlaceholders()
{
// Don't fill empty space with placeholders if a system applet wants exclusive use of the display
if (lockRendering)
return;
// Ask the window manager which tiles are empty
std::vector<Tile *> emptyTiles = inkhud->getEmptyTiles();
// No empty tiles
if (emptyTiles.size() == 0)
return;
SystemApplet *placeholder = inkhud->getSystemApplet("Placeholder");
// uint32_t start = millis();
for (Tile *t : emptyTiles) {
t->assignApplet(placeholder);
placeholder->render();
t->assignApplet(nullptr);
}
// uint32_t stop = millis();
// LOG_DEBUG("Placeholders took %dms to render", stop - start);
}
#endif
@@ -0,0 +1,96 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
Orchestrates updating of the display image
- takes requests (or demands) for display update
- performs the various steps of the rendering operation
- interfaces with the E-Ink driver
*/
#pragma once
#include "configuration.h"
#include "./DisplayHealth.h"
#include "./InkHUD.h"
#include "./Persistence.h"
#include "graphics/niche/Drivers/EInk/EInk.h"
namespace NicheGraphics::InkHUD
{
class Renderer : protected concurrency::OSThread
{
public:
Renderer();
// Configuration, before begin
void setDriver(Drivers::EInk *driver);
void setDisplayResilience(uint8_t fastPerFull, float stressMultiplier);
void begin();
// Call these to make the image change
void requestUpdate(); // Update display, if a foreground applet has info it wants to show
void forceUpdate(Drivers::EInk::UpdateTypes type = Drivers::EInk::UpdateTypes::UNSPECIFIED,
bool async = true); // Update display, regardless of whether any applets requested this
// Wait for an update to complete
void awaitUpdate();
// Receives pixel output from an applet (via a tile, which translates the coordinates)
void handlePixel(int16_t x, int16_t y, Color c);
// Size of display, in context of current rotation
uint16_t width();
uint16_t height();
private:
// Make attemps to render / update, once triggered by requestUpdate or forceUpdate
int32_t runOnce() override;
// Apply the display rotation to handled pixels
void rotatePixelCoords(int16_t *x, int16_t *y);
// Execute the render process now, then hand off to driver for display update
void render(bool async = true);
// Steps of the rendering process
void clearBuffer();
void checkLocks();
bool shouldUpdate();
Drivers::EInk::UpdateTypes decideUpdateType();
void renderUserApplets();
void renderSystemApplets();
void renderPlaceholders();
Drivers::EInk *driver = nullptr; // Interacts with your variants display hardware
DisplayHealth displayHealth; // Manages display health by controlling type of update
uint8_t *imageBuffer = nullptr; // Fed into driver
uint16_t imageBufferHeight = 0;
uint16_t imageBufferWidth = 0;
uint32_t imageBufferSize = 0; // Bytes
SystemApplet *lockRendering = nullptr; // Render this applet *only*
SystemApplet *lockRequests = nullptr; // Honor update requests from this applet *only*
bool requested = false;
bool forced = false;
// For convenience
InkHUD *inkhud = nullptr;
Persistence::Settings *settings = nullptr;
};
} // namespace NicheGraphics::InkHUD
#endif
@@ -0,0 +1,43 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
An applet with nonstandard behavior, which will require special handling
For features like the menu, and the battery icon.
*/
#pragma once
#include "configuration.h"
#include "./Applet.h"
namespace NicheGraphics::InkHUD
{
class SystemApplet : public Applet
{
public:
// System applets have the right to:
bool handleInput = false; // - respond to input from the user button
bool lockRendering = false; // - prevent other applets from being rendered during an update
bool lockRequests = false; // - prevent other applets from triggering display updates
virtual void onReboot() { onShutdown(); } // - handle reboot specially
// Other system applets may take precedence over our own system applet though
// The order an applet is passed to WindowManager::addSystemApplet determines this hierarchy (added earlier = higher rank)
private:
// System applets are always running (active), but may not be visible (foreground)
void onActivate() override {}
void onDeactivate() override {}
};
}; // namespace NicheGraphics::InkHUD
#endif

Some files were not shown because too many files have changed in this diff Show More