diff --git a/README.md b/README.md index c983170..d8d081d 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ The data source handles account sign-in and usage retrieval. Bridge sends only u | Waveshare ePaper 3.97 | 800×480 e-paper with four grayscale levels | Temperature and humidity, usage trends (microSD card required) | | ESP-Mosaico | 480×480 AMOLED | Touchscreen, AI button, auto-rotation, camera gestures (experimental) | | Waveshare RLCD 4.2 | 400×300 monochrome reflective LCD | KEY / BOOT navigation, clock, usage trends (microSD card required) | +| MindReset Read Pico | 684×1216 e-paper with 16 grayscale levels | Touchscreen and three keys, clock, auto-rotation, usage trends (microSD card required) | All devices support Codex / Claude usage display, retain the last usage data when disconnected, and support firmware updates over Bluetooth. See the [device guide](docs/devices.md) for a full feature comparison and control shortcuts. @@ -43,7 +44,7 @@ All devices support Codex / Claude usage display, retain the last usage data whe ### 1. Flash the Firmware -Open the [web flasher](https://quotaframe.com/) in Chrome or Edge on your computer, select your device model, connect it via USB, and follow the instructions. No ESP-IDF installation is required. +Open the [web flasher](https://quotaframe.com/) in Chrome or Edge on your computer, select your device model, connect it via USB, and follow the instructions. No ESP-IDF installation is required. MindReset Read Pico is not in the web flasher; write its release image as described in the firmware guide. For manual flashing or serial recovery, see the [firmware guide](firmware/README.md#烧录和串口日志). @@ -78,7 +79,7 @@ See the [Bridge guide](bridge/README.md) for installation and connection trouble ## Acknowledgments -Thanks to Waveshare for supporting this project through sponsorship. +Thanks to [Waveshare](https://www.waveshare.com/) and [MindReset](https://dot.mindreset.tech/) for their support with development devices. Usage data is provided by [CodexBar](https://github.com/steipete/CodexBar) and [Win-CodexBar](https://github.com/nesszer/Win-CodexBar). Thanks to [esp-desktop-buddy](https://github.com/espressif/esp-desktop-buddy), [LVGL](https://github.com/lvgl/lvgl), [Bleak](https://github.com/hbldh/bleak), [pystray](https://github.com/moses-palmer/pystray), [Pillow](https://github.com/python-pillow/Pillow), and [PyObjC](https://github.com/ronaldoussoren/pyobjc) for supporting the firmware and desktop application. diff --git a/README.zh-CN.md b/README.zh-CN.md index 93cbf2e..5b6b028 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -32,6 +32,7 @@ flowchart LR | Waveshare ePaper 3.97 | 800×480 四灰阶墨水屏 | 温湿度、用量趋势(需 microSD 卡) | | ESP-Mosaico | 480×480 AMOLED | 触屏、AI 键、自动旋转、摄像头手势(实验性) | | Waveshare RLCD 4.2 | 400×300 黑白反射式 LCD | KEY / BOOT 翻页、时钟、用量趋势(需 microSD 卡) | +| MindReset Read Pico | 684×1216 十六灰阶墨水屏 | 触屏与三枚按键、时钟、自动旋转、用量趋势(需 microSD 卡) | 所有设备均支持 Codex / Claude 用量显示、断连保留最后用量与蓝牙固件更新。完整功能对照及快捷操作见[设备说明](docs/devices.md)。 @@ -43,7 +44,7 @@ flowchart LR ### 1. 烧录固件 -使用桌面版 Chrome 或 Edge 打开 [Web 烧录器](https://quotaframe.com/),选择手中的设备型号、连接 USB,按页面提示烧录,无需安装 ESP-IDF。 +使用桌面版 Chrome 或 Edge 打开 [Web 烧录器](https://quotaframe.com/),选择手中的设备型号、连接 USB,按页面提示烧录,无需安装 ESP-IDF。MindReset Read Pico 不在 Web 烧录器中,请按固件说明写入 Release 镜像。 需要手工烧录或串口恢复时,参阅[固件说明](firmware/README.md#烧录和串口日志)。 @@ -78,7 +79,7 @@ flowchart LR ## 致谢 -感谢微雪电子对本项目的赞助支持。 +感谢[微雪电子](https://www.waveshare.com/)与 [MindReset](https://dot.mindreset.tech/) 在开发设备上给予的支持。 用量数据依赖 [CodexBar](https://github.com/steipete/CodexBar) 与 [Win-CodexBar](https://github.com/nesszer/Win-CodexBar)。感谢 [esp-desktop-buddy](https://github.com/espressif/esp-desktop-buddy)、[LVGL](https://github.com/lvgl/lvgl)、[Bleak](https://github.com/hbldh/bleak)、[pystray](https://github.com/moses-palmer/pystray)、[Pillow](https://github.com/python-pillow/Pillow) 与 [PyObjC](https://github.com/ronaldoussoren/pyobjc) 提供固件及桌面端支持。 diff --git a/THIRD_PARTY_LICENSES.md b/THIRD_PARTY_LICENSES.md index 0d7ff72..f7f4867 100644 --- a/THIRD_PARTY_LICENSES.md +++ b/THIRD_PARTY_LICENSES.md @@ -242,6 +242,7 @@ For ESP Component Registry entries, `Registry license` is the API value for the - SPDX: `OFL-1.1` - Generated subsets: weight 700; printable ASCII at 24–56 px, digits and `%:-. ` at 104–184 px - Copyright/notice: `firmware/targets/waveshare_epaper_397/main/fonts/Archivo-OFL.txt` +- Read Pico subsets: weight 700; printable ASCII at 16–56 px, digits and `%:-. ` at 58–236 px; notice: `firmware/targets/mindreset_read_pico/main/fonts/Archivo-OFL.txt` ## JetBrains Mono generated font subsets @@ -249,6 +250,15 @@ For ESP Component Registry entries, `Registry license` is the API value for the - SPDX: `OFL-1.1` - Generated subsets: weight 500; printable ASCII at 15–20 px - Copyright/notice: `firmware/targets/waveshare_epaper_397/main/fonts/JetBrainsMono-OFL.txt` +- Read Pico subsets: printable ASCII at weight 500, 18 px, and weight 700, 16–30 px; notice: `firmware/targets/mindreset_read_pico/main/fonts/JetBrainsMono-OFL.txt` + +## Read Pico board components + +- Version: `28cde682a4468a581c278761922724f57d976418` +- Source: https://github.com/MindReset/read_pico_firmware/tree/28cde682a4468a581c278761922724f57d976418/components +- SPDX: `Apache-2.0` (`e0470_epaper_waveform`, `read_pico`, `read_pico_pmu`, `cst836u`, `sc7a20h`, `fca9555`, `sy7636a`, `pwm_audio`); `LGPL-3.0-or-later` (`epdiy`, a trimmed fork of https://github.com/vroland/epdiy v2.0.0) +- Copyright/notice: `LICENSE` in each directory under `firmware/targets/mindreset_read_pico/components/` +- Local integration: [UPSTREAM.md](firmware/targets/mindreset_read_pico/components/UPSTREAM.md) ## Waveshare ESP32-S3-ePaper-3.97 screen driver diff --git a/bridge/README.md b/bridge/README.md index d899a15..0155fd8 100644 --- a/bridge/README.md +++ b/bridge/README.md @@ -35,7 +35,7 @@ macOS 提供控制台与菜单栏形态,Release workflow 生成带图标和原 滚动仅处理图标范围内的纵向事件,触控板惯性滚动不触发翻页,连续操作最多每 200 ms 翻一页。 -菜单按已添加的设备显示固件更新项,包含新版提示、等待设备确认、传输百分比和重连状态。点击后下载并校验固件,在桌面确认设备、来源、版本和摘要,再通过蓝牙更新;设备仍需逐次物理确认。下载与桌面确认不占独占连接,期间继续同步用量。macOS OTA 软件流程已接入,实机升级、重连和数据恢复仍待验收。 +菜单按已添加的设备显示固件更新项,包含新版提示、等待设备确认、传输百分比和重连状态。点击后下载并校验固件,在桌面确认设备、来源、版本和摘要,再通过蓝牙更新;设备仍需逐次物理确认。下载与桌面确认不占独占连接,期间继续同步用量。 **「重新配对…」只弹说明**:macOS 不提供解绑 API,只能引导去蓝牙设置。 diff --git a/bridge/src/quotaframe_bridge/targets.py b/bridge/src/quotaframe_bridge/targets.py index 822e091..e210f7a 100644 --- a/bridge/src/quotaframe_bridge/targets.py +++ b/bridge/src/quotaframe_bridge/targets.py @@ -129,6 +129,17 @@ def ota_transfer_bytes(self) -> int: asset="assets/devices/waveshare_rlcd_42.jpg", ), ), + TargetDefinition( + id="mindreset_read_pico", + label="MindReset Read Pico", + firmware_project="firmware/targets/mindreset_read_pico", + idf_version="6.1", + ota_image="mr_read_pico.bin", + full_image="mr_read_pico_full.bin", + release_stem="mindreset-read-pico", + image_chip_id=9, + ota_partition_bytes=4 * 1024 * 1024, + ), ) IDF_IMAGES: Mapping[str, str] = MappingProxyType( diff --git a/bridge/src/quotaframe_bridge/ui/macos/app.py b/bridge/src/quotaframe_bridge/ui/macos/app.py index 475a2b4..73343e8 100644 --- a/bridge/src/quotaframe_bridge/ui/macos/app.py +++ b/bridge/src/quotaframe_bridge/ui/macos/app.py @@ -94,6 +94,7 @@ def __init__( self._service: MultiDeviceBridgeService | None = None self._graph: TrayServiceGraph[DarwinPairer] | None = None self._adoption_running = False + self._adoption_future: concurrent.futures.Future[None] | None = None self._stopping = False self._shutdown_lock = threading.Lock() self._firmware_updates = 0 @@ -194,15 +195,24 @@ def _notify_firmware(self, title: str, body: str) -> None: def _add_device(self) -> None: loop, graph = self._loop, self._graph - if loop is None or graph is None: + if loop is None or graph is None or self._stopping: + return + if self._adoption_future is not None and not self._adoption_future.done(): return future = asyncio.run_coroutine_threadsafe(self._run_adoption(graph), loop) + # Retain the task chain while CoreBluetooth is awaiting a native callback. + self._adoption_future = future def report_failure(completed: concurrent.futures.Future[object]) -> None: try: completed.result() + except concurrent.futures.CancelledError: + pass except Exception: LOGGER.exception("manual device adoption failed") + finally: + if self._adoption_future is completed: + self._adoption_future = None future.add_done_callback(report_failure) diff --git a/bridge/tests/test_macos_app.py b/bridge/tests/test_macos_app.py index 27cd29e..3e8d640 100644 --- a/bridge/tests/test_macos_app.py +++ b/bridge/tests/test_macos_app.py @@ -1,8 +1,10 @@ from __future__ import annotations import asyncio +import gc import os import unittest +import weakref from types import SimpleNamespace from unittest.mock import AsyncMock, Mock, patch @@ -74,6 +76,43 @@ async def check(self, *, manual: bool = False) -> None: class MenuBarUpdateWiringTests(unittest.IsolatedAsyncioTestCase): + async def test_adoption_survives_gc_and_releases_completed_future(self): + application, _, _ = self.make_application() + application._loop = asyncio.get_running_loop() + application._graph = object() + waiting = [] + + async def scan(*args, **kwargs): + response = asyncio.get_running_loop().create_future() + waiting.append(weakref.ref(response)) + await response + + with patch.object(app, "run_adoption", new=scan): + application._add_device() + await asyncio.sleep(0) + await asyncio.sleep(0) + gc.collect() + self.assertIsNotNone(waiting[0]()) + application._add_device() + await asyncio.sleep(0) + await asyncio.sleep(0) + self.assertEqual(len(waiting), 1) + waiting[0]().set_result(()) + await asyncio.sleep(0) + await asyncio.sleep(0) + self.assertFalse(application._adoption_running) + self.assertIsNone(application._adoption_future) + application._add_device() + await asyncio.sleep(0) + await asyncio.sleep(0) + with patch.object(app.LOGGER, "exception") as report: + application._adoption_future.cancel() + await asyncio.sleep(0) + await asyncio.sleep(0) + report.assert_not_called() + self.assertFalse(application._adoption_running) + self.assertIsNone(application._adoption_future) + def make_application( self, *, diff --git a/bridge/tests/test_target_registry.py b/bridge/tests/test_target_registry.py index 1a1e3e0..6a3e4fa 100644 --- a/bridge/tests/test_target_registry.py +++ b/bridge/tests/test_target_registry.py @@ -59,6 +59,14 @@ def test_preserves_existing_maintained_target_contracts(self) -> None: "ws_rlcd_42_full.bin", "waveshare-esp32-s3-rlcd-42", ), + "mindreset_read_pico": ( + "MindReset Read Pico", + "firmware/targets/mindreset_read_pico", + "6.1", + "mr_read_pico.bin", + "mr_read_pico_full.bin", + "mindreset-read-pico", + ), } self.assertEqual(set(expected), set(TARGETS_BY_ID)) self.assertEqual( @@ -69,6 +77,7 @@ def test_preserves_existing_maintained_target_contracts(self) -> None: "waveshare_epaper_397": 9, "esp_mosaico": 32, "waveshare_rlcd_42": 9, + "mindreset_read_pico": 9, }, ) for target_id, contract in expected.items(): diff --git a/docs/devices.md b/docs/devices.md index 3b2f597..2184bc2 100644 --- a/docs/devices.md +++ b/docs/devices.md @@ -30,8 +30,11 @@ ePaper 3.97 与 ESP-Mosaico 的详细说明见 [ePaper 3.97 固件说明](../fir RLCD 4.2 使用 400×300 黑白反射式 LCD,提供总览、Codex / Claude 详情、趋势、时钟和设置页;竖屏仅在总览和时钟间轮换。它支持 RTC、温湿度显示、电压估算电量,以及需 FAT microSD 卡的 48 点趋势。屏幕方向由设置选择,可通过 Web 烧录器安装。当前实机验证范围和完整操作见 [RLCD 4.2 说明](../firmware/targets/waveshare_rlcd_42/README.md)。 +Read Pico 使用 684×1216 十六灰阶墨水屏,主页同屏显示 Codex 与 Claude 用量、告警状态和 24 小时趋势,另有时钟、设置、配对和 OTA 页面。它支持 PMU RTC、电量与充电状态、四向自动旋转、拿起时从自动时钟返回主页,以及需 FAT microSD 卡的 48 点趋势。首次安装写入 Release 中的 `mindreset-read-pico-full-` 镜像,方法见[固件说明](../firmware/README.md#烧录和串口日志);完整操作见 [Read Pico 说明](../firmware/targets/mindreset_read_pico/README.md)。 + ## 屏幕快捷操作 +- **Read Pico**:KEY1 / KEY3 在主页与时钟间切换,KEY2 整屏刷新,长按 KEY2 约 0.8 秒进入或退出设置;设置中点按选项即保存,点 DONE 退出。OTA 待确认时点 CONFIRM / DENY,或按 KEY2 确认、长按拒绝。 - **RLCD 4.2**:KEY 上一页,BOOT 下一页,长按 KEY 进入或退出设置;设置中 KEY 选择项目、BOOT 修改选项。KEY 可关闭当前用量告警。OTA 待确认时 KEY 确认,长按 KEY 拒绝。 - **480×480 触屏设置(微雪 AMOLED 2.16 与 ESP-Mosaico)**:长按触屏约 0.8 秒进入,两块板均支持亮度与自动时钟设置。亮度即时预览;AUTO CLOCK 可选择 OFF、1、5、10、30 分钟。SAVE 保存并在重启后保留,CANCEL 撤销未保存调整。OFF 仅关闭自动进入时钟,托盘/菜单栏左键仍可主动切换;微雪另有 PWR 侧键,ESP-Mosaico 另有 AI 键。 - **ESP-Mosaico 实验性手势**:安装左槽 CameraBoard 后,在设置中打开 CAMERA 并保存。用量页向任意方向挥手或左右招手切到下一页;OK 或点赞稳定保持约半秒进入时钟,挥手或招手唤醒。点击顶栏摄像头标识切换预览。完整说明见[手势指南](validation/mosaico-gestures.md)。 diff --git a/docs/release/FIRMWARE_RELEASE.md b/docs/release/FIRMWARE_RELEASE.md index 676a1d6..d601d4f 100644 --- a/docs/release/FIRMWARE_RELEASE.md +++ b/docs/release/FIRMWARE_RELEASE.md @@ -75,6 +75,8 @@ espressif-esp-mosaico-ota-v.bin espressif-esp-mosaico-full-v.bin waveshare-esp32-s3-rlcd-42-ota-v.bin waveshare-esp32-s3-rlcd-42-full-v.bin +mindreset-read-pico-ota-v.bin +mindreset-read-pico-full-v.bin manifest.json SHA256SUMS.txt LICENSE diff --git a/firmware/README.md b/firmware/README.md index 32fee33..8b8a452 100644 --- a/firmware/README.md +++ b/firmware/README.md @@ -38,6 +38,7 @@ Folder Push 本地快照原因见 [UPSTREAM.md](components/esp_desktop_buddy_fol | ESP32-S3-ePaper-3.97 | 已维护 | 自绘帧缓冲、SSD1677 驱动、三向拨轮 | | ESP-Mosaico | 已维护 | 乐鑫 BSP、LVGL、触控标签和手势、AI 键 | | ESP32-S3-RLCD-4.2 | 已维护 | 自绘帧缓冲、ST7305 驱动、KEY / BOOT 按键 | +| MindReset Read Pico | 已维护 | 自绘帧缓冲、epdiy 驱动、触屏与三枚电容键 | 各目标的实机验收范围见 [verification.md](../docs/verification.md)。 @@ -65,6 +66,7 @@ eim run "idf.py -C firmware/targets/ merge-bin -o " v6.1 | `waveshare_epaper_397` | `ws_epaper_397.bin` | `ws_epaper_397_full.bin` | | `esp_mosaico` | `mosaico_usage_panel.bin` | `mosaico_usage_panel_full.bin` | | `waveshare_rlcd_42` | `ws_rlcd_42.bin` | `ws_rlcd_42_full.bin` | +| `mindreset_read_pico` | `mr_read_pico.bin` | `mr_read_pico_full.bin` | 产物位于各目标的 `build/`。ESP-Mosaico 首次配置需启用 preview target,见[目标说明](targets/esp_mosaico/README.md);墨水屏刷新、输入和诊断见[ePaper 说明](targets/waveshare_epaper_397/README.md)。 diff --git a/firmware/targets/esp_mosaico/README.md b/firmware/targets/esp_mosaico/README.md index d124234..7349a43 100644 --- a/firmware/targets/esp_mosaico/README.md +++ b/firmware/targets/esp_mosaico/README.md @@ -44,7 +44,7 @@ OTA 元数据未被改写。新固件启动后,再次完成应用 CDC → ROM 看门狗复位 → 应用 CDC,全程无需按键。 两次启动均检测到 SC101IOT 并持续采集;分别读取 CDC 日志 30 秒、20 秒, -未见崩溃或串口断连。这是短时并行验证,长期稳定性和 Windows 行为仍待验证。 +未见崩溃或串口断连。 应用 CDC 与 ROM 使用不同的 USB 标识,端口会变化;此项验证不代表单条 `idf.py flash` 命令已自动处理端口切换。 diff --git a/firmware/targets/mindreset_read_pico/CMakeLists.txt b/firmware/targets/mindreset_read_pico/CMakeLists.txt new file mode 100644 index 0000000..3ece119 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.16) + +set(EXTRA_COMPONENT_DIRS + "${CMAKE_CURRENT_LIST_DIR}/../../components/usage_json" + "${CMAKE_CURRENT_LIST_DIR}/../../components/usage_core" + "${CMAKE_CURRENT_LIST_DIR}/../../components/usage_protocol" + "${CMAKE_CURRENT_LIST_DIR}/../../components/usage_ble" + "${CMAKE_CURRENT_LIST_DIR}/../../components/usage_ota" + "${CMAKE_CURRENT_LIST_DIR}/../../components/esp_desktop_buddy_folder_push") + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(mr_read_pico) diff --git a/firmware/targets/mindreset_read_pico/README.md b/firmware/targets/mindreset_read_pico/README.md new file mode 100644 index 0000000..9b59e63 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/README.md @@ -0,0 +1,114 @@ +# Read Pico + +Target: `mindreset_read_pico`; application: `mr_read_pico`. +ESP-IDF: v6.1. Flash: 16 MB; PSRAM: 8 MB. Panel: E0470A01, 684 x 1216, 16 gray levels. + +```text +eim run "idf.py -C firmware/targets/mindreset_read_pico build" v6.1 +eim run "idf.py -C firmware/targets/mindreset_read_pico -p flash" v6.1 +``` + +The device advertises as `QF-MR-PICO-` followed by the final two Bluetooth MAC +bytes, and its authenticated status identifies the model as `mindreset_read_pico`. +Usage publication, time synchronization and OTA reuse the shared BLE protocol. +Release assets use the stem `mindreset-read-pico`; write the `-full-` image at +offset 0 for a first installation. + +Board support comes from the vendor firmware; see +[components/UPSTREAM.md](components/UPSTREAM.md). The 120 MHz flash and PSRAM +timing in `sdkconfig.defaults` is required by the panel render path and depends +on the flash fitted to this board. + +## Display + +The views are Home with both services, the +24-hour trend and alarm states; the idle clock; a landscape Home; pairing; OTA +progress and confirmation; Display settings; and the offline state. The view +rotates in quarter turns. Automatic rotation applies after a 1.5-second stable +accelerometer reading; flat or diagonal readings keep the current view. + +Each service leads with its short window and shows the week window beside it; the +week window leads when no short window is reported. The alarm level follows the +fuller of the two windows: from 80% the bar is hatched, and from 95% the service +area takes a gray wash with a NEAR LIMIT or EXHAUSTED label. + +Eight of the sixteen gray levels are used. The panel renders light grays close to +paper, so bar tracks, rules and washes use darker levels than a screen design +suggests; the test card under [USB diagnostics](#usb-diagnostics) compares +candidates. Refresh modes: + +| Mode | Use | +| --- | --- | +| GC16, full panel | First frame, screen or rotation change, link online/offline change, alarm level change, a shrinking usage bar, KEY2, and after 13 consecutive DU or GL16 updates | +| DU, changed pixels | Any update whose changed pixels all turn to ink or paper: values, countdowns, sample ages, the clock, growing bars, Display settings and OTA progress | +| GL16, full panel | Updates that change a gray area, such as text on an alarm wash or a new stored trend sample | + +The trend retains its gray 80–100% band and grid. Its curves and horizontal axis +stay fixed between stored samples, so normal usage and clock updates do not move +the gray chart. Periodic GC16 refreshes still clear ghosting. + +Refreshes run on the application task and block it for the duration of the +waveform. An unchanged frame is not refreshed. The high-voltage rails power down +8 seconds after the last refresh. Usage is redrawn at the configured interval +(15/30/60/120 seconds, default 30); input, link, alarm and minute changes redraw +immediately. + +## Controls + +The three capacitive keys below the display share the touch sensor. + +- KEY1 / KEY3: switch between Home and Clock. +- KEY2: full GC16 refresh. Hold 800 ms: open or close Display settings. +- Display settings: tap a value to select and save it to NVS; tap DONE to exit. +- Clock: a tap returns to Home. A clock opened by the idle timeout also returns + when the device is picked up. +- OTA confirmation: tap CONFIRM or DENY, or press KEY2 to confirm and hold it to + deny. Other navigation is blocked during OTA and pairing. + +A local redraw does not trigger a provider fetch on the Bridge. Power-key events +are acknowledged and ignored; power-off uses the PMU's forced long press. + +## Clock, power and trend + +The PMU RTC stores UTC seconds. A Bridge time sync sets it and stores the UTC +offset in NVS; the time shows `--:--` until both are available. Battery level +and charge state come from the PMU and feed the OTA power gate. Invalid PMU status +readings do not authorize an update. + +Trend history uses a mounted FAT microSD card; absence is nonfatal, and a card +inserted while the device is running is mounted when it is detected. Samples are +stored every 30 minutes as up to 48 timestamped records in `trend.bin`, with +`trend.bak` as the replacement backup. Gaps longer than one hour are not connected. +The upper service blocks use the latest Bridge publication; the trend uses only +stored short-window samples and updates when a new half-hour record is saved. +Its `AS OF HH:MM` label gives the snapshot time in the device's local time zone, +and each legend value shows its age. The axis ends at that snapshot, not at NOW. +Without a valid local clock, the snapshot label shows its age instead. +Offline history keeps its original timestamps. Records older than 24 hours are +excluded using the current clock, with Bridge time as a fallback. Missing short +windows are not replaced with week values. Without an SD card or usable history, +the chart shows NO TREND DATA; current usage remains available above it. + +## Orientation calibration + +`kUprightXSign` and `kLandscapeYSign` in `main/board.cpp` map the accelerometer's +device frame to the four views. The `i` serial command logs the current sample +in mg together with the resolved orientation. + +## USB diagnostics + +Commands are accepted while OTA is idle. `1`-`9` show synthetic views: Home, alarms, clock, landscape Home, pairing, OTA progress, +settings, offline and OTA confirmation. `t` shows the gray-level test card: four +candidates each for small text, bar tracks, rules, large digits and alarm washes, +refreshed with GC16. `r` returns to live data and `i` prints board state. The previews do not alter the live usage model or start an OTA. + +`tools/read_pico/preview/run.sh` renders the same nine views in both rotations +and the test card on the host with the firmware drawing code and checks the touch targets. + +## Fonts + +Glyph masks are generated by `tools/ws397/generate_fonts.py --target read_pico` +from the Google Fonts variable files described in the +[ePaper 3.97 notes](../waveshare_epaper_397/README.md#fonts-and-source): Archivo +weight 700 and JetBrains Mono weights 500 and 700, thresholded to one bit. Sizes above +56 px carry digits and `%:-. ` only. Font licenses are in `main/fonts/`. diff --git a/firmware/targets/mindreset_read_pico/components/UPSTREAM.md b/firmware/targets/mindreset_read_pico/components/UPSTREAM.md new file mode 100644 index 0000000..e9c45f8 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/UPSTREAM.md @@ -0,0 +1,29 @@ +# Board component provenance + +Source: https://github.com/MindReset/read_pico_firmware +Commit: `28cde682a4468a581c278761922724f57d976418` +Path: `components/{epdiy,e0470_epaper_waveform,read_pico,read_pico_pmu,cst836u,sc7a20h,fca9555,sy7636a,pwm_audio}` + +| Component | Role | License | +| --- | --- | --- | +| `epdiy` | Trimmed fork of epdiy v2.0.0 for the ESP32-S3 LCD peripheral path | LGPL-3.0-or-later | +| `e0470_epaper_waveform` | E0470A01 waveform tables and boot-time trimmer | Apache-2.0 | +| `read_pico` | Board support: I2C, panel rails and scan timing, microSD, flash high-performance mode | Apache-2.0 | +| `read_pico_pmu` | CW32L010 PMU host: battery, RTC, power state | Apache-2.0 | +| `cst836u`, `sc7a20h`, `fca9555`, `sy7636a` | Touch, accelerometer, IO expander and panel PMIC drivers | Apache-2.0 | +| `pwm_audio` | Buzzer backend required by `read_pico` | Apache-2.0 | + +Each directory keeps its upstream `LICENSE`. Local changes are limited to: + +- Removal of `read_pico_pmu/docs/` (PMU datasheets and protocol references, 3.6 MB); + `read_pico_pmu/README.md` still refers to that directory. +- Trailing whitespace cleanup in epdiy `src/board/epd_board.c`, `src/diff.S`, + `src/output_common/lut.S` and `src/render.c` for the repository whitespace check. + Driver behavior and waveform data are unchanged. + +`epdiy` is statically linked under LGPL-3.0-or-later. This repository contains the +complete corresponding source and build configuration needed to relink the firmware +with a modified copy. + +To update, replace each directory with the same path at the new upstream commit, +remove `read_pico_pmu/docs/` again and record the commit here. diff --git a/firmware/targets/mindreset_read_pico/components/cst836u/CMakeLists.txt b/firmware/targets/mindreset_read_pico/components/cst836u/CMakeLists.txt new file mode 100644 index 0000000..6525fe3 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/cst836u/CMakeLists.txt @@ -0,0 +1,6 @@ +idf_component_register( + SRCS "cst836u.c" + INCLUDE_DIRS "include" + REQUIRES esp_driver_gpio esp_driver_i2c + PRIV_REQUIRES freertos +) diff --git a/firmware/targets/mindreset_read_pico/components/cst836u/LICENSE b/firmware/targets/mindreset_read_pico/components/cst836u/LICENSE new file mode 100644 index 0000000..500992f --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/cst836u/LICENSE @@ -0,0 +1,17 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +Copyright 2026 mindreset + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/firmware/targets/mindreset_read_pico/components/cst836u/README.md b/firmware/targets/mindreset_read_pico/components/cst836u/README.md new file mode 100644 index 0000000..6d32f7c --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/cst836u/README.md @@ -0,0 +1,108 @@ +# cst836u + +ESP-IDF 下的 **CST836U** 自电容触摸 C 驱动(海栎创)。 + +ESP-IDF C driver for the **CST836U** self-cap touch controller (Hynitron). + +面向手持与墨水屏设备:两点报点、轮询 INT、深度睡眠。手册中的待机手势唤醒依赖对应固件,本驱动不实现。 + +Intended for handheld / e-paper devices: 2-point report, IRQ poll, and deep sleep. Datasheet standby (gesture wake) needs matching firmware and is not in this driver. + +组件名 / Registry name: `mindreset/cst836u` + +## 能力 / Features + +- Handle:调用方传入已有 `i2c_master` 总线、7 位地址、可选 INT / RST 脚。/ Handle-based API: pass an existing `i2c_master` bus, 7-bit address, optional INT / RST GPIO. +- 上电读取 `0xA6`:固件 / 编号 / 模组 / 项目 / 型号。/ Read FW / ID / module / project / type at `0xA6`. +- 从 `0x00` 解析两点(按下 / 抬起 / 移动)。/ Two-point parse from register `0x00` (down / up / move). +- 本驱动实现的手册工作模式:/ Work modes from the datasheet that this firmware actually implements: + - 动态报点 `0xFE00`,典型 **< 5 mA**。/ Dynamic report `0xFE00`, typical **< 5 mA**. + - 深度睡眠 `0xA503`,典型 **10 µA**;不再应答 I2C,RST 后重新写入 `0xFE00`。/ Deep sleep `0xA503`, typical **10 µA**; I2C stops, wake with RST then `0xFE00`. +- RST 可以是本机 GPIO,也可以是 `reset_fn`(位于 IO 扩展上)。/ RST via a native GPIO or a `reset_fn` (IO expander). +- IRQ 开漏、低有效。/ IRQ is open-drain, active low. + +## 硬件 / Hardware + +| 项目 / Item | 规格 / Spec | +|---|---| +| 芯片 / Part | CST836U(海栎创)/ CST836U (Hynitron) | +| 总线 / Bus | I2C,400 kHz | +| 地址 / Address | `0x15`(写 `0x2A`,读 `0x2B`)/ `0x15` (write `0x2A`, read `0x2B`) | +| INT | 任意 GPIO;开漏低有效,10 kΩ 上拉 / Any GPIO; open-drain, active low, 10 kΩ pull-up | +| RST | 任意 GPIO;在扩展口上则用 `reset_fn` / Any GPIO, or `reset_fn` if the line sits on an expander | +| 供电 / Supply | 2.8 V ~ 3.6 V | + +手册还定义了待机模式(约 1.5 mA,50 Hz 扫描 + 手势 IRQ)。许多模组的出厂固件并未实现。 + +Datasheet standby (~1.5 mA, 50 Hz scan + gesture IRQ) is a third mode. Stock firmware on many modules does not implement it. + +## 安装 / Install + +```yaml +dependencies: + mindreset/cst836u: "^1.0.0" +``` + +或:/ Or: + +```bash +idf.py add-dependency "mindreset/cst836u^1.0.0" +``` + +## 用法 / Quick start + +```c +#include "cst836u.h" + +cst836u_handle_t tp = NULL; +cst836u_hw_t hw = CST836U_HW_DEFAULT(); +hw.bus = i2c_bus; +hw.int_gpio = GPIO_NUM_43; +hw.rst_gpio = GPIO_NUM_NC; // 或本机 RST 脚。/ Or a native RST pin. +hw.reset_fn = board_tp_reset; // rst_gpio 为 NC 时用。/ Optional, used when rst_gpio is NC. + +if (cst836u_create(&hw, &tp) == ESP_OK) { + cst836u_touch_t t; + cst836u_read(tp, &t); +} + +// 主机休眠:约 10 µA,直到 RST。/ Host suspend: ~10 µA until RST. +cst836u_set_mode(tp, CST836U_MODE_DEEPSLEEP); +// ... +cst836u_wake(tp); +``` + +扩展口复位: + +IO-expander reset: + +```c +static esp_err_t board_tp_reset(void) { + expander_set_rst(0); + vTaskDelay(pdMS_TO_TICKS(10)); + return expander_set_rst(1); +} +``` + +## 接口分组 / API groups + +| 分组 / Group | 函数 / Functions | +|---|---| +| 生命周期 / Lifecycle | `cst836u_create`, `cst836u_del` | +| 读数 / Read | `cst836u_read`, `cst836u_get_info` | +| 电源 / Power | `cst836u_set_mode`, `cst836u_get_mode`, `cst836u_reset`, `cst836u_wake` | +| 中断 / IRQ | `cst836u_int_asserted`, `cst836u_int_level`, `cst836u_int_gpio` | + +详见 [`include/cst836u.h`](include/cst836u.h)。 + +See [`include/cst836u.h`](include/cst836u.h). + +## 示例 / Example + +[`examples/touch_read`](examples/touch_read) 初始化 I2C 并输出触点坐标;接有 RST 时会演示睡眠与唤醒。引脚在 `idf.py menuconfig` 中配置。 + +[`examples/touch_read`](examples/touch_read) brings up I2C, logs points, and (if RST is wired) sleeps then wakes. Pins are in `idf.py menuconfig`. + +## 许可 / License + +Apache-2.0 diff --git a/firmware/targets/mindreset_read_pico/components/cst836u/cst836u.c b/firmware/targets/mindreset_read_pico/components/cst836u/cst836u.c new file mode 100644 index 0000000..e2565ac --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/cst836u/cst836u.c @@ -0,0 +1,303 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * CST836U 主机:I2C 读点、动态上报 / 深睡、RST 唤醒。 + * + * CST836U host: I2C point read, dynamic report / deep sleep, RST wake. + */ + +#include "cst836u.h" + +#include +#include + +#include "driver/gpio.h" +#include "esp_log.h" +#include "esp_rom_sys.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#define CST836U_REG_TOUCH_DATA 0x00 +#define CST836U_REG_INFO 0xA6 + +#define CST836U_CMD_NORMAL 0xFE00 +#define CST836U_CMD_DEEPSLEEP 0xA503 + +#define I2C_TIMEOUT_MS 100 +#define RST_HOLD_MS 10 +#define RST_BOOT_MS 50 + +static const char* TAG = "cst836u"; + +struct cst836u_dev_t { + i2c_master_bus_handle_t bus; + i2c_master_dev_handle_t dev; + gpio_num_t int_gpio; + gpio_num_t rst_gpio; + esp_err_t (*reset_fn)(void); + cst836u_info_t info; + cst836u_mode_t mode; + bool info_ready; +}; + +static bool gpio_ok(gpio_num_t gpio) { + return gpio != GPIO_NUM_NC && GPIO_IS_VALID_GPIO(gpio); +} + +static esp_err_t read_regs(cst836u_handle_t h, uint8_t reg, uint8_t* data, size_t length) { + esp_err_t err = i2c_master_transmit(h->dev, ®, 1, I2C_TIMEOUT_MS); + if (err != ESP_OK) return err; + esp_rom_delay_us(5); + return i2c_master_receive(h->dev, data, length, I2C_TIMEOUT_MS); +} + +static esp_err_t write_cmd(cst836u_handle_t h, uint16_t cmd) { + uint8_t buf[2] = { (uint8_t)(cmd >> 8), (uint8_t)(cmd & 0xFF) }; + return i2c_master_transmit(h->dev, buf, sizeof(buf), I2C_TIMEOUT_MS); +} + +static esp_err_t int_as_input(cst836u_handle_t h) { + if (!gpio_ok(h->int_gpio)) return ESP_ERR_INVALID_ARG; + gpio_config_t io = { + .pin_bit_mask = 1ULL << h->int_gpio, + .mode = GPIO_MODE_INPUT, + .pull_up_en = GPIO_PULLUP_ENABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + return gpio_config(&io); +} + +static esp_err_t rst_as_output(cst836u_handle_t h) { + if (!gpio_ok(h->rst_gpio)) return ESP_ERR_INVALID_ARG; + gpio_config_t io = { + .pin_bit_mask = 1ULL << h->rst_gpio, + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + esp_err_t err = gpio_config(&io); + if (err != ESP_OK) return err; + return gpio_set_level(h->rst_gpio, 1); +} + +static esp_err_t load_info(cst836u_handle_t h) { + uint8_t info[6] = { 0 }; + esp_err_t err = read_regs(h, CST836U_REG_INFO, info, sizeof(info)); + if (err != ESP_OK) return err; + h->info.fw = info[0]; + h->info.id = info[1]; + h->info.module = info[2]; + h->info.project = info[3]; + h->info.type = (uint16_t)info[5] << 8 | info[4]; + h->info_ready = true; + return ESP_OK; +} + +esp_err_t cst836u_init(i2c_master_bus_handle_t bus_handle, const cst836u_config_t* config, + cst836u_handle_t* handle) { + if (bus_handle == NULL || config == NULL || handle == NULL) return ESP_ERR_INVALID_ARG; + + cst836u_handle_t h = calloc(1, sizeof(*h)); + if (h == NULL) return ESP_ERR_NO_MEM; + + h->bus = bus_handle; + h->int_gpio = config->int_gpio; + h->rst_gpio = config->rst_gpio; + h->reset_fn = config->reset_fn; + h->mode = CST836U_MODE_NORMAL; + + uint8_t addr = config->i2c_addr ? config->i2c_addr : CST836U_ADDR_DEFAULT; + uint32_t hz = config->scl_speed_hz ? config->scl_speed_hz : CST836U_I2C_HZ_DEFAULT; + i2c_device_config_t dev_cfg = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = addr, + .scl_speed_hz = hz, + }; + esp_err_t err = i2c_master_bus_add_device(bus_handle, &dev_cfg, &h->dev); + if (err != ESP_OK) { + ESP_LOGE(TAG, "I2C device add failed: %s", esp_err_to_name(err)); + free(h); + return err; + } + + if (gpio_ok(h->rst_gpio)) { + err = rst_as_output(h); + if (err != ESP_OK) { + ESP_LOGW(TAG, "RST GPIO%d failed: %s", (int)h->rst_gpio, esp_err_to_name(err)); + } + } + + err = load_info(h); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Chip information read failed: %s", esp_err_to_name(err)); + i2c_master_bus_rm_device(h->dev); + free(h); + return err; + } + + if (gpio_ok(h->int_gpio)) { + err = int_as_input(h); + if (err != ESP_OK) { + ESP_LOGW(TAG, "INT GPIO%d input failed: %s", (int)h->int_gpio, esp_err_to_name(err)); + } + } + + ESP_LOGI( + TAG, + "Ready: FW=0x%02X, ID=0x%02X, module=0x%02X, project=0x%02X, type=0x%04X", + h->info.fw, h->info.id, h->info.module, h->info.project, h->info.type + ); + *handle = h; + return ESP_OK; +} + +esp_err_t cst836u_deinit(cst836u_handle_t h) { + if (h == NULL) return ESP_OK; + if (h->dev) { + i2c_master_bus_rm_device(h->dev); + } + free(h); + return ESP_OK; +} + +esp_err_t cst836u_get_info(cst836u_handle_t h, cst836u_info_t* info) { + if (h == NULL || info == NULL) return ESP_ERR_INVALID_ARG; + if (!h->info_ready) return ESP_ERR_INVALID_STATE; + *info = h->info; + return ESP_OK; +} + +esp_err_t cst836u_read(cst836u_handle_t h, cst836u_touch_t* touch) { + if (h == NULL || touch == NULL) return ESP_ERR_INVALID_ARG; + if (h->dev == NULL) return ESP_ERR_INVALID_STATE; + // 深睡后不再应答 I2C。这里直接回空点,主循环才能继续走 on_tick 做超时复位。 + // / Deep sleep ignores I2C. Return empty points so the loop can still + // run on_tick and time out a reset. + if (h->mode == CST836U_MODE_DEEPSLEEP) { + memset(touch, 0, sizeof(*touch)); + return ESP_OK; + } + + uint8_t data[CST836U_RAW_LEN] = { 0 }; + esp_err_t err = ESP_FAIL; + for (int retry = 0; retry < 3; retry++) { + err = read_regs(h, CST836U_REG_TOUCH_DATA, data, sizeof(data)); + if (err != ESP_OK) return err; + if (data[2] < 3) { + err = ESP_OK; + break; + } + err = ESP_ERR_INVALID_RESPONSE; + } + if (err != ESP_OK) return err; + + memset(touch, 0, sizeof(*touch)); + memcpy(touch->raw, data, sizeof(data)); + touch->count = data[2] & 0x0F; + + uint8_t active_n = 0; + bool have_primary = false; + for (int i = 0; i < CST836U_MAX_POINTS; i++) { + const uint8_t* src = &data[3 + i * 6]; + cst836u_point_t* pt = &touch->points[i]; + uint8_t event = src[0] >> 6; + uint8_t id = src[2] >> 4; + pt->event = event; + pt->id = id; + pt->x = (uint16_t)(src[0] & 0x0F) << 8 | src[1]; + pt->y = (uint16_t)(src[2] & 0x0F) << 8 | src[3]; + if (id > 1 || event == 3) { + pt->active = false; + continue; + } + pt->active = event != 1; + if (!have_primary) { + touch->x = pt->x; + touch->y = pt->y; + have_primary = true; + } + if (pt->active) active_n++; + } + touch->touched = active_n != 0; + return ESP_OK; +} + +esp_err_t cst836u_set_mode(cst836u_handle_t h, cst836u_mode_t mode) { + if (h == NULL || h->dev == NULL) return ESP_ERR_INVALID_STATE; + uint16_t cmd; + switch (mode) { + case CST836U_MODE_NORMAL: cmd = CST836U_CMD_NORMAL; break; + case CST836U_MODE_DEEPSLEEP: cmd = CST836U_CMD_DEEPSLEEP; break; + default: return ESP_ERR_INVALID_ARG; + } + + esp_err_t err = write_cmd(h, cmd); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Mode %d command failed: %s", mode, esp_err_to_name(err)); + return err; + } + h->mode = mode; + ESP_LOGI(TAG, "Work mode %s (command 0x%04X)", cst836u_mode_name(mode), cmd); + return ESP_OK; +} + +cst836u_mode_t cst836u_get_mode(cst836u_handle_t h) { + return h == NULL ? CST836U_MODE_NORMAL : h->mode; +} + +esp_err_t cst836u_reset(cst836u_handle_t h) { + if (h == NULL) return ESP_ERR_INVALID_ARG; + if (gpio_ok(h->rst_gpio)) { + gpio_set_level(h->rst_gpio, 0); + vTaskDelay(pdMS_TO_TICKS(RST_HOLD_MS)); + gpio_set_level(h->rst_gpio, 1); + return ESP_OK; + } + if (h->reset_fn != NULL) return h->reset_fn(); + return ESP_ERR_NOT_SUPPORTED; +} + +esp_err_t cst836u_wake(cst836u_handle_t h) { + if (h == NULL || h->dev == NULL) return ESP_ERR_INVALID_STATE; + esp_err_t err = cst836u_reset(h); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Touch reset failed: %s", esp_err_to_name(err)); + return err; + } + vTaskDelay(pdMS_TO_TICKS(RST_BOOT_MS)); + h->mode = CST836U_MODE_NORMAL; + return cst836u_set_mode(h, CST836U_MODE_NORMAL); +} + +bool cst836u_int_asserted(cst836u_handle_t h) { + return h != NULL && gpio_ok(h->int_gpio) && gpio_get_level(h->int_gpio) == 0; +} + +int cst836u_int_level(cst836u_handle_t h) { + if (h == NULL || !gpio_ok(h->int_gpio)) return 1; + return gpio_get_level(h->int_gpio); +} + +gpio_num_t cst836u_int_gpio(cst836u_handle_t h) { + return h == NULL ? GPIO_NUM_NC : h->int_gpio; +} + +const char* cst836u_mode_name(cst836u_mode_t mode) { + switch (mode) { + case CST836U_MODE_NORMAL: return "normal"; + case CST836U_MODE_DEEPSLEEP: return "deepsleep"; + default: return "unknown"; + } +} + +const char* cst836u_event_name(uint8_t event) { + switch (event) { + case 0: return "down"; + case 1: return "up"; + case 2: return "move"; + default: return "---"; + } +} diff --git a/firmware/targets/mindreset_read_pico/components/cst836u/examples/touch_read/CMakeLists.txt b/firmware/targets/mindreset_read_pico/components/cst836u/examples/touch_read/CMakeLists.txt new file mode 100644 index 0000000..c1f782e --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/cst836u/examples/touch_read/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 3.16) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(touch_read) diff --git a/firmware/targets/mindreset_read_pico/components/cst836u/examples/touch_read/main/CMakeLists.txt b/firmware/targets/mindreset_read_pico/components/cst836u/examples/touch_read/main/CMakeLists.txt new file mode 100644 index 0000000..bdf9a31 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/cst836u/examples/touch_read/main/CMakeLists.txt @@ -0,0 +1 @@ +idf_component_register(SRCS "main.c" REQUIRES cst836u driver) diff --git a/firmware/targets/mindreset_read_pico/components/cst836u/examples/touch_read/main/Kconfig.projbuild b/firmware/targets/mindreset_read_pico/components/cst836u/examples/touch_read/main/Kconfig.projbuild new file mode 100644 index 0000000..637c367 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/cst836u/examples/touch_read/main/Kconfig.projbuild @@ -0,0 +1,25 @@ +menu "CST836U touch_read" + + config CST836U_EXAMPLE_I2C_PORT + int "I2C port" + default 0 + range 0 1 + + config CST836U_EXAMPLE_SDA + int "SDA GPIO" + default 39 + + config CST836U_EXAMPLE_SCL + int "SCL GPIO" + default 40 + + config CST836U_EXAMPLE_INT + int "INT GPIO (active low)" + default 43 + + config CST836U_EXAMPLE_RST + int "RST GPIO (-1 = skip sleep/wake demo)" + default -1 + range -1 48 + +endmenu diff --git a/firmware/targets/mindreset_read_pico/components/cst836u/examples/touch_read/main/idf_component.yml b/firmware/targets/mindreset_read_pico/components/cst836u/examples/touch_read/main/idf_component.yml new file mode 100644 index 0000000..d77b811 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/cst836u/examples/touch_read/main/idf_component.yml @@ -0,0 +1,4 @@ +dependencies: + mindreset/cst836u: + version: "^1.0.0" + override_path: "../../.." diff --git a/firmware/targets/mindreset_read_pico/components/cst836u/examples/touch_read/main/main.c b/firmware/targets/mindreset_read_pico/components/cst836u/examples/touch_read/main/main.c new file mode 100644 index 0000000..56ecd5d --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/cst836u/examples/touch_read/main/main.c @@ -0,0 +1,73 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * 轮询 CST836U 触点。接了 RST 则演示深睡再唤醒。 + * 引脚:idf.py menuconfig → CST836U touch_read + * + * Poll CST836U points. If RST is wired, demo deep sleep then wake. + * Pins: idf.py menuconfig → CST836U touch_read + */ + +#include "cst836u.h" + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "sdkconfig.h" + +static const char* TAG = "touch_read"; + +void app_main(void) { + i2c_master_bus_handle_t bus; + i2c_master_bus_config_t bus_cfg = { + .i2c_port = (i2c_port_t)CONFIG_CST836U_EXAMPLE_I2C_PORT, + .sda_io_num = (gpio_num_t)CONFIG_CST836U_EXAMPLE_SDA, + .scl_io_num = (gpio_num_t)CONFIG_CST836U_EXAMPLE_SCL, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .flags.enable_internal_pullup = true, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&bus_cfg, &bus)); + + cst836u_handle_t tp = NULL; + cst836u_config_t config = CST836U_CONFIG_DEFAULT(); + config.int_gpio = (gpio_num_t)CONFIG_CST836U_EXAMPLE_INT; + if (CONFIG_CST836U_EXAMPLE_RST >= 0) { + config.rst_gpio = (gpio_num_t)CONFIG_CST836U_EXAMPLE_RST; + } + if (cst836u_init(bus, &config, &tp) != ESP_OK) { + ESP_LOGE(TAG, "touch init failed"); + return; + } + + cst836u_info_t info; + if (cst836u_get_info(tp, &info) == ESP_OK) { + ESP_LOGI(TAG, "FW=0x%02X ID=0x%02X type=0x%04X", info.fw, info.id, info.type); + } + + for (int i = 0; i < 80; i++) { + cst836u_touch_t t; + if (cst836u_read(tp, &t) == ESP_OK && t.touched) { + ESP_LOGI( + TAG, "irq=%d n=%u P1 %s %u,%u P2 %s %u,%u", + cst836u_int_asserted(tp) ? 1 : 0, t.count, + cst836u_event_name(t.points[0].event), t.points[0].x, t.points[0].y, + cst836u_event_name(t.points[1].event), t.points[1].x, t.points[1].y + ); + } + vTaskDelay(pdMS_TO_TICKS(20)); + } + + if (CONFIG_CST836U_EXAMPLE_RST >= 0) { + ESP_LOGI(TAG, "deep sleep 2s then RST wake"); + ESP_ERROR_CHECK(cst836u_set_mode(tp, CST836U_MODE_DEEPSLEEP)); + vTaskDelay(pdMS_TO_TICKS(2000)); + ESP_ERROR_CHECK(cst836u_wake(tp)); + ESP_LOGI(TAG, "awake, mode=%s", cst836u_mode_name(cst836u_get_mode(tp))); + } + cst836u_deinit(tp); + ESP_ERROR_CHECK(i2c_del_master_bus(bus)); +} diff --git a/firmware/targets/mindreset_read_pico/components/cst836u/examples/touch_read/sdkconfig.defaults b/firmware/targets/mindreset_read_pico/components/cst836u/examples/touch_read/sdkconfig.defaults new file mode 100644 index 0000000..b01fdbe --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/cst836u/examples/touch_read/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_IDF_TARGET="esp32s3" diff --git a/firmware/targets/mindreset_read_pico/components/cst836u/idf_component.yml b/firmware/targets/mindreset_read_pico/components/cst836u/idf_component.yml new file mode 100644 index 0000000..4776c97 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/cst836u/idf_component.yml @@ -0,0 +1,13 @@ +version: "1.0.0" +description: "CST836U self-cap touch (Hynitron) — 2-point report and deep sleep" +url: "https://github.com/mindreset/cst836u" +repository: "https://github.com/mindreset/cst836u.git" +issues: "https://github.com/mindreset/cst836u/issues" +license: "Apache-2.0" +tags: + - touch + - cst836u + - i2c + - hynitron +dependencies: + idf: ">=5.3" diff --git a/firmware/targets/mindreset_read_pico/components/cst836u/include/cst836u.h b/firmware/targets/mindreset_read_pico/components/cst836u/include/cst836u.h new file mode 100644 index 0000000..dc1fbc8 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/cst836u/include/cst836u.h @@ -0,0 +1,113 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * CST836U 自容触摸(Hynitron)。 + * 产品 API:创建、两点读取、动态上报 / 深睡。 + * 手册待机(手势唤醒)依赖固件,本驱动未实现。 + * + * CST836U self-cap touch (Hynitron). + * Product API: create, 2-point read, dynamic report / deep sleep. + * Datasheet standby (gesture wake) depends on firmware and is not implemented. + */ + +#pragma once + +#include +#include + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct cst836u_dev_t* cst836u_handle_t; + +#define CST836U_ADDR_DEFAULT 0x15 +#define CST836U_MAX_POINTS 2 +#define CST836U_RAW_LEN 15 +#define CST836U_I2C_HZ_DEFAULT 400000 + +/// 动态上报(约 5 mA)或深睡(约 10 µA,RST 唤醒)。 +/// / Dynamic report (~5 mA) or deep sleep (~10 µA, RST to wake). +typedef enum { + CST836U_MODE_NORMAL = 0, + CST836U_MODE_DEEPSLEEP, +} cst836u_mode_t; + +typedef struct { + bool active; + uint8_t id; + uint8_t event; + uint16_t x; + uint16_t y; +} cst836u_point_t; + +typedef struct { + bool touched; + uint8_t count; + uint16_t x; + uint16_t y; + cst836u_point_t points[CST836U_MAX_POINTS]; + uint8_t raw[CST836U_RAW_LEN]; +} cst836u_touch_t; + +typedef struct { + uint8_t fw; + uint8_t id; + uint8_t module; + uint8_t project; + uint16_t type; +} cst836u_info_t; + +typedef struct { + uint8_t i2c_addr; + uint32_t scl_speed_hz; + gpio_num_t int_gpio; + gpio_num_t rst_gpio; + /// rst_gpio 为 GPIO_NUM_NC 时用(IO 扩展口等)。 + /// / Used when rst_gpio is GPIO_NUM_NC (IO expander, etc.). + esp_err_t (*reset_fn)(void); +} cst836u_config_t; + +#define CST836U_CONFIG_DEFAULT() \ + { \ + .i2c_addr = CST836U_ADDR_DEFAULT, \ + .scl_speed_hz = CST836U_I2C_HZ_DEFAULT, \ + .int_gpio = GPIO_NUM_NC, \ + .rst_gpio = GPIO_NUM_NC, \ + .reset_fn = NULL, \ + } + +esp_err_t cst836u_init(i2c_master_bus_handle_t bus_handle, const cst836u_config_t* config, + cst836u_handle_t* handle); +esp_err_t cst836u_deinit(cst836u_handle_t handle); + +esp_err_t cst836u_read(cst836u_handle_t h, cst836u_touch_t* touch); +esp_err_t cst836u_get_info(cst836u_handle_t h, cst836u_info_t* info); + +esp_err_t cst836u_set_mode(cst836u_handle_t h, cst836u_mode_t mode); +cst836u_mode_t cst836u_get_mode(cst836u_handle_t h); + +/// 脉冲 RST(GPIO 或 reset_fn)。深睡后 I2C 直到这次脉冲才应答。 +/// / Pulse RST (gpio or reset_fn). Deep sleep ignores I2C until this. +esp_err_t cst836u_reset(cst836u_handle_t h); +/// RST 后再发 0xFE00。 +/// / RST then 0xFE00. +esp_err_t cst836u_wake(cst836u_handle_t h); + +/// IRQ 开漏、低有效。 +/// / IRQ is open-drain, active low. +bool cst836u_int_asserted(cst836u_handle_t h); +int cst836u_int_level(cst836u_handle_t h); +gpio_num_t cst836u_int_gpio(cst836u_handle_t h); + +const char* cst836u_mode_name(cst836u_mode_t mode); +const char* cst836u_event_name(uint8_t event); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/CMakeLists.txt b/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/CMakeLists.txt new file mode 100644 index 0000000..872a52f --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/CMakeLists.txt @@ -0,0 +1,6 @@ +idf_component_register( + SRCS "e0470_epaper_waveform.c" "e0470_waveform_trim.c" + INCLUDE_DIRS "include" + PRIV_INCLUDE_DIRS "waveforms" + REQUIRES epdiy +) diff --git a/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/LICENSE b/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/LICENSE new file mode 100644 index 0000000..500992f --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/LICENSE @@ -0,0 +1,17 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +Copyright 2026 mindreset + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/README.md b/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/README.md new file mode 100644 index 0000000..bc73a62 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/README.md @@ -0,0 +1,58 @@ +# e0470_epaper_waveform + +**E0470A01** 4.7 寸单色墨水屏(684 × 1216,40 pin)的 epdiy 波形表,以及一个在开机时从完整表派生更短表的裁剪器。 + +epdiy waveform tables for the **E0470A01** 4.7" monochrome e-paper panel (684 × 1216, 40-pin), plus a trimmer that derives shorter tables from the full ones at boot. + +组件名 / Registry name: `mindreset/e0470_epaper_waveform` + +## 波形表 / Tables + +| 符号 / Symbol | 内容 / Content | 用途 / Use | +| --- | --- | --- | +| `E0470_WAVEFORM` | 默认表。GC16 36 相 / GL16 37 相,开机由裁剪器派生;附阈值 DU(目标 0–7 → 黑,8–15 → 白) / Default. GC16 36 phases / GL16 37 phases derived by the trimmer at boot, plus a threshold DU (dest 0–7 → black, 8–15 → white) | 固件的页面刷新 / Page refresh in the firmware | +| `E0470_FULL_WAVEFORM` | 完整表:GC16 / GL16 各 48 相,DU 20 相,单一 0–50 °C 温度档 / Full tables: GC16 / GL16 48 phases, DU 20 phases, one 0–50 °C temperature range | 对照与 A/B 比较 / Reference and A/B comparison | +| `E0470_GRAY8_WAVEFORM` | 8 级可分辨灰阶,GC16 / GL16 各 30 相,整屏约 360 ms(默认表约 430 ms) / 8 distinguishable grays, GC16 / GL16 30 phases, about 360 ms per full refresh (default is about 430 ms) | 可选的更快页面模式 / Optional faster page mode | +| `E0470_FOLLOW_WAVEFORM` | 开机生成的 8 帧短 DU(黑 7 / 白 8),配合 FAST 扫描档使用 / 8-frame short DU (7 black / 8 white) built at boot, meant for the FAST scan profile | 触摸笔迹与实时读数 / Touch ink and live digits | + +开机在第一次刷新前调用一次 `e0470_waveform_init()`,它负责生成默认表与跟随表。 + +Call `e0470_waveform_init()` once before the first refresh; it builds the default and follow tables. + +## 裁剪器 / Trimmer + +`e0470_waveform_trim()` 在不改变灰阶梯子的前提下缩短完整的 GC16 / GL16 表。完整表中每对 (from, to) 的序列共用同一个骨架:保持补齐、往反向轨道擦除、在目标轨道饱和、灰阶尾、结尾保持。灰阶档位全部位于尾段;裁剪器保留尾段不动,只裁擦除上限和饱和开头,再把所有序列右对齐。代价是擦除余量变小,旧内容的残影会先出现,因此参数需要在真机上对着残影调整。 + +`e0470_waveform_trim()` shortens a full GC16 / GL16 table without changing its gray ladder. Every (from, to) sequence in the full table shares one skeleton: hold pad, erase toward the opposite rail, saturate on the target rail, gray tail, trailing hold. The gray levels live in the tail; the trimmer keeps the tail intact, cuts the erase maximum and the saturation head, then right-aligns every sequence. The cost is less erase margin, so ghosting from old content appears first. Parameters should be tuned on hardware against that ghosting. + +默认裁剪参数:`erase_max = 11`,`sat_cut = 5`,`white_sat_cut = 0`,`hold = 3`。 + +Default trim: `erase_max = 11`, `sat_cut = 5`, `white_sat_cut = 0`, `hold = 3`. + +## 辅助函数 / Helpers + +- `e0470_waveform_phases()`:取某条波形中 `MODE_GC16` / `MODE_GL16` / `MODE_DU` 的相位表。/ The phase table for `MODE_GC16` / `MODE_GL16` / `MODE_DU` in a given waveform. +- `e0470_phase_action()`:查 (from → to) 在指定相位的 2 bit 动作(保持 / 压黑 / 擦白)。/ The 2-bit action (hold / darken / erase) for (from → to) at a given phase. +- `e0470_follow_lut_build()`:生成任意帧数的跟随表。/ Build a follow table of any frame count. + +## 说明 / Notes + +自行调整屏幕波形会使设备失去保修。`waveforms/*.h` 是数据表,不应手工编辑。 + +Changing panel waveforms voids the device warranty. The `waveforms/*.h` files are data tables and are not meant to be edited by hand. + +帧周期固定为 11 090 µs(约 90 Hz),取自面板算法文档的设计点。扫描时序由 `read_pico`(`read_pico_epd_timing.h`)负责,不在本组件内。 + +The frame period is fixed at 11 090 µs (about 90 Hz), the design point in the panel's algorithm document. Scan timing is owned by `read_pico` (`read_pico_epd_timing.h`), not by this component. + +## 依赖 / Dependencies + +`epdiy`(提供 `epd_waveform.h`)。 + +`epdiy` (for `epd_waveform.h`). + +## 许可 / License + +Apache-2.0,见 [LICENSE](LICENSE)。波形表随本板附带,按现状提供。 + +Apache-2.0, see [LICENSE](LICENSE). The waveform tables ship with the board and are provided as-is. diff --git a/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/e0470_epaper_waveform.c b/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/e0470_epaper_waveform.c new file mode 100644 index 0000000..31bf0dd --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/e0470_epaper_waveform.c @@ -0,0 +1,253 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * E0470A01 波形装配:裁剪默认表、8 灰阶、跟随 DU。 + * 自行调整屏幕波形会使设备失去保修。 + * + * E0470A01 waveform assembly: trimmed default, 8-gray, follow DU. + * Changing panel waveforms voids the warranty. + */ + +#include "e0470_epaper_waveform.h" + +#include +#include + +#include "e0470_waveform_trim.h" +#include "du.h" +#include "gc16.h" +#include "gl16.h" +#include "gray8_gc16.h" +#include "gray8_gl16.h" + +// 温度档 0-50°C。在此固件中没有温度分档的演示。 +// / One 0–50°C temp range. This firmware has no multi-range demo. +static const EpdWaveformTempInterval e0470_intervals[] = { + { .min = 0, .max = 50 }, +}; + +// 把 (from, to) 的一个 2bit 动作写进 epdiy 的表:data[frame][to][from/4],高位是 from0。 +// / Write one 2-bit (from, to) action into the epdiy table: data[frame][to][from/4], MSB is from0. +static inline void lut_or(uint8_t (*data)[16][4], int f, int to, int from, int action) { + data[f][to][from / 4] |= (uint8_t)(action << (6 - 2 * (from % 4))); +} + +static inline int lut_get(const uint8_t (*data)[16][4], int f, int to, int from) { + return (data[f][to][from / 4] >> (6 - 2 * (from % 4))) & 3; +} + +void e0470_follow_lut_build(int frames, uint8_t (*dst)[16][4]) { + memset(dst, 0, (size_t)frames * 16 * 4); + // 两个方向各有自己的满推次数;短表(连续 DU 单帧、连调 dufr n)按表长封顶。 + // / Each direction has its own full-push count; short tables (1-frame + // continuous DU, live-tune dufr n) cap at the table length. + const int black_max = frames < E0470_FOLLOW_BLACK_FRAMES ? frames : E0470_FOLLOW_BLACK_FRAMES; + const int white_max = frames < E0470_FOLLOW_WHITE_FRAMES ? frames : E0470_FOLLOW_WHITE_FRAMES; + for (int to = 0; to < 16; to++) { + for (int from = 0; from < 16; from++) { + if (to == from) continue; + const int diff = to > from ? to - from : from - to; + const int action = to > from ? 2 : 1; // 往白推 0b10,往黑推 0b01 / 0b10 erase, 0b01 darken + const int budget = action == 2 ? white_max : black_max; + // 推动次数 = ceil(diff · 满推 / 15),至少 1:差得远多推,差得近少推。 + // / Push count = ceil(diff · full / 15), at least 1: far travels more, near travels less. + const int pushes = (diff * budget + 14) / 15; + for (int f = 0; f < pushes; f++) lut_or(dst, f, to, from, action); + } + } +} + +/* ---- 跟随 DU:开机按公式生成 / Follow DU: built at boot ---- */ +static uint8_t e0470_follow_data[E0470_FOLLOW_FRAMES][16][4]; +static const EpdWaveformPhases e0470_follow_phases = { + .phases = E0470_FOLLOW_FRAMES, + .phase_times = NULL, + .luts = (const uint8_t*)&e0470_follow_data[0], +}; +static const EpdWaveformPhases* e0470_follow_ranges[] = { &e0470_follow_phases }; +static const EpdWaveformMode e0470_follow_mode = { + .type = 1, // MODE_DU / MODE_DU + .temp_ranges = 1, + .range_data = &e0470_follow_ranges[0], +}; +static const EpdWaveformMode* e0470_follow_modes[] = { &e0470_follow_mode }; + +const EpdWaveform E0470_FOLLOW_WAVEFORM = { + .num_modes = 1, + .num_temp_ranges = 1, + .mode_data = e0470_follow_modes, + .temp_intervals = e0470_intervals, +}; + +/* ---- 阈值 DU / Threshold DU ---- */ +// 源表只认目标 0/15。中间灰按 50/50 切开,暗的走整段到黑、亮的走整段到白。 +// from 不切片,沿用源表对真实起点的时间序列,上一帧残留的浅墨也会被推到黑或白。 +// / Source tables only drive dest 0/15. Mid grays split 50/50: dark runs +// the full path to black, light the full path to white. from is not sliced; +// the source time series for the real start is reused, so leftover ink +// from the last frame is also pushed to black or white. +static uint8_t e0470_complete_du_data[E0470_FULL_DU_FRAMES][16][4]; +static const EpdWaveformPhases e0470_complete_du_phases = { + .phases = E0470_FULL_DU_FRAMES, + .phase_times = NULL, + .luts = (const uint8_t*)&e0470_complete_du_data[0], +}; +static const EpdWaveformPhases* e0470_complete_du_ranges[] = { + &e0470_complete_du_phases, +}; +static const EpdWaveformMode e0470_complete_du_mode = { + .type = 1, + .temp_ranges = 1, + .range_data = &e0470_complete_du_ranges[0], +}; + +static void e0470_complete_du_build(void) { + memset(e0470_complete_du_data, 0, sizeof(e0470_complete_du_data)); + for (int to = 0; to < 16; to++) { + const int to_bin = to < 8 ? 0 : 15; + for (int from = 0; from < 16; from++) { + for (int f = 0; f < E0470_FULL_DU_FRAMES; f++) { + const int action = lut_get(e0470_full_du_data, f, to_bin, from); + if (action != 0) lut_or(e0470_complete_du_data, f, to, from, action); + } + } + } +} + +// 白底 15→15 源表全保持。挂在已经「往白推」的那一相上再推 1 帧,不增加相数。 +// 差分会跳过未变白像素,GL16 必须走全像素这帧才打到白底。 +// / Source 15→15 is all-hold. Hang one extra white push on an already-white +// phase without adding phases. Diff skips unchanged white; GL16 must be +// full-pixel for this tick to hit the white background. +static void e0470_gl16_white_tick(uint8_t (*data)[16][4], int frames) { + int tick = -1; + for (int f = frames - 1; f >= 0; f--) { + for (int from = 0; from < 15; from++) { + if (lut_get(data, f, 15, from) == 2) { + tick = f; + break; + } + } + if (tick >= 0) break; + } + if (tick < 0) tick = frames > 2 ? frames - 3 : 0; + lut_or(data, tick, 15, 15, 2); +} + +/* ---- 完整表 / Full tables ---- */ +// DU 20 相,GC16 48 相;GL16 用 RAM 副本以便白底补 1 帧。 +// / DU 20, GC16 48; GL16 uses a RAM copy so the white-bg tick can be added. +static uint8_t e0470_full_gl16_live[E0470_FULL_GL16_FRAMES][16][4]; +static const EpdWaveformPhases e0470_full_gl16_live_phases = { + .phases = E0470_FULL_GL16_FRAMES, + .phase_times = NULL, + .luts = (const uint8_t*)&e0470_full_gl16_live[0], +}; +static const EpdWaveformPhases* e0470_full_gl16_live_ranges[] = { + &e0470_full_gl16_live_phases, +}; +static const EpdWaveformMode e0470_full_gl16_live_mode = { + .type = 5, .temp_ranges = 1, .range_data = &e0470_full_gl16_live_ranges[0], +}; +static const EpdWaveformMode* e0470_full_modes[] = { + &e0470_full_du_mode, + &e0470_full_gc16_mode, + &e0470_full_gl16_live_mode, +}; + +const EpdWaveform E0470_FULL_WAVEFORM = { + .num_modes = 3, + .num_temp_ranges = 1, + .mode_data = e0470_full_modes, + .temp_intervals = e0470_intervals, +}; + +/* ---- 8 灰阶表 / 8-gray tables ---- */ +// GC16 / GL16 各 30 相,拿灰阶档数换速度。 +// / GC16 / GL16 30 phases each; trade gray steps for speed. +static const EpdWaveformMode* e0470_gray8_modes[] = { + &e0470_complete_du_mode, + &e0470_gray8_gc16_mode, + &e0470_gray8_gl16_mode, +}; + +const EpdWaveform E0470_GRAY8_WAVEFORM = { + .num_modes = 3, + .num_temp_ranges = 1, + .mode_data = e0470_gray8_modes, + .temp_intervals = e0470_intervals, +}; + +/* ---- 默认表 / Default tables ---- */ +// 完整灰阶表裁掉余量,开机算进 RAM。 +// / Trim slack from the full gray tables into RAM at boot. +static uint8_t e0470_gc16_data[E0470_FULL_GC16_FRAMES][16][4]; +static uint8_t e0470_gl16_data[E0470_FULL_GL16_FRAMES][16][4]; +static const EpdWaveformPhases e0470_gc16_phases = { + .phases = E0470_GC16_FRAMES, + .phase_times = NULL, + .luts = (const uint8_t*)&e0470_gc16_data[0], +}; +static const EpdWaveformPhases e0470_gl16_phases = { + .phases = E0470_GL16_FRAMES, + .phase_times = NULL, + .luts = (const uint8_t*)&e0470_gl16_data[0], +}; +static const EpdWaveformPhases* e0470_gc16_ranges[] = { &e0470_gc16_phases }; +static const EpdWaveformPhases* e0470_gl16_ranges[] = { &e0470_gl16_phases }; +static const EpdWaveformMode e0470_gc16_mode = { + .type = 2, .temp_ranges = 1, .range_data = &e0470_gc16_ranges[0], +}; +static const EpdWaveformMode e0470_gl16_mode = { + .type = 5, .temp_ranges = 1, .range_data = &e0470_gl16_ranges[0], +}; +static const EpdWaveformMode* e0470_modes[] = { + &e0470_complete_du_mode, + &e0470_gc16_mode, + &e0470_gl16_mode, +}; + +const EpdWaveform E0470_WAVEFORM = { + .num_modes = 3, + .num_temp_ranges = 1, + .mode_data = e0470_modes, + .temp_intervals = e0470_intervals, +}; + +const EpdWaveformPhases* e0470_waveform_phases(const EpdWaveform* waveform, int mode) { + if (waveform == NULL) return NULL; + const int type = mode & 0x3F; + for (int i = 0; i < waveform->num_modes; i++) { + if (waveform->mode_data[i]->type == type) return waveform->mode_data[i]->range_data[0]; + } + return NULL; +} + +int e0470_phase_action(const EpdWaveformPhases* phases, int phase, int to, int from) { + if (phases == NULL || phases->luts == NULL) return 0; + if (phase < 0 || phase >= phases->phases) return 0; + if ((unsigned)to > 15 || (unsigned)from > 15) return 0; + const uint8_t* cell = phases->luts + ((size_t)phase * 16 + to) * 4 + from / 4; + return (*cell >> (6 - 2 * (from % 4))) & 3; +} + +void e0470_waveform_init(void) { + e0470_follow_lut_build(E0470_FOLLOW_FRAMES, e0470_follow_data); + e0470_complete_du_build(); + + const e0470_trim_t trim = { + .erase_max = E0470_TRIM_ERASE_MAX, + .sat_cut = E0470_TRIM_SAT_CUT, + .white_sat_cut = E0470_TRIM_WHITE_SAT_CUT, + .hold = E0470_TRIM_HOLD, + }; + const int gc = e0470_waveform_trim(&e0470_full_gc16_phases, &trim, e0470_gc16_data); + const int gl = e0470_waveform_trim(&e0470_full_gl16_phases, &trim, e0470_gl16_data); + assert(gc == E0470_GC16_FRAMES); + assert(gl == E0470_GL16_FRAMES); + + memcpy(e0470_full_gl16_live, e0470_full_gl16_data, sizeof(e0470_full_gl16_live)); + e0470_gl16_white_tick(e0470_full_gl16_live, E0470_FULL_GL16_FRAMES); + e0470_gl16_white_tick(e0470_gl16_data, E0470_GL16_FRAMES); +} diff --git a/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/e0470_waveform_trim.c b/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/e0470_waveform_trim.c new file mode 100644 index 0000000..54ba6f2 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/e0470_waveform_trim.c @@ -0,0 +1,110 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * 从完整灰阶表裁擦除/饱和余量。自行调整屏幕波形会使设备失去保修。 + * + * Trim erase/sat slack from a full gray table. Changing panel waveforms + * voids the warranty. + */ + +#include "e0470_waveform_trim.h" + +#include + +#include "epdiy.h" + +#define MAX_PHASES 64 + +// 表的字节布局:data[frame][to][from/4],每字节高位起 4 个 2bit 动作。 +// / Table layout: data[frame][to][from/4], four 2-bit actions MSB-first. +static inline int lut_get(const uint8_t (*data)[16][4], int f, int to, int from) { + return (data[f][to][from / 4] >> (6 - 2 * (from % 4))) & 3; +} + +static inline void lut_set(uint8_t (*data)[16][4], int f, int to, int from, int v) { + const int shift = 6 - 2 * (from % 4); + data[f][to][from / 4] = (uint8_t)((data[f][to][from / 4] & ~(3 << shift)) | (v << shift)); +} + +// 一对 (from, to) 裁完之后的序列,先攒起来,等知道最长的一条再统一右对齐。 +// / Hold each trimmed (from, to) sequence until the longest is known, then right-align. +typedef struct { + uint8_t seq[MAX_PHASES]; + int len; +} trimmed_seq_t; + +static int run_length(const uint8_t* seq, int start, int end, int value) { + int n = 0; + while (start + n < end && seq[start + n] == value) n++; + return n; +} + +// 把一条序列按骨架拆开再拼回去。to=15 的饱和极性是白(0b10),其余是黑(0b01), +// 擦除极性总是饱和的反面。没有擦除段(例如从白压到黑)的序列 e=0,逻辑照样成立。 +// / Split a sequence on the skeleton and rebuild. to=15 sat is white (0b10), +// else black (0b01); erase is the opposite of sat. No erase (e.g. white→black) +// means e=0; the same logic still holds. +static void trim_one( + const uint8_t* seq, int phases, int to, const e0470_trim_t* trim, trimmed_seq_t* out +) { + const int sat = to == 15 ? 2 : 1; + const int erase = to == 15 ? 1 : 2; + + int a = 0; + while (a < phases && seq[a] == 0) a++; + int z = phases; + while (z > a && seq[z - 1] == 0) z--; + out->len = 0; + if (a >= z) return; // 全保持(GL16 的白到白) / All-hold (GL16 white-to-white) + + int e = run_length(seq, a, z, erase); + int s = run_length(seq, a + e, z, sat); + int keep_e = e < trim->erase_max ? e : trim->erase_max; + const int want_cut = to == 15 ? trim->white_sat_cut : trim->sat_cut; + int cut_s = want_cut < s - 1 ? want_cut : s - 1; + if (cut_s < 0) cut_s = 0; + int keep_s = s - cut_s; + + int n = 0; + for (int i = 0; i < keep_e; i++) out->seq[n++] = (uint8_t)erase; + for (int i = 0; i < keep_s; i++) out->seq[n++] = (uint8_t)sat; + for (int i = a + e + s; i < z; i++) out->seq[n++] = seq[i]; + out->len = n; +} + +int e0470_waveform_trim( + const EpdWaveformPhases* src, const e0470_trim_t* trim, uint8_t (*dst_data)[16][4] +) { + if (src == NULL || trim == NULL || dst_data == NULL) return 0; + if (src->phases <= 0 || src->phases > MAX_PHASES) return 0; + if (trim->erase_max < 0 || trim->sat_cut < 0 || trim->white_sat_cut < 0 || trim->hold < 1) { + return 0; + } + + const uint8_t(*data)[16][4] = (const uint8_t(*)[16][4])src->luts; + static trimmed_seq_t seqs[16][16]; + int longest = 0; + + for (int to = 0; to < 16; to++) { + for (int from = 0; from < 16; from++) { + uint8_t seq[MAX_PHASES]; + for (int f = 0; f < src->phases; f++) seq[f] = (uint8_t)lut_get(data, f, to, from); + trim_one(seq, src->phases, to, trim, &seqs[to][from]); + if (seqs[to][from].len > longest) longest = seqs[to][from].len; + } + } + + const int phases = longest + trim->hold; + if (phases > src->phases) return 0; // 只允许变短,调用方按 src->phases 给的缓冲 / Shorter only; caller sized dst to src->phases + + memset(dst_data, 0, (size_t)phases * 16 * 4); + for (int to = 0; to < 16; to++) { + for (int from = 0; from < 16; from++) { + const trimmed_seq_t* t = &seqs[to][from]; + const int start = longest - t->len; // 右对齐到保持相之前 / Right-align before the hold phases + for (int i = 0; i < t->len; i++) lut_set(dst_data, start + i, to, from, t->seq[i]); + } + } + return phases; +} diff --git a/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/idf_component.yml b/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/idf_component.yml new file mode 100644 index 0000000..76d1661 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/idf_component.yml @@ -0,0 +1,13 @@ +version: "1.0.0" +description: "E0470A01 684x1216 epaper waveforms for epdiy: full GC16/GL16, boot-time trimmed default, 7-frame follow DU" +url: "https://github.com/mindreset/e0470_epaper_waveform" +repository: "https://github.com/mindreset/e0470_epaper_waveform.git" +issues: "https://github.com/mindreset/e0470_epaper_waveform/issues" +license: "Apache-2.0" +tags: + - epd + - eink + - waveform + - e0470-a01 +dependencies: + idf: ">=5.3" diff --git a/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/include/e0470_epaper_waveform.h b/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/include/e0470_epaper_waveform.h new file mode 100644 index 0000000..195e701 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/include/e0470_epaper_waveform.h @@ -0,0 +1,101 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * E0470A01(684×1216,40pin)的 epdiy 波形。 + * 自行调整屏幕波形会使设备失去保修。 + * E0470_WAVEFORM 默认:裁剪 GC16 36 相 / GL16 37 相 + 50/50 阈值 DU + * E0470_FULL_WAVEFORM GC16 / GL16 各 48 相,一个温度档 0-50°C + * E0470_GRAY8_WAVEFORM 8 灰阶:GC16 / GL16 各 30 相,灰阶少一半换来整屏约 360ms + * E0470_FOLLOW_WAVEFORM 跟随 DU:8 帧(黑 7 / 白 8),用于触摸笔迹 + * 默认与 8 灰阶波形挂阈值 DU(目标 0-7→黑、8-15→白);波形保留只驱动 0/15 的源表。 + * GL16 给 15→15 补 1 帧白推;刷新走全像素,否则差分跳过白底。 + * 开机先调一次 e0470_waveform_init()。 + * + * epdiy waveforms for the E0470A01 (684×1216, 40-pin). + * Changing panel waveforms voids the warranty. + * E0470_WAVEFORM Default: trimmed GC16 36 / GL16 37 + 50/50 threshold DU + * E0470_FULL_WAVEFORM GC16 / GL16 48 phases each, one 0–50°C temp range + * E0470_GRAY8_WAVEFORM 8-gray: GC16 / GL16 30 phases; half the steps, ~360 ms full + * E0470_FOLLOW_WAVEFORM Follow DU: 8 frames (black 7 / white 8) for touch ink + * Default and 8-gray hang a threshold DU (dest 0–7→black, 8–15→white); the + * source tables still drive only 0/15. GL16 adds one white push on 15→15; + * refresh is full-pixel or the diff skips already-white. + * Call e0470_waveform_init() once at boot. + */ + +#pragma once + +#include + +#include "epd_waveform.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/// 完整灰阶表:GC16 / GL16 各 48 相,白推动梯子 17 档、去重后 11 级可分辨灰阶。 +/// DU 是 20 相,只驱动目标 0/15;E0470_FULL_WAVEFORM 原样保留对照。 +/// / Full gray tables: GC16 / GL16 48 phases, 17-step white-push ladder, +/// 11 distinguishable after collapse. DU is 20 phases, dest 0/15 only; +/// E0470_FULL_WAVEFORM is kept as a reference. +#define E0470_FULL_DU_FRAMES 20 +#define E0470_FULL_GC16_FRAMES 48 +#define E0470_FULL_GL16_FRAMES 48 +extern const EpdWaveform E0470_FULL_WAVEFORM; + +/// 另一份表:只有 8 级可分辨灰阶(净推动梯子 8 档),30 相,整屏约 360ms(默认表约 430ms)。 +/// 灰阶档数 = 白推动梯子的档数,在这块屏上 16 级与 30 相不可兼得,所以它是备选不是默认。 +/// / Alternate table: 8 distinguishable grays (8-step net push), 30 phases, +/// ~360 ms full (default ~430 ms). Gray count is the white-push ladder; 16 +/// levels and 30 phases cannot both fit this panel, so this is optional. +#define E0470_GRAY8_GC16_FRAMES 30 +#define E0470_GRAY8_GL16_FRAMES 30 +extern const EpdWaveform E0470_GRAY8_WAVEFORM; + +/// 裁剪版保留新屏所需的 3 个尾部保持相。完整48相实测未明显改善边缘残影,因此恢复 +/// 已验证的主动段裁剪,避免无收益地增加约150ms;无损前导保持跳过仍由渲染器执行。 +/// / Trim keeps the 3 trailing hold phases the new panel needs. The full 48 +/// did not clearly help edge ghosting, so the proven active-segment trim is +/// restored instead of adding ~150 ms for no gain. Lossless leading-hold +/// skip is still done by the renderer. +#define E0470_TRIM_ERASE_MAX 11 +#define E0470_TRIM_SAT_CUT 5 +#define E0470_TRIM_WHITE_SAT_CUT 0 +#define E0470_TRIM_HOLD 3 +#define E0470_GC16_FRAMES 36 +#define E0470_GL16_FRAMES 37 + +/// 帧周期取算法文档第 8 节的设计点 ν≈90Hz。 +/// / Frame period is the §8 design point ν≈90 Hz. +#define E0470_WAVEFORM_FRAME_US 11090 +extern const EpdWaveform E0470_WAVEFORM; + +/// 触摸笔迹跟手用的 8 帧短 DU,走 FAST 扫描(帧周期约 7ms)。 +/// 推动次数按 |to-from| 比例分配,满幅迁移往黑推 7 相、往白推 8 相。 +/// / 8-frame short DU for touch ink, FAST scan (~7 ms/frame). Push count +/// scales with |to-from|; full travel is 7 black / 8 white. +#define E0470_FOLLOW_BLACK_FRAMES 7 +#define E0470_FOLLOW_WHITE_FRAMES 8 +#define E0470_FOLLOW_FRAMES 8 +extern const EpdWaveform E0470_FOLLOW_WAVEFORM; + +/// 按上面的公式生成 frames 帧的跟随表写进 dst(容量 frames×16×4 字节)。 +/// / Build a follow table of `frames` into dst (frames×16×4 bytes). +void e0470_follow_lut_build(int frames, uint8_t (*dst)[16][4]); + +/// 在一条 EpdWaveform 里按模式(MODE_GC16 / MODE_GL16 / MODE_DU)找唯一温度档的相位表。 +/// / Look up the single temp-range phase table for MODE_GC16 / MODE_GL16 / MODE_DU. +const EpdWaveformPhases* e0470_waveform_phases(const EpdWaveform* waveform, int mode); + +/// 查 (from→to) 在第 phase 相的 2bit 动作:0 保持、1 压黑、2 擦白。 +/// / 2-bit action for (from→to) at `phase`: 0 hold, 1 darken, 2 erase. +int e0470_phase_action(const EpdWaveformPhases* phases, int phase, int to, int from); + +/// 生成 E0470_WAVEFORM 与 E0470_FOLLOW_WAVEFORM 的表。必须在第一次刷新之前调用一次。 +/// / Build E0470_WAVEFORM and E0470_FOLLOW_WAVEFORM. Call once before the first refresh. +void e0470_waveform_init(void); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/include/e0470_waveform_trim.h b/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/include/e0470_waveform_trim.h new file mode 100644 index 0000000..204877d --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/include/e0470_waveform_trim.h @@ -0,0 +1,58 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * 从完整灰阶表派生更短的表。自行调整屏幕波形会使设备失去保修。 + * + * 完整表每对 (from, to) 的序列都是同一个骨架: + * + * [保持补齐][擦除:往目标的反向轨道推 0~19 相][饱和:压到目标侧轨道 17 相][灰阶尾:0~10 相][保持 3 相] + * + * 灰阶的档数和位置全在「灰阶尾」里,擦除和饱和只是为了让所有像素在进尾巴之前站 + * 在同一个起点上,室温下 19 / 17 相里有相当大的余量。这里保留尾巴一相不动,只裁擦除 + * 的上限和饱和的开头,再把所有序列重新右对齐,就得到一张相数更少、灰阶梯子不变的表。 + * 代价是擦除余量变小,旧内容的残影会先出来,所以参数要上机看着残影调。 + * + * Derive a shorter table from a full gray table. Changing panel waveforms + * voids the warranty. + * + * Every (from, to) sequence in the full table shares one skeleton: + * + * [hold pad][erase: opposite rail 0–19][sat: dest rail 17][gray tail 0–10][hold 3] + * + * Gray count and placement live in the tail. Erase and sat only park every + * pixel at the same start before the tail; room-temp 19 / 17 has a lot of + * slack. This keeps the tail intact, trims erase max and sat head, then + * right-aligns every sequence so phase count drops and the ladder stays. + * The cost is less erase margin, so old-content ghosting shows first; + * tune on hardware against that ghosting. + */ + +#pragma once + +#include + +#include "epd_waveform.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + int erase_max; ///< 擦除段最多保留几相(源表最长 19) / Max erase phases kept (source max 19) + int sat_cut; ///< 黑饱和段(to≠15)开头砍掉几相(源表 17,最短的一段是 14,所以 ≤13) / Black-sat head cut (source 17; shortest run 14, so ≤13) + int white_sat_cut; ///< 白饱和段(to=15)开头砍掉几相;白轨压不满就发灰,单独给 / White-sat head cut (to=15); under-driven white goes gray, so separate + int hold; ///< 结尾保持相数(源表 3,至少 1:要把最后一相的电压从像素上写掉) / Trailing holds (source 3, at least 1 to write off the last voltage) +} e0470_trim_t; + +/// 按参数从 src 派生一张表写进 dst_data,容量至少要 src->phases 相。返回新相数, +/// 参数不合法返回 0。src 可以是 GC16 也可以是 GL16,骨架一样。 +/// / Derive a table from src into dst_data (at least src->phases). Returns +/// the new phase count, or 0 on bad args. src may be GC16 or GL16; same skeleton. +int e0470_waveform_trim( + const EpdWaveformPhases* src, const e0470_trim_t* trim, uint8_t (*dst_data)[16][4] +); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/waveforms/du.h b/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/waveforms/du.h new file mode 100644 index 0000000..f23909a --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/waveforms/du.h @@ -0,0 +1,41 @@ +#pragma once +/* DU 表(EPD Waveform Engineering Studio V2 生成,type=1,20 相)。 + * 结尾的空相是保持相,不要「优化」掉,见 e0470_epaper_waveform.h。 + * 自行调整屏幕波形会使设备失去保修。 */ +#include + +#include "e0470_epaper_waveform.h" +#include "epd_internals.h" + +static const uint8_t e0470_full_du_data[E0470_FULL_DU_FRAMES][16][4] = { + {{0x15,0x55,0x55,0x55},{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},{0x80,0x00,0x00,0x00}}, + {{0x05,0x55,0x55,0x55},{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},{0xA0,0x00,0x00,0x00}}, + {{0x00,0x55,0x55,0x55},{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},{0xAA,0x80,0x00,0x00}}, + {{0x00,0x01,0x55,0x55},{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},{0xAA,0xA8,0x00,0x00}}, + {{0x00,0x00,0x15,0x55},{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},{0xAA,0xAA,0x80,0x00}}, + {{0x00,0x00,0x01,0x55},{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},{0xAA,0xAA,0xA8,0x00}}, + {{0x00,0x00,0x00,0x55},{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},{0xAA,0xAA,0xAA,0x00}}, + {{0x00,0x00,0x00,0x15},{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},{0xAA,0xAA,0xAA,0x80}}, + {{0x00,0x00,0x00,0x05},{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},{0xAA,0xAA,0xAA,0xA0}}, + {{0x00,0x00,0x00,0x05},{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},{0xAA,0xAA,0xAA,0xA0}}, + {{0x00,0x00,0x00,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,0x00,0x00},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0xAA,0xAA,0xAA,0xA0}}, + {{0x00,0x00,0x00,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,0x00,0x00},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0xAA,0xAA,0xAA,0xA8}}, + {{0x00,0x00,0x00,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,0x00,0x00},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0xAA,0xAA,0xAA,0xA8}}, + {{0x00,0x00,0x00,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,0x00,0x00},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0xAA,0xAA,0xAA,0xA8}}, + {{0x00,0x00,0x00,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,0x00,0x00},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0xAA,0xAA,0xAA,0xA8}}, + {{0x00,0x00,0x00,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,0x00,0x00},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0xAA,0xAA,0xAA,0xA8}}, + {{0x00,0x00,0x00,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,0x00,0x00},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0xAA,0xAA,0xAA,0xA8}}, + {{0x15,0x55,0x55,0x55},{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},{0xAA,0xAA,0xAA,0xA8}}, + {{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,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,0x00,0x00}} +}; + +static const EpdWaveformPhases e0470_full_du_phases = { + .phases = E0470_FULL_DU_FRAMES, + .phase_times = NULL, + .luts = (const uint8_t*)&e0470_full_du_data[0], +}; +static const EpdWaveformPhases* e0470_full_du_ranges[] = { &e0470_full_du_phases }; +static const EpdWaveformMode e0470_full_du_mode = { + .type = 1, .temp_ranges = 1, .range_data = &e0470_full_du_ranges[0], +}; diff --git a/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/waveforms/gc16.h b/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/waveforms/gc16.h new file mode 100644 index 0000000..8b788af --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/waveforms/gc16.h @@ -0,0 +1,68 @@ +#pragma once +/* GC16 表(EPD Waveform Engineering Studio V2 生成,type=2,48 相)。 + * 结尾的空相是保持相,不要「优化」掉,见 e0470_epaper_waveform.h。 + * 自行调整屏幕波形会使设备失去保修。 */ +#include + +#include "e0470_epaper_waveform.h" +#include "epd_internals.h" + +static const uint8_t e0470_full_gc16_data[E0470_FULL_GC16_FRAMES][16][4] = { + {{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},{0x80,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},{0x80,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0xA0,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},{0xA0,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0xA0,0x00,0x00,0x00},{0xAA,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},{0x80,0x00,0x00,0x00},{0xAA,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0xAA,0x00,0x00,0x00},{0xAA,0xA8,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},{0x80,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0xA0,0x00,0x00,0x00},{0xAA,0xA8,0x00,0x00},{0xA0,0x00,0x00,0x00},{0xAA,0xA8,0x00,0x00},{0xAA,0xAA,0x80,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},{0x80,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0xA0,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0xAA,0x00,0x00,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0x00,0x00,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0xA8,0x00},{0x00,0x00,0x00,0x00}}, + {{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0xA0,0x00,0x00,0x00},{0xA0,0x00,0x00,0x00},{0xAA,0x00,0x00,0x00},{0xA0,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0xAA,0xA8,0x00,0x00},{0xAA,0xAA,0xA8,0x00},{0xAA,0xA8,0x00,0x00},{0xAA,0xAA,0xA8,0x00},{0xAA,0xAA,0xAA,0x00},{0x00,0x00,0x00,0x00}}, + {{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0xA0,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0xA0,0x00,0x00,0x00},{0xAA,0x00,0x00,0x00},{0xAA,0x00,0x00,0x00},{0xAA,0xA8,0x00,0x00},{0xAA,0x00,0x00,0x00},{0xA0,0x00,0x00,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0x80},{0x00,0x00,0x00,0x00}}, + {{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0xAA,0x00,0x00,0x00},{0xA0,0x00,0x00,0x00},{0xAA,0x00,0x00,0x00},{0xAA,0xA8,0x00,0x00},{0xAA,0xA8,0x00,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xA8,0x00,0x00},{0xAA,0x00,0x00,0x00},{0xAA,0xAA,0xA8,0x00},{0xAA,0xAA,0xAA,0x80},{0xAA,0xAA,0xA8,0x00},{0xAA,0xAA,0xAA,0x80},{0xAA,0xAA,0xAA,0xA0},{0x00,0x00,0x00,0x00}}, + {{0x00,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0xAA,0xA8,0x00,0x00},{0xAA,0x00,0x00,0x00},{0xAA,0xA8,0x00,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0xA8,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xA8,0x00,0x00},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA0},{0x00,0x00,0x00,0x00}}, + {{0x80,0x00,0x00,0x00},{0xA0,0x00,0x00,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xA8,0x00,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0xA8,0x00},{0xAA,0xAA,0xA8,0x00},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xA8,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0xAA,0x80},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0x80},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x00,0x00,0x00,0x01}}, + {{0xA0,0x00,0x00,0x00},{0xAA,0x00,0x00,0x00},{0xAA,0xAA,0xA8,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0xA8,0x00},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0x80},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xA8,0x00},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x00,0x00,0x00,0x01}}, + {{0xAA,0x00,0x00,0x00},{0xAA,0xA8,0x00,0x00},{0xAA,0xAA,0xAA,0x20},{0xAA,0xAA,0xA8,0x00},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0x80},{0xAA,0xAA,0xAA,0x80},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0x80},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x00,0x00,0x00,0x01}}, + {{0xAA,0xA8,0x00,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0x80},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0x80},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x00,0x00,0x00,0x01}}, + {{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0xA8,0x00},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0x80},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x00,0x00,0x00,0x01}}, + {{0xAA,0xAA,0xA8,0x00},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x00,0x00,0x00,0x01}}, + {{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0x80},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x00,0x00,0x00,0x01}}, + {{0xAA,0xAA,0xAA,0x80},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x00,0x00,0x00,0x05}}, + {{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA4},{0x55,0x55,0x55,0x55},{0x00,0x00,0x00,0x05}}, + {{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xA8},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x00,0x00,0x00,0x05}}, + {{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xA4},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x00,0x00,0x00,0x15}}, + {{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x00,0x00,0x00,0x55}}, + {{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA4},{0xAA,0xAA,0xAA,0xA9},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x00,0x00,0x01,0x55}}, + {{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x54},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x00,0x00,0x15,0x55}}, + {{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0x94},{0xAA,0xAA,0xAA,0xA8},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x00,0x01,0x55,0x55}}, + {{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x55,0x55,0x55,0x54},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x00,0x15,0x55,0x55}}, + {{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x05,0x55,0x55,0x55}}, + {{0xAA,0xAA,0xAA,0xA8},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x15,0x55,0x55,0x55}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA},{0x55,0x55,0x55,0x55},{0x00,0x00,0x00,0x00},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA},{0x55,0x55,0x55,0x55},{0x00,0x00,0x00,0x00},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x00,0x00,0x00,0x00},{0xAA,0xAA,0xAA,0xAA},{0x00,0x00,0x00,0x00},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x00,0x00,0x00,0x00},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA},{0x00,0x00,0x00,0x00},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA},{0x55,0x55,0x55,0x55},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA}}, + {{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA}}, + {{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,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,0x00,0x00}} +}; + +static const EpdWaveformPhases e0470_full_gc16_phases = { + .phases = E0470_FULL_GC16_FRAMES, + .phase_times = NULL, + .luts = (const uint8_t*)&e0470_full_gc16_data[0], +}; +static const EpdWaveformPhases* e0470_full_gc16_ranges[1] = { &e0470_full_gc16_phases }; +static const EpdWaveformMode e0470_full_gc16_mode = { + .type = 2, .temp_ranges = 1, .range_data = &e0470_full_gc16_ranges[0], +}; diff --git a/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/waveforms/gl16.h b/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/waveforms/gl16.h new file mode 100644 index 0000000..49e4c47 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/waveforms/gl16.h @@ -0,0 +1,69 @@ +#pragma once +/* GL16 表(EPD Waveform Engineering Studio V2 生成,type=5,48 相)。 + * 结尾的空相是保持相,不要「优化」掉,见 e0470_epaper_waveform.h。 + * 自行调整屏幕波形会使设备失去保修。 */ +#include + +#include "e0470_epaper_waveform.h" +#include "epd_internals.h" + +static const uint8_t e0470_full_gl16_data[E0470_FULL_GL16_FRAMES][16][4] = { + {{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},{0x80,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},{0x80,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0xA0,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},{0xA0,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0xA0,0x00,0x00,0x00},{0xAA,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},{0x80,0x00,0x00,0x00},{0xAA,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0xAA,0x00,0x00,0x00},{0xAA,0xA8,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},{0x80,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0xA0,0x00,0x00,0x00},{0xAA,0xA8,0x00,0x00},{0xA0,0x00,0x00,0x00},{0xAA,0xA8,0x00,0x00},{0xAA,0xAA,0x80,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},{0x80,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0xA0,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0xAA,0x00,0x00,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0x00,0x00,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0xA8,0x00},{0x00,0x00,0x00,0x00}}, + {{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0xA0,0x00,0x00,0x00},{0xA0,0x00,0x00,0x00},{0xAA,0x00,0x00,0x00},{0xA0,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0xAA,0xA8,0x00,0x00},{0xAA,0xAA,0xA8,0x00},{0xAA,0xA8,0x00,0x00},{0xAA,0xAA,0xA8,0x00},{0xAA,0xAA,0xAA,0x00},{0x00,0x00,0x00,0x00}}, + {{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0xA0,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0xA0,0x00,0x00,0x00},{0xAA,0x00,0x00,0x00},{0xAA,0x00,0x00,0x00},{0xAA,0xA8,0x00,0x00},{0xAA,0x00,0x00,0x00},{0xA0,0x00,0x00,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0x80},{0x00,0x00,0x00,0x00}}, + {{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0xAA,0x00,0x00,0x00},{0xA0,0x00,0x00,0x00},{0xAA,0x00,0x00,0x00},{0xAA,0xA8,0x00,0x00},{0xAA,0xA8,0x00,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xA8,0x00,0x00},{0xAA,0x00,0x00,0x00},{0xAA,0xAA,0xA8,0x00},{0xAA,0xAA,0xAA,0x80},{0xAA,0xAA,0xA8,0x00},{0xAA,0xAA,0xAA,0x80},{0xAA,0xAA,0xAA,0xA0},{0x00,0x00,0x00,0x00}}, + {{0x00,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0xAA,0xA8,0x00,0x00},{0xAA,0x00,0x00,0x00},{0xAA,0xA8,0x00,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0xA8,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xA8,0x00,0x00},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA0},{0x00,0x00,0x00,0x00}}, + {{0x80,0x00,0x00,0x00},{0xA0,0x00,0x00,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xA8,0x00,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0xA8,0x00},{0xAA,0xAA,0xA8,0x00},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xA8,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0xAA,0x80},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0x80},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x00,0x00,0x00,0x00}}, + {{0xA0,0x00,0x00,0x00},{0xAA,0x00,0x00,0x00},{0xAA,0xAA,0xA8,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0xA8,0x00},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0x80},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xA8,0x00},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x00,0x00,0x00,0x00}}, + {{0xAA,0x00,0x00,0x00},{0xAA,0xA8,0x00,0x00},{0xAA,0xAA,0xAA,0x20},{0xAA,0xAA,0xA8,0x00},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0x80},{0xAA,0xAA,0xAA,0x80},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0x80},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x00,0x00,0x00,0x00}}, + {{0xAA,0xA8,0x00,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0x80},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0x80},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x00,0x00,0x00,0x00}}, + {{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0xA8,0x00},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0x80},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x00,0x00,0x00,0x00}}, + {{0xAA,0xAA,0xA8,0x00},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x00,0x00,0x00,0x04}}, + {{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0x80},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x00,0x00,0x00,0x04}}, + {{0xAA,0xAA,0xAA,0x80},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x00,0x00,0x00,0x04}}, + {{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA4},{0x55,0x55,0x55,0x55},{0x00,0x00,0x00,0x14}}, + {{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xA8},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x00,0x00,0x00,0x54}}, + {{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xA4},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x00,0x00,0x01,0x54}}, + {{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x00,0x00,0x15,0x54}}, + {{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA4},{0xAA,0xAA,0xAA,0xA9},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x00,0x01,0x55,0x54}}, + {{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x54},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x00,0x05,0x55,0x54}}, + {{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0x94},{0xAA,0xAA,0xAA,0xA8},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x00,0x05,0x55,0x54}}, + {{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x55,0x55,0x55,0x54},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x00,0x15,0x55,0x54}}, + {{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x05,0x5A,0xAA,0xA8}}, + {{0xAA,0xAA,0xAA,0xA8},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x15,0x5A,0xAA,0xA8}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xA8}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xA8}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xA8}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xA8}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xA8}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xA8}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xA8}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xA8}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA},{0x55,0x55,0x55,0x55},{0x00,0x00,0x00,0x00},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xA8}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xA8}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA},{0x55,0x55,0x55,0x55},{0x00,0x00,0x00,0x00},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xA8}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xA8}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x00,0x00,0x00,0x00},{0xAA,0xAA,0xAA,0xAA},{0x00,0x00,0x00,0x00},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xA8}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x00,0x00,0x00,0x00},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA},{0x00,0x00,0x00,0x00},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xA8}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xA8}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA},{0x55,0x55,0x55,0x55},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xA5,0x55,0x54}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xA8}}, + {{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xA8}}, + {{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,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,0x00,0x00}} +}; + +static const EpdWaveformPhases e0470_full_gl16_phases = { + .phases = E0470_FULL_GL16_FRAMES, + .phase_times = NULL, + .luts = (const uint8_t*)&e0470_full_gl16_data[0], +}; +static const EpdWaveformPhases* e0470_full_gl16_ranges[1] = { &e0470_full_gl16_phases }; +static const EpdWaveformMode e0470_full_gl16_mode = { + .type = 5, .temp_ranges = 1, .range_data = &e0470_full_gl16_ranges[0], +}; diff --git a/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/waveforms/gray8_gc16.h b/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/waveforms/gray8_gc16.h new file mode 100644 index 0000000..21c8336 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/waveforms/gray8_gc16.h @@ -0,0 +1,51 @@ +#pragma once +/* 8 灰阶 GC16 表(type=2,30 相,温度槽 6,各槽几乎相同)。 + * 结尾的空相是保持相,不要「优化」掉,见 e0470_epaper_waveform.h。 + * 自行调整屏幕波形会使设备失去保修。 */ +#include + +#include "e0470_epaper_waveform.h" +#include "epd_internals.h" + +static const uint8_t e0470_gray8_gc16_data[E0470_GRAY8_GC16_FRAMES][16][4] = { + {{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,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},{0x80,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},{0xA8,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},{0x80,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0xAA,0xA0,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},{0x80,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0xA8,0x00,0x00,0x00},{0xA8,0x00,0x00,0x00},{0xAA,0xAA,0x80,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},{0x80,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0xA8,0x00,0x00,0x00},{0xA8,0x00,0x00,0x00},{0xA8,0x00,0x00,0x00},{0xAA,0xA0,0x00,0x00},{0xAA,0xA0,0x00,0x00},{0xAA,0xAA,0xAA,0x00},{0x00,0x00,0x00,0x00}}, + {{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0xA8,0x00,0x00,0x00},{0xA8,0x00,0x00,0x00},{0xA8,0x00,0x00,0x00},{0xAA,0xA0,0x00,0x00},{0xAA,0xA0,0x00,0x00},{0xAA,0xA0,0x00,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0xAA,0xA0},{0x00,0x00,0x00,0x00}}, + {{0x00,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0xA8,0x00,0x00,0x00},{0xA8,0x00,0x00,0x00},{0xA8,0x00,0x00,0x00},{0xAA,0xA0,0x00,0x00},{0xAA,0xA0,0x00,0x00},{0xAA,0xA0,0x00,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0xA0},{0x00,0x00,0x00,0x00}}, + {{0x80,0x00,0x00,0x00},{0xA8,0x00,0x00,0x00},{0xA8,0x00,0x00,0x00},{0xAA,0xA0,0x00,0x00},{0xAA,0xA0,0x00,0x00},{0xAA,0xA0,0x00,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA8},{0x00,0x00,0x00,0x01}}, + {{0xA8,0x00,0x00,0x00},{0xAA,0xA0,0x00,0x00},{0xAA,0xA0,0x00,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA8},{0x00,0x00,0x00,0x01}}, + {{0xAA,0xA0,0x00,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x00,0x00,0x00,0x01}}, + {{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x55,0x55,0x55,0x55},{0x00,0x00,0x00,0x05}}, + {{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x55,0x55,0x55,0x55},{0x00,0x00,0x00,0x05}}, + {{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x00,0x00,0x00,0x55}}, + {{0xAA,0xAA,0xAA,0xA0},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x00,0x00,0x15,0x55}}, + {{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x00,0x05,0x55,0x55}}, + {{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0xAA,0xAA,0xAA,0xA8},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x01,0x55,0x55,0x55}}, + {{0xAA,0xAA,0xAA,0xA8},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x15,0x55,0x55,0x55}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA}}, + {{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA}}, + {{0x55,0x55,0x55,0x55},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA},{0xAA,0xAA,0xAA,0xAA}}, + {{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,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,0x00,0x00}}, +}; + +static const EpdWaveformPhases e0470_gray8_gc16_phases = { + .phases = E0470_GRAY8_GC16_FRAMES, + .phase_times = NULL, + .luts = (const uint8_t*)&e0470_gray8_gc16_data[0], +}; +static const EpdWaveformPhases* e0470_gray8_gc16_ranges[] = { &e0470_gray8_gc16_phases }; +static const EpdWaveformMode e0470_gray8_gc16_mode = { + .type = 2, .temp_ranges = 1, .range_data = &e0470_gray8_gc16_ranges[0], +}; diff --git a/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/waveforms/gray8_gl16.h b/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/waveforms/gray8_gl16.h new file mode 100644 index 0000000..cc38157 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/waveforms/gray8_gl16.h @@ -0,0 +1,51 @@ +#pragma once +/* 8 灰阶 GL16 表(type=5,30 相,温度槽 6,各槽几乎相同)。 + * 结尾的空相是保持相,不要「优化」掉,见 e0470_epaper_waveform.h。 + * 自行调整屏幕波形会使设备失去保修。 */ +#include + +#include "e0470_epaper_waveform.h" +#include "epd_internals.h" + +static const uint8_t e0470_gray8_gl16_data[E0470_GRAY8_GL16_FRAMES][16][4] = { + {{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,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},{0x80,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},{0xA8,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},{0x80,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0xAA,0xA0,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},{0x80,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0xA8,0x00,0x00,0x00},{0xA8,0x00,0x00,0x00},{0xAA,0xAA,0x80,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},{0x80,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0xA8,0x00,0x00,0x00},{0xA8,0x00,0x00,0x00},{0xA8,0x00,0x00,0x00},{0xAA,0xA0,0x00,0x00},{0xAA,0xA0,0x00,0x00},{0xAA,0xAA,0xAA,0x00},{0x00,0x00,0x00,0x00}}, + {{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0xA8,0x00,0x00,0x00},{0xA8,0x00,0x00,0x00},{0xA8,0x00,0x00,0x00},{0xAA,0xA0,0x00,0x00},{0xAA,0xA0,0x00,0x00},{0xAA,0xA0,0x00,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0xAA,0xA0},{0x00,0x00,0x00,0x00}}, + {{0x00,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0x80,0x00,0x00,0x00},{0xA8,0x00,0x00,0x00},{0xA8,0x00,0x00,0x00},{0xA8,0x00,0x00,0x00},{0xAA,0xA0,0x00,0x00},{0xAA,0xA0,0x00,0x00},{0xAA,0xA0,0x00,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0x80,0x00},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0x00},{0xAA,0xAA,0xAA,0xA0},{0x00,0x00,0x00,0x00}}, + {{0x00,0x00,0x00,0x00},{0x88,0x00,0x00,0x00},{0xA0,0x00,0x00,0x00},{0xA8,0xA0,0x00,0x00},{0xAA,0x20,0x00,0x00},{0xAA,0x80,0x00,0x00},{0xAA,0xA2,0x80,0x00},{0xAA,0xA8,0x80,0x00},{0xAA,0xAA,0x00,0x00},{0xAA,0xAA,0x8A,0x00},{0xAA,0xAA,0xA2,0x00},{0xAA,0xAA,0xA8,0x00},{0xAA,0xAA,0xAA,0x20},{0xAA,0xAA,0xAA,0x80},{0xAA,0xAA,0xAA,0xA0},{0x00,0x00,0x00,0x00}}, + {{0x28,0x00,0x00,0x00},{0x8A,0xA0,0x00,0x00},{0xA2,0xA0,0x00,0x00},{0xA8,0xAA,0x80,0x00},{0xAA,0x2A,0x80,0x00},{0xAA,0x8A,0x80,0x00},{0xAA,0xA2,0xAA,0x00},{0xAA,0xA8,0xAA,0x00},{0xAA,0xAA,0x2A,0x00},{0xAA,0xAA,0x8A,0xA0},{0xAA,0xAA,0xA2,0xA0},{0xAA,0xAA,0xA8,0xA0},{0xAA,0xAA,0xAA,0x20},{0xAA,0xAA,0xAA,0x80},{0xAA,0xAA,0xAA,0xA0},{0x00,0x00,0x00,0x00}}, + {{0x2A,0xA0,0x00,0x00},{0x8A,0xAA,0x80,0x00},{0xA2,0xAA,0x80,0x00},{0xA8,0xAA,0xAA,0x00},{0xAA,0x2A,0xAA,0x00},{0xAA,0x8A,0xAA,0x00},{0xAA,0xA2,0xAA,0xA0},{0xAA,0xA8,0xAA,0xA0},{0xAA,0xAA,0x2A,0xA0},{0xAA,0xAA,0x8A,0xA0},{0xAA,0xAA,0xA2,0xA0},{0xAA,0xAA,0xA8,0xA0},{0xAA,0xAA,0xAA,0x28},{0xAA,0xAA,0xAA,0x88},{0xAA,0xAA,0xAA,0xA0},{0x00,0x00,0x00,0x00}}, + {{0x2A,0xAA,0x80,0x00},{0x8A,0xAA,0xAA,0x00},{0xA2,0xAA,0xAA,0x00},{0xA8,0xAA,0xAA,0xA0},{0xAA,0x2A,0xAA,0xA0},{0xAA,0x8A,0xAA,0xA0},{0xAA,0xA2,0xAA,0xA0},{0xAA,0xA8,0xAA,0xA0},{0xAA,0xAA,0x2A,0xA0},{0xAA,0xAA,0x8A,0xA8},{0xAA,0xAA,0xA2,0xA8},{0xAA,0xAA,0xA8,0xA8},{0xAA,0xAA,0xAA,0x28},{0xAA,0xAA,0xAA,0x88},{0x55,0x55,0x55,0x51},{0x00,0x00,0x00,0x04}}, + {{0x2A,0xAA,0xAA,0x00},{0x8A,0xAA,0xAA,0xA0},{0xA2,0xAA,0xAA,0xA0},{0xA8,0xAA,0xAA,0xA0},{0xAA,0x2A,0xAA,0xA0},{0xAA,0x8A,0xAA,0xA0},{0xAA,0xA2,0xAA,0xA8},{0xAA,0xA8,0xAA,0xA8},{0xAA,0xAA,0x2A,0xA8},{0xAA,0xAA,0x8A,0xA8},{0xAA,0xAA,0xA2,0xA8},{0xAA,0xAA,0xA8,0xA8},{0xAA,0xAA,0xAA,0x28},{0xAA,0xAA,0xAA,0x88},{0x55,0x55,0x55,0x51},{0x00,0x00,0x00,0x04}}, + {{0x2A,0xAA,0xAA,0xA0},{0x8A,0xAA,0xAA,0xA0},{0xA2,0xAA,0xAA,0xA0},{0xA8,0xAA,0xAA,0xA8},{0xAA,0x2A,0xAA,0xA8},{0xAA,0x8A,0xAA,0xA8},{0xAA,0xA2,0xAA,0xA8},{0xAA,0xA8,0xAA,0xA8},{0xAA,0xAA,0x2A,0xA8},{0xAA,0xAA,0x8A,0xA8},{0xAA,0xAA,0xA2,0xA8},{0xAA,0xAA,0xA8,0xA8},{0x55,0x55,0x55,0x15},{0x55,0x55,0x55,0x45},{0x55,0x55,0x55,0x51},{0x00,0x00,0x00,0x54}}, + {{0x2A,0xAA,0xAA,0xA0},{0x8A,0xAA,0xAA,0xA8},{0xA2,0xAA,0xAA,0xA8},{0xA8,0xAA,0xAA,0xA8},{0xAA,0x2A,0xAA,0xA8},{0xAA,0x8A,0xAA,0xA8},{0xAA,0xA2,0xAA,0xA8},{0xAA,0xA8,0xAA,0xA8},{0xAA,0xAA,0x2A,0xA8},{0x55,0x55,0x45,0x55},{0x55,0x55,0x51,0x55},{0x55,0x55,0x54,0x55},{0x55,0x55,0x55,0x15},{0x55,0x55,0x55,0x45},{0x55,0x55,0x55,0x51},{0x00,0x00,0x15,0x54}}, + {{0x2A,0xAA,0xAA,0xA8},{0x8A,0xAA,0xAA,0xA8},{0xA2,0xAA,0xAA,0xA8},{0xA8,0xAA,0xAA,0xA8},{0xAA,0x2A,0xAA,0xA8},{0xAA,0x8A,0xAA,0xA8},{0x55,0x51,0x55,0x55},{0x55,0x54,0x55,0x55},{0x55,0x55,0x15,0x55},{0x55,0x55,0x45,0x55},{0x55,0x55,0x51,0x55},{0x55,0x55,0x54,0x55},{0x55,0x55,0x55,0x15},{0x55,0x55,0x55,0x45},{0x55,0x55,0x55,0x51},{0x00,0x05,0x55,0x54}}, + {{0x2A,0xAA,0xAA,0xA8},{0x8A,0xAA,0xAA,0xA8},{0xA2,0xAA,0xAA,0xA8},{0x54,0x55,0x55,0x55},{0x55,0x15,0x55,0x55},{0x55,0x45,0x55,0x55},{0x55,0x51,0x55,0x55},{0x55,0x54,0x55,0x55},{0x55,0x55,0x15,0x55},{0x55,0x55,0x45,0x55},{0x55,0x55,0x51,0x55},{0x55,0x55,0x54,0x55},{0x55,0x55,0x55,0x15},{0x55,0x55,0x55,0x45},{0x55,0x55,0x55,0x51},{0x01,0x55,0x55,0x54}}, + {{0x2A,0xAA,0xAA,0xA8},{0x45,0x55,0x55,0x55},{0x51,0x55,0x55,0x55},{0x54,0x55,0x55,0x55},{0x55,0x15,0x55,0x55},{0x55,0x45,0x55,0x55},{0x55,0x51,0x55,0x55},{0x55,0x54,0x55,0x55},{0x55,0x55,0x15,0x55},{0x55,0x55,0x45,0x55},{0x55,0x55,0x51,0x55},{0x55,0x55,0x54,0x55},{0x55,0x55,0x55,0x15},{0x55,0x55,0x55,0x45},{0x55,0x55,0x55,0x51},{0x15,0x55,0x55,0x54}}, + {{0x15,0x55,0x55,0x55},{0x45,0x55,0x55,0x55},{0x51,0x55,0x55,0x55},{0x54,0x55,0x55,0x55},{0x55,0x15,0x55,0x55},{0x55,0x45,0x55,0x55},{0x55,0x51,0x55,0x55},{0x55,0x54,0x55,0x55},{0x55,0x55,0x15,0x55},{0x55,0x55,0x45,0x55},{0x55,0x55,0x51,0x55},{0x55,0x55,0x54,0x55},{0x55,0x55,0x55,0x15},{0x55,0x55,0x55,0x45},{0x55,0x55,0x55,0x51},{0xAA,0xAA,0xAA,0xA8}}, + {{0x15,0x55,0x55,0x55},{0x45,0x55,0x55,0x55},{0x51,0x55,0x55,0x55},{0x54,0x55,0x55,0x55},{0x55,0x15,0x55,0x55},{0x55,0x45,0x55,0x55},{0x55,0x51,0x55,0x55},{0x55,0x54,0x55,0x55},{0x55,0x55,0x15,0x55},{0x55,0x55,0x45,0x55},{0x55,0x55,0x51,0x55},{0x55,0x55,0x54,0x55},{0x55,0x55,0x55,0x15},{0x55,0x55,0x55,0x45},{0x55,0x55,0x55,0x51},{0xAA,0xAA,0xAA,0xA8}}, + {{0x15,0x55,0x55,0x55},{0x45,0x55,0x55,0x55},{0x51,0x55,0x55,0x55},{0x54,0x55,0x55,0x55},{0x55,0x15,0x55,0x55},{0x55,0x45,0x55,0x55},{0x55,0x51,0x55,0x55},{0x55,0x54,0x55,0x55},{0x55,0x55,0x15,0x55},{0x55,0x55,0x45,0x55},{0x55,0x55,0x51,0x55},{0x55,0x55,0x54,0x55},{0x55,0x55,0x55,0x15},{0x55,0x55,0x55,0x45},{0x55,0x55,0x55,0x51},{0xAA,0xAA,0xAA,0xA8}}, + {{0x15,0x55,0x55,0x55},{0x45,0x55,0x55,0x55},{0x51,0x55,0x55,0x55},{0x54,0x55,0x55,0x55},{0x55,0x15,0x55,0x55},{0x55,0x45,0x55,0x55},{0x55,0x51,0x55,0x55},{0x55,0x54,0x55,0x55},{0x55,0x55,0x15,0x55},{0x55,0x55,0x45,0x55},{0x55,0x55,0x51,0x55},{0x55,0x55,0x54,0x55},{0x55,0x55,0x55,0x15},{0x55,0x55,0x55,0x45},{0xAA,0xAA,0xAA,0xA2},{0xAA,0xAA,0xAA,0xA8}}, + {{0x15,0x55,0x55,0x55},{0x45,0x55,0x55,0x55},{0x51,0x55,0x55,0x55},{0x54,0x55,0x55,0x55},{0x55,0x15,0x55,0x55},{0x55,0x45,0x55,0x55},{0x55,0x51,0x55,0x55},{0x55,0x54,0x55,0x55},{0x55,0x55,0x15,0x55},{0x55,0x55,0x45,0x55},{0x55,0x55,0x51,0x55},{0x55,0x55,0x54,0x55},{0x55,0x55,0x55,0x15},{0x55,0x55,0x55,0x45},{0xAA,0xAA,0xAA,0xA2},{0xAA,0xAA,0xAA,0xA8}}, + {{0x15,0x55,0x55,0x55},{0x45,0x55,0x55,0x55},{0x51,0x55,0x55,0x55},{0x54,0x55,0x55,0x55},{0x55,0x15,0x55,0x55},{0x55,0x45,0x55,0x55},{0x55,0x51,0x55,0x55},{0x55,0x54,0x55,0x55},{0x55,0x55,0x15,0x55},{0x55,0x55,0x45,0x55},{0x55,0x55,0x51,0x55},{0x55,0x55,0x54,0x55},{0xAA,0xAA,0xAA,0x2A},{0xAA,0xAA,0xAA,0x8A},{0xAA,0xAA,0xAA,0xA2},{0xAA,0xAA,0xAA,0xA8}}, + {{0x15,0x55,0x55,0x55},{0x45,0x55,0x55,0x55},{0x51,0x55,0x55,0x55},{0x54,0x55,0x55,0x55},{0x55,0x15,0x55,0x55},{0x55,0x45,0x55,0x55},{0x55,0x51,0x55,0x55},{0x55,0x54,0x55,0x55},{0x55,0x55,0x15,0x55},{0xAA,0xAA,0x8A,0xAA},{0xAA,0xAA,0xA2,0xAA},{0xAA,0xAA,0xA8,0xAA},{0xAA,0xAA,0xAA,0x2A},{0xAA,0xAA,0xAA,0x8A},{0xAA,0xAA,0xAA,0xA2},{0xAA,0xAA,0xAA,0xA8}}, + {{0x15,0x55,0x55,0x55},{0x45,0x55,0x55,0x55},{0x51,0x55,0x55,0x55},{0x54,0x55,0x55,0x55},{0x55,0x15,0x55,0x55},{0x55,0x45,0x55,0x55},{0xAA,0xA2,0xAA,0xAA},{0xAA,0xA8,0xAA,0xAA},{0xAA,0xAA,0x2A,0xAA},{0xAA,0xAA,0x8A,0xAA},{0xAA,0xAA,0xA2,0xAA},{0xAA,0xAA,0xA8,0xAA},{0xAA,0xAA,0xAA,0x2A},{0xAA,0xAA,0xAA,0x8A},{0xAA,0xAA,0xAA,0xA2},{0xAA,0xAA,0xAA,0xA8}}, + {{0x15,0x55,0x55,0x55},{0x45,0x55,0x55,0x55},{0x51,0x55,0x55,0x55},{0xA8,0xAA,0xAA,0xAA},{0xAA,0x2A,0xAA,0xAA},{0xAA,0x8A,0xAA,0xAA},{0xAA,0xA2,0xAA,0xAA},{0xAA,0xA8,0xAA,0xAA},{0xAA,0xAA,0x2A,0xAA},{0xAA,0xAA,0x8A,0xAA},{0xAA,0xAA,0xA2,0xAA},{0xAA,0xAA,0xA8,0xAA},{0xAA,0xAA,0xAA,0x2A},{0xAA,0xAA,0xAA,0x8A},{0xAA,0xAA,0xAA,0xA2},{0xAA,0xAA,0xAA,0xA8}}, + {{0x15,0x55,0x55,0x55},{0x8A,0xAA,0xAA,0xAA},{0xA2,0xAA,0xAA,0xAA},{0xA8,0xAA,0xAA,0xAA},{0xAA,0x2A,0xAA,0xAA},{0xAA,0x8A,0xAA,0xAA},{0xAA,0xA2,0xAA,0xAA},{0xAA,0xA8,0xAA,0xAA},{0xAA,0xAA,0x2A,0xAA},{0xAA,0xAA,0x8A,0xAA},{0xAA,0xAA,0xA2,0xAA},{0xAA,0xAA,0xA8,0xAA},{0xAA,0xAA,0xAA,0x2A},{0xAA,0xAA,0xAA,0x8A},{0xAA,0xAA,0xAA,0xA2},{0xAA,0xAA,0xAA,0xA8}}, + {{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,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,0x00,0x00}}, +}; + +static const EpdWaveformPhases e0470_gray8_gl16_phases = { + .phases = E0470_GRAY8_GL16_FRAMES, + .phase_times = NULL, + .luts = (const uint8_t*)&e0470_gray8_gl16_data[0], +}; +static const EpdWaveformPhases* e0470_gray8_gl16_ranges[] = { &e0470_gray8_gl16_phases }; +static const EpdWaveformMode e0470_gray8_gl16_mode = { + .type = 5, .temp_ranges = 1, .range_data = &e0470_gray8_gl16_ranges[0], +}; diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/CMakeLists.txt b/firmware/targets/mindreset_read_pico/components/epdiy/CMakeLists.txt new file mode 100755 index 0000000..37dbf06 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/CMakeLists.txt @@ -0,0 +1,45 @@ + +# 只保留 ESP32-S3 的 LCD 外设输出路径;上游的 ESP32 I2S 路径与社区波形表已裁掉。 +set(app_sources "src/epdiy.c" + "src/render.c" + "src/output_lcd/render_lcd.c" + "src/output_lcd/lcd_driver.c" + "src/output_common/rmt_compat.c" + "src/output_common/lut.c" + "src/output_common/lut.S" + "src/output_common/line_queue.c" + "src/output_common/render_context.c" + "src/font.c" + "src/displays.c" + "src/diff.S" + "src/highlevel.c" + "src/board/epd_board.c" +) + +if (${IDF_VERSION_MAJOR} LESS 5) + message(FATAL_ERROR "epdiy requires ESP-IDF 5.x or newer") +endif() + +set(epdiy_requires driver esp_timer esp_adc esp_lcd) +if (${IDF_VERSION_MAJOR} GREATER 5 OR (${IDF_VERSION_MAJOR} EQUAL 5 AND ${IDF_VERSION_MINOR} GREATER 2)) + list(APPEND epdiy_requires esp_driver_i2c) +endif() +if(DEFINED IDF_VERSION_MAJOR AND IDF_VERSION_MAJOR GREATER_EQUAL 6) + list(APPEND epdiy_requires esp_driver_gpio esp_driver_rmt esp_driver_dma) +endif() + +set(epdiy_private_requires hal soc esp_hw_support esp_rom) +if(DEFINED IDF_VERSION_MAJOR AND IDF_VERSION_MAJOR GREATER_EQUAL 6) + list(APPEND epdiy_private_requires esp_hal_dma esp_hal_rmt esp_hal_lcd) +endif() + +idf_component_register( + SRCS ${app_sources} + INCLUDE_DIRS "src/" "include" + REQUIRES ${epdiy_requires} + PRIV_REQUIRES ${epdiy_private_requires} +) + +# formatting specifiers maybe incompatible between idf versions because of different int definitions +component_compile_options(-Wno-error=format= -Wno-format) +set_source_files_properties("src/output_common/lut.c" PROPERTIES COMPILE_OPTIONS -mno-fix-esp32-psram-cache-issue) diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/LICENSE b/firmware/targets/mindreset_read_pico/components/epdiy/LICENSE new file mode 100644 index 0000000..d6d24e8 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/LICENSE @@ -0,0 +1,24 @@ +epdiy — parallel e-paper driver +Upstream: https://github.com/vroland/epdiy +License: LGPL-3.0-or-later (see idf_component.yml) + +本目录是 epdiy 的 fork,按 upstream 的 v2.0.0(组件版本号见 idf_component.yml) +裁剪而来,只保留 ESP32-S3 的 LCD 外设输出路径与本板需要的显示定义,本地改动包括: + +- 删除 ESP32 的 I2S 输出路径(src/output_i2s/)、社区波形表与示例图片 +- src/board/ 换成本板定义(引脚在 components/read_pico/read_pico_board.c 里注册) +- src/displays.c 只保留 4.7" 1216x684 单色面板(E0470_DISPLAY) +- 波形表改为从 components/e0470_epaper_waveform 取 +- src/render.c / highlevel.c:差分按列裁剪、按 (from,to) 直方图无损跳过前导空相位、 + 统计各阶段耗时;output_lcd/render_lcd.c:可配置的帧前预填行数 + +This directory is a trimmed fork of epdiy v2.0.0: only the ESP32-S3 LCD render +path and the display definition for this board are kept. Local changes are +listed above. + +- output_lcd/render_lcd.c:扫描未消费完即结束时中止相位,汇合生产者后清理行队列,防止下一相位死锁。 + Abort incomplete scan phases and clear queues after joining producers to prevent next-phase deadlock. + +The full text of the GNU Lesser General Public License version 3 is available at +https://www.gnu.org/licenses/lgpl-3.0.txt and applies to all files in this +directory unless a file states otherwise. 上游文件头部的版权声明保持原样。 diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/README.md b/firmware/targets/mindreset_read_pico/components/epdiy/README.md new file mode 100644 index 0000000..a5828e7 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/README.md @@ -0,0 +1,39 @@ +# epdiy(裁剪 fork / trimmed fork) + +小纸 Pico 使用的墨水屏渲染器。本目录是 [epdiy](https://github.com/vroland/epdiy) v2.0.0 的 fork,按本板需要裁剪到 ESP32-S3 的 LCD 外设输出路径与对应的显示定义。 + +E-paper renderer for the Read Pico board. This directory is a fork of [epdiy](https://github.com/vroland/epdiy) v2.0.0, trimmed to the ESP32-S3 LCD peripheral output path and the display definition this board needs. + +上游 / Upstream: https://github.com/vroland/epdiy +许可 / License: LGPL-3.0-or-later + +## 与上游的差异 / What changed from upstream + +- 删除 ESP32 的 I2S 输出路径(`src/output_i2s/`)、社区波形表与示例图片。/ The ESP32 I2S output path (`src/output_i2s/`), community waveform tables and example images are removed. +- `src/board/` 换成本板定义;引脚在 `components/read_pico/read_pico_board.c` 中注册。/ `src/board/` is replaced by this board's definition; pins are registered from `components/read_pico/read_pico_board.c`. +- `src/displays.c` 只保留 4.7 寸 1216 × 684 单色面板(`E0470_DISPLAY`)。/ `src/displays.c` keeps only the 4.7" 1216 × 684 monochrome panel (`E0470_DISPLAY`). +- 波形表改为从 `components/e0470_epaper_waveform` 获取,不再使用内置表。/ Waveform tables come from `components/e0470_epaper_waveform` instead of the built-in set. +- `src/render.c` / `src/highlevel.c`:差分按列裁剪,按 (from, to) 直方图无损跳过前导空相位,并记录各阶段耗时。/ Diff is cropped by column, leading empty phases are skipped losslessly using a (from, to) histogram, and per-stage timing is recorded. +- `src/output_lcd/render_lcd.c`:每帧前的预填行数可配置。/ Configurable number of pre-fill lines before each frame. + +完整清单同时保留在 [LICENSE](LICENSE) 中。 + +The full list is also kept in [LICENSE](LICENSE). + +## 公开头文件 / Public headers + +- `include/epd_lcd.h`:LCD 总线配置与像素时钟控制。/ LCD bus configuration and pixel-clock control. +- `include/epd_waveform.h`:波形组件使用的 `EpdWaveform` / `EpdWaveformPhases` 类型。/ `EpdWaveform` / `EpdWaveformPhases` types used by the waveform component. +- `src/epdiy.h`、`src/epd_highlevel.h`:epdiy 的绘制与高层刷新接口,形态与上游一致。/ The epdiy drawing and high-level update API, unchanged in shape from upstream. + +## 说明 / Notes + +本 fork 只维护到小纸 Pico 固件需要的程度。仓库其它部分的注释整理与风格统一不涉及本目录;上游文件头保持原样。 + +This fork is maintained only as far as the Read Pico firmware needs it. Comment cleanup and style passes in the rest of the repository do not touch this directory; upstream file headers are kept as they were. + +## 许可 / License + +LGPL-3.0-or-later。完整文本见 https://www.gnu.org/licenses/lgpl-3.0.txt,适用于本目录全部文件,除非某文件另有说明。 + +LGPL-3.0-or-later. The full text is at https://www.gnu.org/licenses/lgpl-3.0.txt and applies to every file in this directory unless a file states otherwise. diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/idf_component.yml b/firmware/targets/mindreset_read_pico/components/epdiy/idf_component.yml new file mode 100755 index 0000000..468e386 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/idf_component.yml @@ -0,0 +1,4 @@ +version: "2.0.0" +description: "Drive parallel e-Paper displays with epdiy-based boards." +url: "https://github.com/vroland/epdiy" +license: LGPL-3.0-or-later diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/include/epd_lcd.h b/firmware/targets/mindreset_read_pico/components/epdiy/include/epd_lcd.h new file mode 100644 index 0000000..d375275 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/include/epd_lcd.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + gpio_num_t data[16]; + gpio_num_t clock; + gpio_num_t ckv; + gpio_num_t start_pulse; + gpio_num_t leh; + gpio_num_t stv; +} lcd_bus_config_t; + +/// 一行四段 + CKV。行长 = L_SL + L_BL + L_DL + L_EL,必须在改像素钟之前写好。 +typedef struct { + int le_high_time; ///< L_SL 钟 + int line_front_porch; ///< L_BL 钟 + int line_end; ///< L_EL 钟 + int ckv_high_time; ///< CKV 高电平,0.1µs +} LcdLineTiming_t; + +typedef struct { + size_t pixel_clock; + LcdLineTiming_t line; + int bus_width; + lcd_bus_config_t bus; +} LcdEpdConfig_t; + +void epd_lcd_init(const LcdEpdConfig_t* config, int display_width, int display_height); +void epd_lcd_deinit(void); +void epd_lcd_set_pixel_clock_MHz(int frequency); +void epd_lcd_set_line_timing(const LcdLineTiming_t* timing); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/include/epd_waveform.h b/firmware/targets/mindreset_read_pico/components/epdiy/include/epd_waveform.h new file mode 100644 index 0000000..e066f30 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/include/epd_waveform.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +typedef struct { + int phases; + const uint8_t* luts; + const int* phase_times; +} EpdWaveformPhases; + +typedef struct { + uint8_t type; + uint8_t temp_ranges; + EpdWaveformPhases const** range_data; +} EpdWaveformMode; + +typedef struct { + int min; + int max; +} EpdWaveformTempInterval; + +typedef struct { + uint8_t num_modes; + uint8_t num_temp_ranges; + EpdWaveformMode const** mode_data; + EpdWaveformTempInterval const* temp_intervals; +} EpdWaveform; diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/src/board/epd_board.c b/firmware/targets/mindreset_read_pico/components/epdiy/src/board/epd_board.c new file mode 100755 index 0000000..116d12a --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/src/board/epd_board.c @@ -0,0 +1,120 @@ +#include "epd_board.h" + +#include +#include + +#include "epdiy.h" + +/** + * The board's display control pin state. + * 全局控制寄存器状态。 + * 用于保存当前 EPD 相关控制引脚的逻辑状态, + * 如 ep_output_enable, ep_mode, ep_sth, ep_stv, ep_latch_enable 等。 + * 最终会通过 epd_board->set_ctrl() 写入硬件引脚。 + */ +static epd_ctrl_state_t ctrl_state; + +/** + * The EPDIY board in use. + */ +const EpdBoardDefinition* epd_board = NULL; + +/** + * busy 等待(延时)。 + * 使用 CPU 周期计数器进行延时,不依赖 RTOS。 + * IRAM_ATTR 表示放入指令 RAM,加快执行速度。 + */ +void IRAM_ATTR epd_busy_delay(uint32_t cycles) { + volatile unsigned long counts = XTHAL_GET_CCOUNT() + cycles; + while (XTHAL_GET_CCOUNT() < counts) { + }; +} + +/** + * 设置当前使用的 EPD 板(仅能设置一次)。 + * 通常在上电初始化时由用户调用: + * + * epd_set_board(&board); + */ +void epd_set_board(const EpdBoardDefinition* board_definition) { + if (epd_board == NULL) { + epd_board = board_definition; + } else { + ESP_LOGW("epdiy", "EPD board can only be set once!"); + } +} + +/** + * 获取当前使用的 EPD 板定义。 + */ +const EpdBoardDefinition* epd_current_board() { + return epd_board; +} + +/** + * 设置 EPD “模式”位和“输出使能”位。 + * state = true 则两个信号都被设置为1。 + * + * 最后调用 epd_board->set_ctrl() 将修改同步到硬件。 + */ +void epd_set_mode(bool state) { + ctrl_state.ep_output_enable = state; + ctrl_state.ep_mode = state; + + // 掩码:表示本次写入哪些字段需要生效 + epd_ctrl_state_t mask = { + .ep_output_enable = true, + .ep_mode = true, + }; + + // 将修改写入实际硬件 + epd_board->set_ctrl(&ctrl_state, &mask); +} + +/** + * 返回控制寄存器的当前状态指针, + * 供其他模块读取或修改。 + */ +epd_ctrl_state_t* epd_ctrl_state() { + return &ctrl_state; +} + +/** + * 初始化 EPD 控制寄存器,在 EPD 初始化流程中调用。 + * 设置所有控制信号为默认值。 + */ +void epd_control_reg_init() { + ctrl_state.ep_latch_enable = false; + ctrl_state.ep_output_enable = false; + ctrl_state.ep_sth = true; + ctrl_state.ep_mode = false; + ctrl_state.ep_stv = true; + + // mask 指定哪些位需要写入 + epd_ctrl_state_t mask = { + .ep_latch_enable = true, + .ep_output_enable = true, + .ep_sth = true, + .ep_mode = true, + .ep_stv = true, + }; + + // 应用到硬件 + epd_board->set_ctrl(&ctrl_state, &mask); +} + +/** + * 在 deinit (关机) 时调用。 + * 关闭输出,使能低功耗。 + */ +void epd_control_reg_deinit() { + ctrl_state.ep_output_enable = false; + ctrl_state.ep_mode = false; + ctrl_state.ep_stv = false; + epd_ctrl_state_t mask = { + .ep_output_enable = true, + .ep_mode = true, + .ep_stv = true, + }; + epd_board->set_ctrl(&ctrl_state, &mask); +} diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/src/diff.S b/firmware/targets/mindreset_read_pico/components/epdiy/src/diff.S new file mode 100755 index 0000000..52a9228 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/src/diff.S @@ -0,0 +1,159 @@ +#include +#include +#include "sdkconfig.h" + +#ifdef CONFIG_IDF_TARGET_ESP32S3 + +.text +.align 4 +.global epd_interlace_4bpp_line_VE +.type epd_interlace_4bpp_line_VE,@function + +// // CRASH AND BURN for debugging +// EE.MOVI.32.A q3, a2, 0 +// EE.MOVI.32.A q3, a3, 1 +// EE.MOVI.32.A q3, a4, 2 +// EE.MOVI.32.A q3, a5, 3 +// l8ui a10, a10, 0 + +// bool interlace_line( +// const uint8_t *to, +// const uint8_t *from, +// uint8_t *col_dirtyness; +// uint8_t *interlaced, +// int fb_width +// ) +epd_interlace_4bpp_line_VE: +// to - a2 +// from - a3 +// interlaced - a4 +// col_dirtyness - a5 +// fb_width - a6 + + entry a1, 32 + + // divide by 32 for loop count + srli a11, a6, 5 + + movi.n a10, 0xF0F0F0F0; + EE.MOVI.32.Q q6,a10,0 + EE.MOVI.32.Q q6,a10,1 + EE.MOVI.32.Q q6,a10,2 + EE.MOVI.32.Q q6,a10,3 + + movi.n a10, 0x0F0F0F0F + EE.MOVI.32.Q q7,a10,0 + EE.MOVI.32.Q q7,a10,1 + EE.MOVI.32.Q q7,a10,2 + EE.MOVI.32.Q q7,a10,3 + + // put 4 into shift amount + movi.n a10, 4 + WSR.SAR a10 + + // "dirtyness" register + EE.ZERO.Q q5 + + // Instructions sometimes are in an unexpected order + // for best pipeline utilization + loopnez a11, .loop_end_difference + + EE.VLD.128.IP q0, a2, 16 + EE.VLD.128.IP q1, a3, 16 + + // load column dirtyness + EE.VLD.128.IP q3, a5, 0 + + // update dirtyness + EE.XORQ q4, q1, q0 + + // line dirtyness accumulator + EE.ORQ q5, q5, q4 + // column dirtyness + EE.ORQ q3, q3, q4 + + // store column dirtyness + EE.VST.128.IP q3, a5, 16 + + // mask out every second value + EE.ANDQ q2, q0, q7 + EE.ANDQ q0, q0, q6 + EE.ANDQ q3, q1, q7 + EE.ANDQ q1, q1, q6 + + // shift vectors to align + EE.VSL.32 q2, q2 + EE.VSR.32 q1, q1 + + // the right shift sign-extends, + // so we make sure the resulting shift is logical by masking again + EE.ANDQ q1, q1, q7 + + // Combine "from" and "to" nibble + EE.ORQ q2, q2, q3 + EE.ORQ q0, q0, q1 + + // Zip masked out values together + EE.VZIP.8 q2, q0 + + // store interlaced buffer data + EE.VST.128.IP q2, a4, 16 + EE.VST.128.IP q0, a4, 16 + +.loop_end_difference: + + EE.MOVI.32.A q5, a2, 0 + EE.MOVI.32.A q5, a3, 1 + EE.MOVI.32.A q5, a4, 2 + EE.MOVI.32.A q5, a5, 3 + or a2, a2, a3 + or a2, a2, a4 + or a2, a2, a5 + + //movi.n a2, 1 // return "true" + + // CRASH AND BURN for debugging + //EE.MOVI.32.A q5, a2, 0 + //EE.MOVI.32.A q5, a3, 1 + //EE.MOVI.32.A q5, a4, 2 + //EE.MOVI.32.A q5, a5, 3 + //movi.n a10, 0 + //l8ui a10, a10, 0 + + retw.n + + +.global epd_apply_line_mask_VE +.type epd_apply_line_mask_VE,@function + +// void epd_apply_line_mask_VE( +// uint8_t *line, +// const uint8_t *mask, +// int mask_len +// ) +epd_apply_line_mask_VE: +// line - a2 +// mask - a3 +// mask_len - a4 + + entry a1, 32 + + // divide by 16 for loop count + srli a4, a4, 4 + + // Instructions sometimes are in an unexpected order + // for best pipeline utilization + loopnez a4, .loop_end_mask + + EE.VLD.128.IP q0, a2, 0 + EE.VLD.128.IP q1, a3, 16 + + EE.ANDQ q0, q0, q1 + + EE.VST.128.IP q0, a2, 16 + +.loop_end_mask: + + retw.n + +#endif \ No newline at end of file diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/src/displays.c b/firmware/targets/mindreset_read_pico/components/epdiy/src/displays.c new file mode 100755 index 0000000..4db6606 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/src/displays.c @@ -0,0 +1,13 @@ +#include + +#include "epd_display.h" + +// E0470A01 / 684×1216 40pin。总线按本板 16bit,不是原厂 demo 板的 8bit/20MHz。 +// 波形在应用侧 epd_hl_init(&E0470_WAVEFORM),这里不挂社区 LUT。 +const EpdDisplay_t E0470_DISPLAY = { + .width = 1216, + .height = 684, + .bus_width = 16, + .bus_speed = 24, + .default_waveform = NULL, +}; diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/src/epd_board.h b/firmware/targets/mindreset_read_pico/components/epdiy/src/epd_board.h new file mode 100755 index 0000000..e468b32 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/src/epd_board.h @@ -0,0 +1,122 @@ +/** + * @file "epd_board.h" + * @brief Board-definitions provided by epdiy. + */ + +#pragma once + +#include +#include + +#include +#include + +/** + * State of display control pins. + */ +typedef struct { + bool ep_latch_enable : 1; + bool ep_output_enable : 1; + bool ep_sth : 1; + bool ep_mode : 1; + bool ep_stv : 1; +} epd_ctrl_state_t; + +/** + * Operations available on an epdiy board. + */ +typedef struct { + /** + * Initialize the board. + */ + void (*init)(uint32_t epd_row_width); + /** + * Clean up resources and peripherals used by the board. + */ + void (*deinit)(void); + /** + * Set display line state + */ + void (*set_ctrl)(epd_ctrl_state_t*, const epd_ctrl_state_t* const); + /** + * Enable power to the display. + */ + void (*poweron)(epd_ctrl_state_t*); + + /** + * Measure VCOM kick-back. Only in v6 & v7 boards! + */ + void (*measure_vcom)(epd_ctrl_state_t* state); + + /** + * Disable power to the display. + */ + void (*poweroff)(epd_ctrl_state_t*); + + /** + * Set the display common voltage if supported. + * + * Voltage is set as absolute value in millivolts. + * Although VCOM is negative, this function takes a positive (absolute) value. + */ + void (*set_vcom)(int); + + /** + * Get the current temperature if supported by the board. + */ + float (*get_temperature)(void); + + /** + * Set GPIO direction of the broken-out GPIO extender port, + * if available. + * Setting `make_input` to `1` corresponds to input, `0` corresponds to output. + */ + esp_err_t (*gpio_set_direction)(int pin, bool make_input); + + /** + * Get the input level of a GPIO extender pin, if available. + */ + bool (*gpio_read)(int pin); + + /** + * Set the output level of a GPIO extender, if available. + */ + esp_err_t (*gpio_write)(int pin, bool value); +} EpdBoardDefinition; + +/** + * Get the current board. + */ +const EpdBoardDefinition* epd_current_board(); + +/** + * Set the board hardware definition. This must be called before epd_init() + * + * The implementation of this method is in board/epd_board.c. + **/ +void epd_set_board(const EpdBoardDefinition* board); + +/** + * Get the board's current control register state. + */ +epd_ctrl_state_t* epd_ctrl_state(); + +/** + * Set the display mode pin. + */ +void epd_set_mode(bool state); + +/** + * Initialize the control register + */ +void epd_control_reg_init(); + +/** + * Put the control register into the state of lowest power consumption. + */ +void epd_control_reg_deinit(); + +/** + * Helper for short, precise delays. + */ +void epd_busy_delay(uint32_t cycles); diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/src/epd_display.h b/firmware/targets/mindreset_read_pico/components/epdiy/src/epd_display.h new file mode 100755 index 0000000..a412b18 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/src/epd_display.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include "epd_internals.h" + +typedef struct { + /// Width of the display in pixels. + int width; + /// Height of the display in pixels. + int height; + + /// Width of the data bus in bits. + uint8_t bus_width; + /// Speed of the data bus in MHz, if configurable. + /// (Only used by the LCD based renderer in V7+) + int bus_speed; + + /// Default waveform to use. + const EpdWaveform* default_waveform; +} EpdDisplay_t; + +extern const EpdDisplay_t E0470_DISPLAY; \ No newline at end of file diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/src/epd_highlevel.h b/firmware/targets/mindreset_read_pico/components/epdiy/src/epd_highlevel.h new file mode 100755 index 0000000..85f3a15 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/src/epd_highlevel.h @@ -0,0 +1,177 @@ +/** + * @file "epd_highlevel.h" + * @brief High-level API for drawing to e-paper displays. + * + * The functions in this library provide a simple way to manage updates of e-paper display. + * To use it, follow the steps below: + * + * 1. First, we declare a global object that manages the display state. + * + * EpdiyHighlevelState hl; + * + * 2. Then, the driver and framebuffers must be initialized. + * + * epd_init(EPD_LUT_1K); + * hl = epd_hl_init(EPD_BUILTIN_WAVEFORM); + * + * 3. Now, we can draw to the allocated framebuffer, + * using the draw and text functions defined in `epdiy.h`. + * This will not yet update the display, but only its representation in memory. + * + * // A reference to the framebuffer + * uint8_t* fb = epd_hl_get_framebuffer(&hl); + * + * // draw a black rectangle + * EpdRect some_rect = { + * .x = 100, + * .y = 100, + * .width = 100, + * .height = 100 + * }; + * epd_fill_rect(some_rect, 0x0, fb); + * + * // write a message + * int cursor_x = 100; + * int cursor_y = 300; + * epd_write_default(&FiraSans, "Hello, World!", &cursor_x, &cursor_y, fb); + * + * // finally, update the display! + * int temperature = 25; + * epd_poweron(); + * EpdDrawError err = epd_hl_update_screen(&hl, MODE_GC16, temperature); + * epd_poweroff(); + * + * That's it! For many application, this will be enough. + * For special applications and requirements, have a + * closer look at the `epdiy.h` header. + * + * Colors + * ====== + * + * Since most displays only support 16 colors, we're only using the upper 4 bits (nibble) of a byte + * to detect the color. + * + * char pixel_color = color & 0xF0; + * + * So keep in mind, when passing a color to any function, to always set the upper 4 bits, otherwise + * the color would be black. + * + * Possible colors are `0xF0` (white) through `0x80` (median gray) til `0x00` (black). + */ + +#ifdef __cplusplus +extern "C" { + +#endif +#pragma once +#include +#include "epdiy.h" + +#define EPD_BUILTIN_WAVEFORM NULL + +/// Holds the internal state of the high-level API. +typedef struct { + /// The "front" framebuffer object. + uint8_t* front_fb; + /// The "back" framebuffer object. + uint8_t* back_fb; + /// Buffer for holding the interlaced difference image. + uint8_t* difference_fb; + /// Tainted lines based on the last difference calculation. + bool* dirty_lines; + /// Tainted column nibbles based on the last difference calculation. + uint8_t* dirty_columns; + /// The waveform information to use. + const EpdWaveform* waveform; +} EpdiyHighlevelState; + +/** + * Initialize a state object. + * This allocates two framebuffers and an update buffer for + * the display in the external PSRAM. + * In order to keep things simple, a chip reset is triggered if this fails. + * + * @param waveform: The waveform to use for updates. + * If you did not create your own, this will be `EPD_BUILTIN_WAVEFORM`. + * @returns An initialized state object. + */ +EpdiyHighlevelState epd_hl_init(const EpdWaveform* waveform); + +/// Get a reference to the front framebuffer. +/// Use this to draw on the framebuffer before updating the screen with `epd_hl_update_screen()`. +uint8_t* epd_hl_get_framebuffer(EpdiyHighlevelState* state); + +/** + * Update the EPD screen to match the content of the front frame buffer. + * Prior to this, power to the display must be enabled via `epd_poweron()` + * and should be disabled afterwards if no immediate additional updates follow. + * + * @param state: A reference to the `EpdiyHighlevelState` object used. + * @param mode: The update mode to use. + * Additional mode settings like the framebuffer format or + * previous display state are determined by the driver and must not be supplied here. + * In most cases, one of `MODE_GC16` and `MODE_GL16` should be used. + * @param temperature: Environmental temperature of the display in °C. + * @returns `EPD_DRAW_SUCCESS` on sucess, a combination of error flags otherwise. + */ +enum EpdDrawError epd_hl_update_screen( + EpdiyHighlevelState* state, enum EpdDrawMode mode, int temperature +); + +/// 强制整屏脏行/脏列,16 灰阶全刷,不因差分跳过未变化像素。 +enum EpdDrawError epd_hl_update_screen_full( + EpdiyHighlevelState* state, enum EpdDrawMode mode, int temperature +); + +/// 把参考帧当成全白再 GC16 全刷,适合清屏后出图。 +enum EpdDrawError epd_hl_update_screen_from_white( + EpdiyHighlevelState* state, enum EpdDrawMode mode, int temperature +); + +/** + * Update an area of the screen to match the content of the front framebuffer. + * Supplying a small area to update can speed up the update process. + * Prior to this, power to the display must be enabled via `epd_poweron()` + * and should be disabled afterwards if no immediate additional updates follow. + * + * @param state: A reference to the `EpdiyHighlevelState` object used. + * @param mode: See `epd_hl_update_screen()`. + * @param temperature: Environmental temperature of the display in °C. + * @param area: Area of the screen to update. + * @returns `EPD_DRAW_SUCCESS` on sucess, a combination of error flags otherwise. + */ +enum EpdDrawError epd_hl_update_area( + EpdiyHighlevelState* state, enum EpdDrawMode mode, int temperature, EpdRect area +); + +/// 区域内强制脏行/脏列,未变化的白像素也会走 LUT(GL16 白底补推用)。 +enum EpdDrawError epd_hl_update_area_full( + EpdiyHighlevelState* state, enum EpdDrawMode mode, int temperature, EpdRect area +); + +/** + * Reset the front framebuffer to a white state. + * + * @param state: A reference to the `EpdiyHighlevelState` object used. + */ +void epd_hl_set_all_white(EpdiyHighlevelState* state); + +/** + * 最近一次 epd_hl_update_* 的耗时分解(ms):算差分、扫面板、回写 back_fb。 + */ +void epd_hl_last_timing(int* diff_ms, int* draw_ms, int* copy_ms); + +/** + * Bring the display to a fully white state and get rid of any + * remaining artifacts. + */ +void epd_fullclear(EpdiyHighlevelState* state, int temperature); + +/** + * Just changes the used Waveform + */ +void epd_hl_waveform(EpdiyHighlevelState* state, const EpdWaveform* waveform); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/src/epd_internals.h b/firmware/targets/mindreset_read_pico/components/epdiy/src/epd_internals.h new file mode 100755 index 0000000..b5a0956 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/src/epd_internals.h @@ -0,0 +1,53 @@ +/** + * @file epd_internals.h + * @brief Internal definitions and auxiliary data types. + * + * Unless you want to extend the library itself (Which you are very welcome to do), + * you will most likely not need to know about this file. + */ + +#ifndef EPD_INTERNALS_H +#define EPD_INTERNALS_H + +#include +#include +#include "epd_waveform.h" + +/// minimal draw time in ms for a frame layer, +/// which will allow all particles to set properly. +#define MINIMUM_FRAME_TIME 12 + +/// Frame draw time for monochrome mode in 1/10 us. +#define MONOCHROME_FRAME_TIME 120 + +/// Font data stored PER GLYPH +typedef struct { + uint16_t width; ///< Bitmap dimensions in pixels + uint16_t height; ///< Bitmap dimensions in pixels + uint16_t advance_x; ///< Distance to advance cursor (x axis) + int16_t left; ///< X dist from cursor pos to UL corner + int16_t top; ///< Y dist from cursor pos to UL corner + uint32_t compressed_size; ///< Size of the zlib-compressed font data. + uint32_t data_offset; ///< Pointer into EpdFont->bitmap +} EpdGlyph; + +/// Glyph interval structure +typedef struct { + uint32_t first; ///< The first unicode code point of the interval + uint32_t last; ///< The last unicode code point of the interval + uint32_t offset; ///< Index of the first code point into the glyph array +} EpdUnicodeInterval; + +/// Data stored for FONT AS A WHOLE +typedef struct { + const uint8_t* bitmap; ///< Glyph bitmaps, concatenated + const EpdGlyph* glyph; ///< Glyph array + const EpdUnicodeInterval* intervals; ///< Valid unicode intervals for this font + uint32_t interval_count; ///< Number of unicode intervals. + bool compressed; ///< Does this font use compressed glyph bitmaps? + uint16_t advance_y; ///< Newline distance (y axis) + int ascender; ///< Maximal height of a glyph above the base line + int descender; ///< Maximal height of a glyph below the base line +} EpdFont; + +#endif // EPD_INTERNALS_H diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/src/epdiy.c b/firmware/targets/mindreset_read_pico/components/epdiy/src/epdiy.c new file mode 100755 index 0000000..c04725c --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/src/epdiy.c @@ -0,0 +1,543 @@ +#include "epdiy.h" +#include "epd_board.h" +#include "epd_display.h" +#include "output_common/render_method.h" +#include "render.h" + +#include +#include +#include +#include + +// Simple x and y coordinate +typedef struct { + uint16_t x; + uint16_t y; +} Coord_xy; + +static const EpdDisplay_t* display = NULL; + +// Display rotation. Can be updated using epd_set_rotation(enum EpdRotation) +static enum EpdRotation display_rotation = EPD_ROT_LANDSCAPE; + +#ifndef _swap_int +#define _swap_int(a, b) \ + { \ + int t = a; \ + a = b; \ + b = t; \ + } +#endif + +EpdRect epd_full_screen() { + EpdRect area = { .x = 0, .y = 0, .width = epd_width(), .height = epd_height() }; + return area; +} + +void epd_clear() { + epd_clear_area(epd_full_screen()); +} + +void epd_draw_hline(int x, int y, int length, uint8_t color, uint8_t* framebuffer) { + for (int i = 0; i < length; i++) { + int xx = x + i; + epd_draw_pixel(xx, y, color, framebuffer); + } +} + +void epd_draw_vline(int x, int y, int length, uint8_t color, uint8_t* framebuffer) { + for (int i = 0; i < length; i++) { + int yy = y + i; + epd_draw_pixel(x, yy, color, framebuffer); + } +} + +Coord_xy _rotate(uint16_t x, uint16_t y) { + switch (display_rotation) { + case EPD_ROT_LANDSCAPE: + break; + case EPD_ROT_PORTRAIT: + _swap_int(x, y); + x = epd_width() - x - 1; + break; + case EPD_ROT_INVERTED_LANDSCAPE: + x = epd_width() - x - 1; + y = epd_height() - y - 1; + break; + case EPD_ROT_INVERTED_PORTRAIT: + _swap_int(x, y); + y = epd_height() - y - 1; + break; + } + Coord_xy coord = { x, y }; + return coord; +} + +void epd_draw_pixel(int x, int y, uint8_t color, uint8_t* framebuffer) { + // Check rotation and move pixel around if necessary + Coord_xy coord = _rotate(x, y); + x = coord.x; + y = coord.y; + + if (x < 0 || x >= epd_width()) { + return; + } + if (y < 0 || y >= epd_height()) { + return; + } + + uint8_t* buf_ptr = &framebuffer[y * epd_width() / 2 + x / 2]; + if (x % 2) { + *buf_ptr = (*buf_ptr & 0x0F) | (color & 0xF0); + } else { + *buf_ptr = (*buf_ptr & 0xF0) | (color >> 4); + } +} + +void epd_draw_circle(int x0, int y0, int r, uint8_t color, uint8_t* framebuffer) { + int f = 1 - r; + int ddF_x = 1; + int ddF_y = -2 * r; + int x = 0; + int y = r; + + epd_draw_pixel(x0, y0 + r, color, framebuffer); + epd_draw_pixel(x0, y0 - r, color, framebuffer); + epd_draw_pixel(x0 + r, y0, color, framebuffer); + epd_draw_pixel(x0 - r, y0, color, framebuffer); + + while (x < y) { + if (f >= 0) { + y--; + ddF_y += 2; + f += ddF_y; + } + x++; + ddF_x += 2; + f += ddF_x; + + epd_draw_pixel(x0 + x, y0 + y, color, framebuffer); + epd_draw_pixel(x0 - x, y0 + y, color, framebuffer); + epd_draw_pixel(x0 + x, y0 - y, color, framebuffer); + epd_draw_pixel(x0 - x, y0 - y, color, framebuffer); + epd_draw_pixel(x0 + y, y0 + x, color, framebuffer); + epd_draw_pixel(x0 - y, y0 + x, color, framebuffer); + epd_draw_pixel(x0 + y, y0 - x, color, framebuffer); + epd_draw_pixel(x0 - y, y0 - x, color, framebuffer); + } +} + +void epd_fill_circle(int x0, int y0, int r, uint8_t color, uint8_t* framebuffer) { + epd_draw_vline(x0, y0 - r, 2 * r + 1, color, framebuffer); + epd_fill_circle_helper(x0, y0, r, 3, 0, color, framebuffer); +} + +void epd_fill_circle_helper( + int x0, int y0, int r, int corners, int delta, uint8_t color, uint8_t* framebuffer +) { + int f = 1 - r; + int ddF_x = 1; + int ddF_y = -2 * r; + int x = 0; + int y = r; + int px = x; + int py = y; + + delta++; // Avoid some +1's in the loop + + while (x < y) { + if (f >= 0) { + y--; + ddF_y += 2; + f += ddF_y; + } + x++; + ddF_x += 2; + f += ddF_x; + // These checks avoid double-drawing certain lines, important + // for the SSD1306 library which has an INVERT drawing mode. + if (x < (y + 1)) { + if (corners & 1) + epd_draw_vline(x0 + x, y0 - y, 2 * y + delta, color, framebuffer); + if (corners & 2) + epd_draw_vline(x0 - x, y0 - y, 2 * y + delta, color, framebuffer); + } + if (y != py) { + if (corners & 1) + epd_draw_vline(x0 + py, y0 - px, 2 * px + delta, color, framebuffer); + if (corners & 2) + epd_draw_vline(x0 - py, y0 - px, 2 * px + delta, color, framebuffer); + py = y; + } + px = x; + } +} + +void epd_draw_rect(EpdRect rect, uint8_t color, uint8_t* framebuffer) { + int x = rect.x; + int y = rect.y; + int w = rect.width; + int h = rect.height; + epd_draw_hline(x, y, w, color, framebuffer); + epd_draw_hline(x, y + h - 1, w, color, framebuffer); + epd_draw_vline(x, y, h, color, framebuffer); + epd_draw_vline(x + w - 1, y, h, color, framebuffer); +} + +void epd_fill_rect(EpdRect rect, uint8_t color, uint8_t* framebuffer) { + int x = rect.x; + int y = rect.y; + int w = rect.width; + int h = rect.height; + for (int i = y; i < y + h; i++) { + epd_draw_hline(x, i, w, color, framebuffer); + } +} + +static void epd_write_line(int x0, int y0, int x1, int y1, uint8_t color, uint8_t* framebuffer) { + int steep = abs(y1 - y0) > abs(x1 - x0); + if (steep) { + _swap_int(x0, y0); + _swap_int(x1, y1); + } + + if (x0 > x1) { + _swap_int(x0, x1); + _swap_int(y0, y1); + } + + int dx, dy; + dx = x1 - x0; + dy = abs(y1 - y0); + + int err = dx / 2; + int ystep; + + if (y0 < y1) { + ystep = 1; + } else { + ystep = -1; + } + + for (; x0 <= x1; x0++) { + if (steep) { + epd_draw_pixel(y0, x0, color, framebuffer); + } else { + epd_draw_pixel(x0, y0, color, framebuffer); + } + err -= dy; + if (err < 0) { + y0 += ystep; + err += dx; + } + } +} + +void epd_draw_line(int x0, int y0, int x1, int y1, uint8_t color, uint8_t* framebuffer) { + // Update in subclasses if desired! + if (x0 == x1) { + if (y0 > y1) + _swap_int(y0, y1); + epd_draw_vline(x0, y0, y1 - y0 + 1, color, framebuffer); + } else if (y0 == y1) { + if (x0 > x1) + _swap_int(x0, x1); + epd_draw_hline(x0, y0, x1 - x0 + 1, color, framebuffer); + } else { + epd_write_line(x0, y0, x1, y1, color, framebuffer); + } +} + +void epd_draw_triangle( + int x0, int y0, int x1, int y1, int x2, int y2, uint8_t color, uint8_t* framebuffer +) { + epd_draw_line(x0, y0, x1, y1, color, framebuffer); + epd_draw_line(x1, y1, x2, y2, color, framebuffer); + epd_draw_line(x2, y2, x0, y0, color, framebuffer); +} + +void epd_fill_triangle( + int x0, int y0, int x1, int y1, int x2, int y2, uint8_t color, uint8_t* framebuffer +) { + int a, b, y, last; + + // Sort coordinates by Y order (y2 >= y1 >= y0) + if (y0 > y1) { + _swap_int(y0, y1); + _swap_int(x0, x1); + } + if (y1 > y2) { + _swap_int(y2, y1); + _swap_int(x2, x1); + } + if (y0 > y1) { + _swap_int(y0, y1); + _swap_int(x0, x1); + } + + if (y0 == y2) { // Handle awkward all-on-same-line case as its own thing + a = b = x0; + if (x1 < a) + a = x1; + else if (x1 > b) + b = x1; + if (x2 < a) + a = x2; + else if (x2 > b) + b = x2; + epd_draw_hline(a, y0, b - a + 1, color, framebuffer); + return; + } + + int dx01 = x1 - x0, dy01 = y1 - y0, dx02 = x2 - x0, dy02 = y2 - y0, dx12 = x2 - x1, + dy12 = y2 - y1; + int32_t sa = 0, sb = 0; + + // For upper part of triangle, find scanline crossings for segments + // 0-1 and 0-2. If y1=y2 (flat-bottomed triangle), the scanline y1 + // is included here (and second loop will be skipped, avoiding a /0 + // error there), otherwise scanline y1 is skipped here and handled + // in the second loop...which also avoids a /0 error here if y0=y1 + // (flat-topped triangle). + if (y1 == y2) + last = y1; // Include y1 scanline + else + last = y1 - 1; // Skip it + + for (y = y0; y <= last; y++) { + a = x0 + sa / dy01; + b = x0 + sb / dy02; + sa += dx01; + sb += dx02; + /* longhand: + a = x0 + (x1 - x0) * (y - y0) / (y1 - y0); + b = x0 + (x2 - x0) * (y - y0) / (y2 - y0); + */ + if (a > b) + _swap_int(a, b); + epd_draw_hline(a, y, b - a + 1, color, framebuffer); + } + + // For lower part of triangle, find scanline crossings for segments + // 0-2 and 1-2. This loop is skipped if y1=y2. + sa = (int32_t)dx12 * (y - y1); + sb = (int32_t)dx02 * (y - y0); + for (; y <= y2; y++) { + a = x1 + sa / dy12; + b = x0 + sb / dy02; + sa += dx12; + sb += dx02; + /* longhand: + a = x1 + (x2 - x1) * (y - y1) / (y2 - y1); + b = x0 + (x2 - x0) * (y - y0) / (y2 - y0); + */ + if (a > b) + _swap_int(a, b); + epd_draw_hline(a, y, b - a + 1, color, framebuffer); + } +} + +void epd_copy_to_framebuffer(EpdRect image_area, const uint8_t* image_data, uint8_t* framebuffer) { + assert(framebuffer != NULL); + + for (uint32_t i = 0; i < image_area.width * image_area.height; i++) { + uint32_t value_index = i; + // for images of uneven width, + // consume an additional nibble per row. + if (image_area.width % 2) { + value_index += i / image_area.width; + } + uint8_t val = (value_index % 2) ? (image_data[value_index / 2] & 0xF0) >> 4 + : image_data[value_index / 2] & 0x0F; + + int xx = image_area.x + i % image_area.width; + if (xx < 0 || xx >= epd_width()) { + continue; + } + int yy = image_area.y + i / image_area.width; + if (yy < 0 || yy >= epd_height()) { + continue; + } + uint8_t* buf_ptr = &framebuffer[yy * epd_width() / 2 + xx / 2]; + if (xx % 2) { + *buf_ptr = (*buf_ptr & 0x0F) | (val << 4); + } else { + *buf_ptr = (*buf_ptr & 0xF0) | val; + } + } +} + +enum EpdDrawError epd_draw_image(EpdRect area, const uint8_t* data, const EpdWaveform* waveform) { + int temperature = epd_ambient_temperature(); + assert(waveform != NULL); + EpdRect no_crop = { + .x = 0, + .y = 0, + .width = 0, + .height = 0, + }; + return epd_draw_base(area, data, no_crop, EPD_MODE_DEFAULT, temperature, NULL, NULL, waveform); +} + +void epd_set_rotation(enum EpdRotation rotation) { + display_rotation = rotation; +} + +enum EpdRotation epd_get_rotation() { + return display_rotation; +} + +int epd_rotated_display_width() { + int display_width = epd_width(); + switch (display_rotation) { + case EPD_ROT_PORTRAIT: + display_width = epd_height(); + break; + case EPD_ROT_INVERTED_PORTRAIT: + display_width = epd_height(); + break; + case EPD_ROT_INVERTED_LANDSCAPE: + case EPD_ROT_LANDSCAPE: + break; + } + return display_width; +} + +int epd_rotated_display_height() { + int display_height = epd_height(); + switch (display_rotation) { + case EPD_ROT_PORTRAIT: + display_height = epd_width(); + break; + case EPD_ROT_INVERTED_PORTRAIT: + display_height = epd_width(); + break; + case EPD_ROT_INVERTED_LANDSCAPE: + case EPD_ROT_LANDSCAPE: + break; + } + return display_height; +} + +uint8_t epd_get_pixel(int x, int y, int fb_width, int fb_height, const uint8_t* framebuffer) { + if (x < 0 || x >= fb_width) { + return 0; + } + if (y < 0 || y >= fb_height) { + return 0; + } + int fb_width_bytes = fb_width / 2 + fb_width % 2; + uint8_t buf_val = framebuffer[y * fb_width_bytes + x / 2]; + if (x % 2) { + buf_val = (buf_val & 0xF0) >> 4; + } else { + buf_val = (buf_val & 0x0F); + } + + // epd_draw_pixel needs a 0 -> 255 value + return buf_val << 4; +} + +static void draw_rotated_transparent_image( + EpdRect image_area, + const uint8_t* image_buffer, + uint8_t* framebuffer, + uint8_t* transparent_color +) { + uint16_t x_offset = 0; + uint16_t y_offset = 0; + uint8_t pixel_color; + for (uint16_t y = 0; y < image_area.height; y++) { + for (uint16_t x = 0; x < image_area.width; x++) { + x_offset = image_area.x + x; + y_offset = image_area.y + y; + if (x_offset >= epd_rotated_display_width()) + continue; + if (y_offset >= epd_rotated_display_height()) + continue; + pixel_color = epd_get_pixel(x, y, image_area.width, image_area.height, image_buffer); + if (transparent_color == NULL || pixel_color != (*transparent_color)) + epd_draw_pixel(x_offset, y_offset, pixel_color, framebuffer); + } + } +} + +void epd_draw_rotated_transparent_image( + EpdRect image_area, const uint8_t* image_buffer, uint8_t* framebuffer, uint8_t transparent_color +) { + draw_rotated_transparent_image(image_area, image_buffer, framebuffer, &transparent_color); +} + +void epd_draw_rotated_image(EpdRect image_area, const uint8_t* image_buffer, uint8_t* framebuffer) { + if (epd_get_rotation() != EPD_ROT_LANDSCAPE) { + draw_rotated_transparent_image(image_area, image_buffer, framebuffer, NULL); + } else { + epd_copy_to_framebuffer(image_area, image_buffer, framebuffer); + } +} + +void epd_poweron() { + epd_current_board()->poweron(epd_ctrl_state()); +} + +void epd_poweroff() { + epd_current_board()->poweroff(epd_ctrl_state()); +} + +void epd_init(const EpdBoardDefinition* board, const EpdDisplay_t* disp, enum EpdInitOptions options) { + display = disp; + epd_set_board(board); + epd_renderer_init(options); +} + +void epd_deinit() { + epd_renderer_deinit(); +} + +float epd_ambient_temperature() { + if (!epd_current_board()) { + ESP_LOGE("epdiy", "Could not read temperature: board not set!"); + return 0.0; + } + + if (!epd_current_board()->get_temperature) { + ESP_LOGW("epdiy", "No ambient temperature sensor - returning 21C"); + return 21.0; + } + return epd_current_board()->get_temperature(); +} + +void epd_set_vcom(uint16_t vcom) { + if (!epd_current_board()) { + ESP_LOGE("epdiy", "Could not set VCOM: board not set!"); + return; + } + + if (!epd_current_board()->set_vcom) { + ESP_LOGE("epdiy", "Could not set VCOM: board does not support setting VCOM in software."); + return; + } + return epd_current_board()->set_vcom(vcom); +} + +const EpdDisplay_t* epd_get_display() { + assert(display != NULL); + return display; +} + +int epd_width() { + return display->width; +} + +int epd_height() { + return display->height; +} + +void epd_set_lcd_pixel_clock_MHz(int frequency) { +#ifdef RENDER_METHOD_LCD + void epd_lcd_set_pixel_clock_MHz(int frequency); + epd_lcd_set_pixel_clock_MHz(frequency); +#else + ESP_LOGW("epdiy", "called set_lcd_pixel_clock_MHz, but LCD driver is not used!"); +#endif +} diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/src/epdiy.h b/firmware/targets/mindreset_read_pico/components/epdiy/src/epdiy.h new file mode 100755 index 0000000..8ce13fb --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/src/epdiy.h @@ -0,0 +1,638 @@ +/** + * @file epdiy.h + * A driver library for drawing to an EPD. + */ +#include "epd_display.h" +#ifdef __cplusplus +extern "C" { +#endif + +#pragma once +#include +#include +#include + +#include "epd_internals.h" + +/// An area on the display. +typedef struct { + /// Horizontal position. + int x; + /// Vertical position. + int y; + /// Area / image width, must be positive. + int width; + /// Area / image height, must be positive. + int height; +} EpdRect; + +/// Global EPD driver options. +enum EpdInitOptions { + /// Use the default options. + EPD_OPTIONS_DEFAULT = 0, + /// Use a small look-up table of 1024 bytes. + /// The EPD driver will use less space, but performance may be worse. + EPD_LUT_1K = 1, + /// Use a 64K lookup table. (default) + /// Best performance, but permanently occupies a 64k block of internal memory. + EPD_LUT_64K = 2, + + /// Use a small feed queue of 8 display lines. + /// This uses less memory, but may impact performance. + EPD_FEED_QUEUE_8 = 4, + /// Use a feed queue of 32 display lines. (default) + /// Best performance, but larger memory footprint. + EPD_FEED_QUEUE_32 = 8, +}; + +/// The image drawing mode. +enum EpdDrawMode { + /// An init waveform. + /// This is currently unused, use `epd_clear()` instead. + MODE_INIT = 0x0, + /// Direct Update: Go from any color to black for white only. + MODE_DU = 0x1, + /// Go from any grayscale value to another with a flashing update. + MODE_GC16 = 0x2, + /// Faster version of `MODE_GC16`. + /// Not available with default epdiy waveforms. + MODE_GC16_FAST = 0x3, + /// Animation Mode: Fast, monochrom updates. + /// Not available with default epdiy waveforms. + MODE_A2 = 0x4, + /// Go from any grayscale value to another with a non-flashing update. + MODE_GL16 = 0x5, + /// Faster version of `MODE_GL16`. + /// Not available with default epdiy waveforms. + MODE_GL16_FAST = 0x6, + /// A 4-grayscale version of `MODE_DU`. + /// Not available with default epdiy waveforms. + MODE_DU4 = 0x7, + /// Arbitrary transitions for 4 grayscale values. + /// Not available with default epdiy waveforms. + MODE_GL4 = 0xA, + /// Not available with default epdiy waveforms. + MODE_GL16_INV = 0xB, + + /// Go from a white screen to arbitrary grayscale, quickly. + /// Exclusively available with epdiy waveforms. + MODE_EPDIY_WHITE_TO_GL16 = 0x10, + /// Go from a black screen to arbitrary grayscale, quickly. + /// Exclusively available with epdiy waveforms. + MODE_EPDIY_BLACK_TO_GL16 = 0x11, + + /// Monochrome mode. Only supported with 1bpp buffers. + MODE_EPDIY_MONOCHROME = 0x20, + + MODE_UNKNOWN_WAVEFORM = 0x3F, + + // Framebuffer packing modes + /// 1 bit-per-pixel framebuffer with 0 = black, 1 = white. + /// MSB is left is the leftmost pixel, LSB the rightmost pixel. + MODE_PACKING_8PPB = 0x40, + /// 4 bit-per pixel framebuffer with 0x0 = black, 0xF = white. + /// The upper nibble corresponds to the left pixel. + /// A byte cannot wrap over multiple rows, images of uneven width + /// must add a padding nibble per line. + MODE_PACKING_2PPB = 0x80, + /// A difference image with one pixel per byte. + /// The upper nibble marks the "from" color, + /// the lower nibble the "to" color. + MODE_PACKING_1PPB_DIFFERENCE = 0x100, + // reserver for 4PPB mode + + /// Assert that the display has a uniform color, e.g. after initialization. + /// If `MODE_PACKING_2PPB` is specified, a optimized output calculation can be used. + /// Draw on a white background + PREVIOUSLY_WHITE = 0x200, + /// See `PREVIOUSLY_WHITE`. + /// Draw on a black background + PREVIOUSLY_BLACK = 0x400, + + /// Enforce NOT using S3 Vector extensions. + /// USed for testing. + MODE_FORCE_NO_PIE = 0x800, +}; + +/** Display software rotation. + * Sets the rotation for the purposes of the drawing and font functions + * Use epd_set_rotation(EPD_ROT_*) to set it using one of the options below + * Use epd_get_rotation() in case you need to read this value + */ +enum EpdRotation { + EPD_ROT_LANDSCAPE = 0, + EPD_ROT_PORTRAIT = 1, + EPD_ROT_INVERTED_LANDSCAPE = 2, + EPD_ROT_INVERTED_PORTRAIT = 3, +}; + +/// Possible failures when drawing. +enum EpdDrawError { + EPD_DRAW_SUCCESS = 0x0, + /// No valid framebuffer packing mode was specified. + EPD_DRAW_INVALID_PACKING_MODE = 0x1, + + /// No lookup table implementation for this mode / packing. + EPD_DRAW_LOOKUP_NOT_IMPLEMENTED = 0x2, + + /// The string to draw is invalid. + EPD_DRAW_STRING_INVALID = 0x4, + + /// The string was not empty, but no characters where drawable. + EPD_DRAW_NO_DRAWABLE_CHARACTERS = 0x8, + + /// Allocation failed + EPD_DRAW_FAILED_ALLOC = 0x10, + + /// A glyph could not be drawn, and not fallback was present. + EPD_DRAW_GLYPH_FALLBACK_FAILED = 0x20, + + /// The specified crop area is invalid. + EPD_DRAW_INVALID_CROP = 0x40, + + /// No such mode is available with the current waveform. + EPD_DRAW_MODE_NOT_FOUND = 0x80, + + /// The waveform info file contains no applicable temperature range. + EPD_DRAW_NO_PHASES_AVAILABLE = 0x100, + + /// An invalid combination of font flags was used. + EPD_DRAW_INVALID_FONT_FLAGS = 0x200, + + /// The waveform lookup could not keep up with the display output. + /// + /// Reduce the display clock speed. + EPD_DRAW_EMPTY_LINE_QUEUE = 0x400, +}; + +/// The default draw mode (non-flashy refresh, whith previously white screen). +#define EPD_MODE_DEFAULT (MODE_GL16 | PREVIOUSLY_WHITE) + +/// Font drawing flags +enum EpdFontFlags { + /// Draw a background. + /// + /// Take the background into account + /// when calculating the size. + EPD_DRAW_BACKGROUND = 0x1, + + /// Left-Align lines + EPD_DRAW_ALIGN_LEFT = 0x2, + /// Right-align lines + EPD_DRAW_ALIGN_RIGHT = 0x4, + /// Center-align lines + EPD_DRAW_ALIGN_CENTER = 0x8, +}; + +/// Font properties. +typedef struct { + /// Foreground color + uint8_t fg_color : 4; + /// Background color + uint8_t bg_color : 4; + /// Use the glyph for this codepoint for missing glyphs. + uint32_t fallback_glyph; + /// Additional flags, reserved for future use + enum EpdFontFlags flags; +} EpdFontProperties; + +#include "epd_board.h" +#include "epd_display.h" +#include "epd_highlevel.h" + +/** Initialize the ePaper display */ +void epd_init( + const EpdBoardDefinition* board, const EpdDisplay_t* display, enum EpdInitOptions options +); + +/** + * Get the configured display. + */ +const EpdDisplay_t* epd_get_display(); + +/** + * Get the EPD display's witdth. + */ +int epd_width(); + +/** + * Get the EPD display's height. + */ +int epd_height(); + +/** + * Set the display common voltage if supported. + * + * Voltage is set as absolute value in millivolts. + * Although VCOM is negative, this function takes a positive (absolute) value. + */ +void epd_set_vcom(uint16_t vcom); + +/** + * Get the current ambient temperature in °C, + * if the board has a sensor. + */ +float epd_ambient_temperature(); + +/** Get the display rotation value */ +enum EpdRotation epd_get_rotation(); + +/** Set the display rotation: Affects the drawing and font functions */ +void epd_set_rotation(enum EpdRotation rotation); + +/** Get screen width after rotation */ +int epd_rotated_display_width(); + +/** Get screen height after rotation */ +int epd_rotated_display_height(); + +/** Deinit the ePaper display */ +void epd_deinit(); + +/** Enable display power supply. */ +void epd_poweron(); + +/** Disable display power supply. */ +void epd_poweroff(); + +/** Clear the whole screen by flashing it. */ +void epd_clear(); + +/** + * Clear an area by flashing it. + * + * @param area: The area to clear. + */ +void epd_clear_area(EpdRect area); + +/** + * Clear an area by flashing it. + * + * @param area: The area to clear. + * @param cycles: The number of black-to-white clear cycles. + * @param cycle_time: Length of a cycle. Default: 50 (us). + */ +void epd_clear_area_cycles(EpdRect area, int cycles, int cycle_time); + +/** + * @returns Rectancle representing the whole screen area. + */ +EpdRect epd_full_screen(); + +/** + * Draw a picture to a given framebuffer. + * + * @param image_area: The area to copy to. `width` and `height` of the area + * must correspond to the image dimensions in pixels. + * @param image_data: The image data, as a buffer of 4 bit wide brightness + * values. Pixel data is packed (two pixels per byte). A byte cannot wrap over + * multiple rows, images of uneven width must add a padding nibble per line. + * @param framebuffer: The framebuffer object, + * which must be `epd_width() / 2 * epd_height()` large. + */ +void epd_copy_to_framebuffer(EpdRect image_area, const uint8_t* image_data, uint8_t* framebuffer); + +/** + * Draw a pixel a given framebuffer. + * + * @param x: Horizontal position in pixels. + * @param y: Vertical position in pixels. + * @param color: The gray value of the line (see [Colors](#Colors)); + * @param framebuffer: The framebuffer to draw to, + */ +void epd_draw_pixel(int x, int y, uint8_t color, uint8_t* framebuffer); + +/** + * Draw a horizontal line to a given framebuffer. + * + * @param x: Horizontal start position in pixels. + * @param y: Vertical start position in pixels. + * @param length: Length of the line in pixels. + * @param color: The gray value of the line (see [Colors](#Colors)); + * @param framebuffer: The framebuffer to draw to, + * which must be `epd_width() / 2 * epd_height()` bytes large. + */ +void epd_draw_hline(int x, int y, int length, uint8_t color, uint8_t* framebuffer); + +/** + * Draw a horizontal line to a given framebuffer. + * + * @param x: Horizontal start position in pixels. + * @param y: Vertical start position in pixels. + * @param length: Length of the line in pixels. + * @param color: The gray value of the line (see [Colors](#Colors)); + * @param framebuffer: The framebuffer to draw to, + * which must be `epd_width() / 2 * epd_height()` bytes large. + */ +void epd_draw_vline(int x, int y, int length, uint8_t color, uint8_t* framebuffer); + +void epd_fill_circle_helper( + int x0, int y0, int r, int corners, int delta, uint8_t color, uint8_t* framebuffer +); + +/** + * Draw a circle with given center and radius + * + * @param x: Center-point x coordinate + * @param y: Center-point y coordinate + * @param r: Radius of the circle in pixels + * @param color: The gray value of the line (see [Colors](#Colors)); + * @param framebuffer: The framebuffer to draw to, + */ +void epd_draw_circle(int x, int y, int r, uint8_t color, uint8_t* framebuffer); + +/** + * Draw a circle with fill with given center and radius + * + * @param x: Center-point x coordinate + * @param y: Center-point y coordinate + * @param r: Radius of the circle in pixels + * @param color: The gray value of the line (see [Colors](#Colors)); + * @param framebuffer: The framebuffer to draw to, + */ +void epd_fill_circle(int x, int y, int r, uint8_t color, uint8_t* framebuffer); + +/** + * Draw a rectanle with no fill color + * + * @param rect: The rectangle to draw. + * @param color: The gray value of the line (see [Colors](#Colors)); + * @param framebuffer: The framebuffer to draw to, + */ +void epd_draw_rect(EpdRect rect, uint8_t color, uint8_t* framebuffer); + +/** + * Draw a rectanle with fill color + * + * @param rect: The rectangle to fill. + * @param color: The gray value of the line (see [Colors](#Colors)); + * @param framebuffer: The framebuffer to draw to, + */ +void epd_fill_rect(EpdRect rect, uint8_t color, uint8_t* framebuffer); + +/** + * Draw a line + * + * @param x0 Start point x coordinate + * @param y0 Start point y coordinate + * @param x1 End point x coordinate + * @param y1 End point y coordinate + * @param color: The gray value of the line (see [Colors](#Colors)); + * @param framebuffer: The framebuffer to draw to, + */ +void epd_draw_line(int x0, int y0, int x1, int y1, uint8_t color, uint8_t* framebuffer); + +/** + * Draw a triangle with no fill color + * + * @param x0 Vertex #0 x coordinate + * @param y0 Vertex #0 y coordinate + * @param x1 Vertex #1 x coordinate + * @param y1 Vertex #1 y coordinate + * @param x2 Vertex #2 x coordinate + * @param y2 Vertex #2 y coordinate + * @param color: The gray value of the line (see [Colors](#Colors)); + * @param framebuffer: The framebuffer to draw to, + */ +void epd_draw_triangle( + int x0, int y0, int x1, int y1, int x2, int y2, uint8_t color, uint8_t* framebuffer +); + +/** + * Draw a triangle with color-fill + * + * @param x0 Vertex #0 x coordinate + * @param y0 Vertex #0 y coordinate + * @param x1 Vertex #1 x coordinate + * @param y1 Vertex #1 y coordinate + * @param x2 Vertex #2 x coordinate + * @param y2 Vertex #2 y coordinate + * @param color: The gray value of the line (see [Colors](#Colors)); + * @param framebuffer: The framebuffer to draw to, + */ +void epd_fill_triangle( + int x0, int y0, int x1, int y1, int x2, int y2, uint8_t color, uint8_t* framebuffer +); +/** + * Get the current ambient temperature in °C, if supported by the board. + * Requires the display to be powered on. + */ +float epd_ambient_temperature(); + +/** + * The default font properties. + */ +EpdFontProperties epd_font_properties_default(); + +/*! + * Get the text bounds for string, when drawn at (x, y). + * Set font properties to NULL to use the defaults. + */ +void epd_get_text_bounds( + const EpdFont* font, + const char* string, + const int* x, + const int* y, + int* x1, + int* y1, + int* w, + int* h, + const EpdFontProperties* props +); +/*! + * Returns a rect with the bounds of the text + * @param font : the font used to get the character sizes + * @param string: pointer to c string + * @param x : left most position of rectangle + * @param y : top most point of the rectangle + * @param margin : to be pllied to the width and height + * @returns EpdRect with x and y as per the original and height and width + * adjusted to fit the text with the margin added as well. + */ +EpdRect epd_get_string_rect( + const EpdFont* font, + const char* string, + int x, + int y, + int margin, + const EpdFontProperties* properties +); + +/** + * Write text to the EPD. + */ +enum EpdDrawError epd_write_string( + const EpdFont* font, + const char* string, + int* cursor_x, + int* cursor_y, + uint8_t* framebuffer, + const EpdFontProperties* properties +); + +/** + * Write a (multi-line) string to the EPD. + */ +enum EpdDrawError epd_write_default( + const EpdFont* font, const char* string, int* cursor_x, int* cursor_y, uint8_t* framebuffer +); + +/** + * Get the font glyph for a unicode code point. + */ +const EpdGlyph* epd_get_glyph(const EpdFont* font, uint32_t code_point); + +/** + * Darken / lighten an area for a given time. + * + * @param area: The area to darken / lighten. + * @param time: The time in us to apply voltage to each pixel. + * @param color: 1: lighten, 0: darken. + */ +void epd_push_pixels(EpdRect area, short time, int color); + +/** + * Base function for drawing an image on the screen. + * If It is very customizable, and the documentation below should be studied carefully. + * For simple applications, use the epdiy highlevel api in "epd_higlevel.h". + * + * @param area: The area of the screen to draw to. + * This can be imagined as shifting the origin of the frame buffer. + * @param data: A full framebuffer of display data. + * It's structure depends on the chosen `mode`. + * @param crop_to: Only draw a part of the frame buffer. + * Set to `epd_full_screen()` to draw the full buffer. + * @param mode: Specifies the Waveform used, the framebuffer format + * and additional information, like if the display is cleared. + * @param temperature: The temperature of the display in °C. + * Currently, this is unused by the default waveforms at can be + * set to room temperature, e.g. 20-25°C. + * @param drawn_lines: If not NULL, an array of at least the height of the + * image. Every line where the corresponding value in `lines` is `false` will be + * skipped. + * @param drawn_columns: If not NULL, an array of at least the width of the + * image / 2, 16-byte aligned. + * The image will only be updated in pixel columns where the corresponding nibbles are + * non-zero. + * @param waveform: The waveform information to use for drawing. + * If you don't have special waveforms, use `EPD_BUILTIN_WAVEFORM`. + * @returns `EPD_DRAW_SUCCESS` on sucess, a combination of error flags otherwise. + */ +enum EpdDrawError epd_draw_base( + EpdRect area, + const uint8_t* data, + EpdRect crop_to, + enum EpdDrawMode mode, + int temperature, + const bool* drawn_lines, + const uint8_t* drawn_columns, + const EpdWaveform* waveform +); + +/// 把波形第 `frame` 相展开成 1ppB / S3 VE 用的 1K 表(256 × uint32)。 +void epd_build_1ppB_lut_1k(uint8_t* lut, const EpdWaveformPhases* phases, int frame); + +/** + * 差分刷新(`MODE_PACKING_1PPB_DIFFERENCE`)时跳过前导的「全屏保持」相位。 + * + * 原厂 GC16 表里所有 (from, to) 的序列都右对齐到同一相结束,前面用保持相补齐, + * 早期几相只有极少数转换(深色到浅灰)在动。先统计这次差分图里实际出现的 + * (from, to) 对,再从表里查这些对最早在第几相开始动,之前的相位整屏都是保持, + * 直接不扫。像素收到的驱动序列一模一样,所以无损;省多少取决于画面内容。 + */ +void epd_set_leading_skip(bool enable); + +/** 最近一次 `epd_draw_base` 因上面的优化跳过的相位数。 */ +int epd_last_leading_skip(void); + +/** + * 每帧开扫前先算好的行数(LCD 输出路径)。这是帧间空档的主要来源,行队列只要跑在 + * DMA 前面就够,调小能压缩每相之间的间隙;太小会欠载并置 EPD_DRAW_EMPTY_LINE_QUEUE。 + */ +void epd_lcd_set_prefill_lines(int lines); +int epd_lcd_prefill_lines(void); + +/** 作废上一次差分顺手做的 (from, to) 统计。算了差分却没接着扫描时调,免得被后面 + * 别的绘制误用;`epd_draw_base` 自己用完也会作废。 */ +void epd_leading_skip_discard(void); + +/** + * 差分实际计算的列范围:crop_to 的列区间向外对齐到 32 像素。`epd_difference_image_cropped` + * 只在这段列里算差分、标脏列,段外像素不会被驱动,回写 back_fb 也只应回写这一段。 + */ +void epd_difference_column_range(EpdRect crop_to, int* x_start, int* x_end); + +/** + * Calculate a `MODE_PACKING_1PPB_DIFFERENCE` difference image + * from two `MODE_PACKING_2PPB` (4 bit-per-pixel) buffers. + * If you're using the epdiy highlevel api, this is handled by the update functions. + * + * @param to: The goal image as 4-bpp (`MODE_PACKING_2PPB`) framebuffer. + * @param from: The previous image as 4-bpp (`MODE_PACKING_2PPB`) framebuffer. + * @param crop_to: Only calculate the difference for a crop of the input framebuffers. + * The `interlaced` will not be modified outside the crop area. + * @param interlaced: The resulting difference image in `MODE_PACKING_1PPB_DIFFERENCE` format. + * @param dirty_lines: An array of at least `epd_height()`. + * The positions corresponding to lines where `to` and `from` differ + * are set to `true`, otherwise to `false`. + * @param col_dirtyness: An array of at least `epd_width() / 2`. + * If a nibble is set to non-zero, the pixel column is marked as changed, aka "dirty." + * The buffer must be 16 byte aligned. + * @returns The smallest rectangle containing all changed pixels. + */ +EpdRect epd_difference_image_cropped( + const uint8_t* to, + const uint8_t* from, + EpdRect crop_to, + uint8_t* interlaced, + bool* dirty_lines, + uint8_t* col_dirtiness +); + +/** + * Simplified version of `epd_difference_image_cropped()`, which considers the + * whole display frame buffer. + * + * See `epd_difference_image_cropped() for details.` + */ +EpdRect epd_difference_image( + const uint8_t* to, + const uint8_t* from, + uint8_t* interlaced, + bool* dirty_lines, + uint8_t* col_dirtiness +); + +/** + * Return the pixel color of a 4 bit image array + * x,y coordinates of the image pixel + * fb_width, fb_height dimensions + * @returns uint8_t 0-255 representing the color on given coordinates (as in epd_draw_pixel) + */ +uint8_t epd_get_pixel(int x, int y, int fb_width, int fb_height, const uint8_t* framebuffer); + +/** + * Draw an image reading pixel per pixel and being rotation aware (via epd_draw_pixel) + */ +void epd_draw_rotated_image(EpdRect image_area, const uint8_t* image_buffer, uint8_t* framebuffer); + +/** + * Draw an image reading pixel per pixel and being rotation aware (via epd_draw_pixel) + * With an optional transparent color (color key transparency) + */ +void epd_draw_rotated_transparent_image( + EpdRect image_area, const uint8_t* image_buffer, uint8_t* framebuffer, uint8_t transparent_color +); + +/** + * Override the pixel clock when using the LCD driver for display output (Epdiy V7+). + * This may result in draws failing if it's set too high! + * + * This method can be used to tune your application for maximum refresh speed, + * if you can guarantee the driver can keep up. + */ +void epd_set_lcd_pixel_clock_MHz(int frequency); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/src/font.c b/firmware/targets/mindreset_read_pico/components/epdiy/src/font.c new file mode 100755 index 0000000..39e4112 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/src/font.c @@ -0,0 +1,434 @@ +#include +#include +#include +#include + +#include "epdiy.h" + +#include +#include +#include +#include + +typedef struct { + uint8_t mask; /* char data will be bitwise AND with this */ + uint8_t lead; /* start bytes of current char in utf-8 encoded character */ + uint32_t beg; /* beginning of codepoint range */ + uint32_t end; /* end of codepoint range */ + int bits_stored; /* the number of bits from the codepoint that fits in char */ +} utf_t; + +/* + * UTF-8 decode inspired from rosetta code + * https://rosettacode.org/wiki/UTF-8_encode_and_decode#C + */ +static utf_t* utf[] = { + /* mask lead beg end bits */ + [0] = &(utf_t){ 0b00111111, 0b10000000, 0, 0, 6 }, + [1] = &(utf_t){ 0b01111111, 0b00000000, 0000, 0177, 7 }, + [2] = &(utf_t){ 0b00011111, 0b11000000, 0200, 03777, 5 }, + [3] = &(utf_t){ 0b00001111, 0b11100000, 04000, 0177777, 4 }, + [4] = &(utf_t){ 0b00000111, 0b11110000, 0200000, 04177777, 3 }, + &(utf_t){ 0 }, +}; + +static inline int min(int x, int y) { + return x < y ? x : y; +} +static inline int max(int x, int y) { + return x > y ? x : y; +} + +static int utf8_len(const uint8_t ch) { + int len = 0; + for (utf_t** u = utf; (*u)->mask; ++u) { + if ((ch & ~(*u)->mask) == (*u)->lead) { + break; + } + ++len; + } + if (len > 4) { /* Malformed leading byte */ + assert("invalid unicode."); + } + return len; +} + +static uint32_t next_cp(const uint8_t** string) { + if (**string == 0) { + return 0; + } + int bytes = utf8_len(**string); + const uint8_t* chr = *string; + *string += bytes; + int shift = utf[0]->bits_stored * (bytes - 1); + uint32_t codep = (*chr++ & utf[bytes]->mask) << shift; + + for (int i = 1; i < bytes; ++i, ++chr) { + shift -= utf[0]->bits_stored; + codep |= ((const uint8_t)*chr & utf[0]->mask) << shift; + } + + return codep; +} + +EpdFontProperties epd_font_properties_default() { + EpdFontProperties props + = { .fg_color = 0, .bg_color = 15, .fallback_glyph = 0, .flags = EPD_DRAW_ALIGN_LEFT }; + return props; +} + +const EpdGlyph* epd_get_glyph(const EpdFont* font, uint32_t code_point) { + const EpdUnicodeInterval* intervals = font->intervals; + for (int i = 0; i < font->interval_count; i++) { + const EpdUnicodeInterval* interval = &intervals[i]; + if (code_point >= interval->first && code_point <= interval->last) { + return &font->glyph[interval->offset + (code_point - interval->first)]; + } + if (code_point < interval->first) { + return NULL; + } + } + return NULL; +} + +static int uncompress( + uint8_t* dest, size_t uncompressed_size, const uint8_t* source, size_t source_size +) { + tinfl_decompressor* decomp; + if (uncompressed_size == 0 || dest == NULL || source_size == 0 || source == NULL) { + return -1; + } + decomp = malloc(sizeof(tinfl_decompressor)); + if (!decomp) { + // Out of memory + return -1; + } + tinfl_init(decomp); + + // we know everything will fit into the buffer. + tinfl_status decomp_status = tinfl_decompress( + decomp, + source, + &source_size, + dest, + dest, + &uncompressed_size, + TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF + ); + free(decomp); + if (decomp_status != TINFL_STATUS_DONE) { + return decomp_status; + } + return 0; +} + +/*! + @brief Draw a single character to a pre-allocated buffer. +*/ +static enum EpdDrawError IRAM_ATTR draw_char( + const EpdFont* font, + uint8_t* buffer, + int* cursor_x, + int cursor_y, + uint32_t cp, + const EpdFontProperties* props +) { + assert(props != NULL); + + const EpdGlyph* glyph = epd_get_glyph(font, cp); + if (!glyph) { + glyph = epd_get_glyph(font, props->fallback_glyph); + } + + if (!glyph) { + return EPD_DRAW_GLYPH_FALLBACK_FAILED; + } + + uint32_t offset = glyph->data_offset; + uint16_t width = glyph->width, height = glyph->height; + int left = glyph->left; + + int byte_width = (width / 2 + width % 2); + unsigned long bitmap_size = byte_width * height; + const uint8_t* bitmap = NULL; + if (bitmap_size > 0 && font->compressed) { + uint8_t* tmp_bitmap = (uint8_t*)malloc(bitmap_size); + if (tmp_bitmap == NULL && bitmap_size) { + ESP_LOGE("font", "malloc failed."); + return EPD_DRAW_FAILED_ALLOC; + } + uncompress(tmp_bitmap, bitmap_size, &font->bitmap[offset], glyph->compressed_size); + bitmap = tmp_bitmap; + } else { + bitmap = &font->bitmap[offset]; + } + + uint8_t color_lut[16]; + for (int c = 0; c < 16; c++) { + int color_difference = (int)props->fg_color - (int)props->bg_color; + color_lut[c] = max(0, min(15, props->bg_color + c * color_difference / 15)); + } + bool background_needed = props->flags & EPD_DRAW_BACKGROUND; + + for (int y = 0; y < height; y++) { + int yy = cursor_y - glyph->top + y; + int start_pos = *cursor_x + left; + bool byte_complete = start_pos % 2; + int x = max(0, -start_pos); + int max_x = start_pos + width; + uint8_t color; + + for (int xx = start_pos; xx < max_x; xx++) { + uint8_t bm = bitmap[y * byte_width + x / 2]; + if ((x & 1) == 0) { + bm = bm & 0xF; + } else { + bm = bm >> 4; + } + if (background_needed || bm) { + color = color_lut[bm] << 4; + epd_draw_pixel(xx, yy, color, buffer); + } + byte_complete = !byte_complete; + x++; + } + } + if (bitmap_size > 0 && font->compressed) { + free((uint8_t*)bitmap); + } + *cursor_x += glyph->advance_x; + return EPD_DRAW_SUCCESS; +} + +/*! + * @brief Calculate the bounds of a character when drawn at (x, y), move the + * cursor (*x) forward, adjust the given bounds. + */ +static void get_char_bounds( + const EpdFont* font, + uint32_t cp, + int* x, + int* y, + int* minx, + int* miny, + int* maxx, + int* maxy, + const EpdFontProperties* props +) { + assert(props != NULL); + + const EpdGlyph* glyph = epd_get_glyph(font, cp); + + if (!glyph) { + glyph = epd_get_glyph(font, props->fallback_glyph); + } + + if (!glyph) { + return; + } + + int x1 = *x + glyph->left, y1 = *y + glyph->top - glyph->height, x2 = x1 + glyph->width, + y2 = y1 + glyph->height; + + // background needs to be taken into account + if (props->flags & EPD_DRAW_BACKGROUND) { + *minx = min(*x, min(*minx, x1)); + *maxx = max(max(*x + glyph->advance_x, x2), *maxx); + *miny = min(*y + font->descender, min(*miny, y1)); + *maxy = max(*y + font->ascender, max(*maxy, y2)); + } else { + if (x1 < *minx) + *minx = x1; + if (y1 < *miny) + *miny = y1; + if (x2 > *maxx) + *maxx = x2; + if (y2 > *maxy) + *maxy = y2; + } + *x += glyph->advance_x; +} + +EpdRect epd_get_string_rect( + const EpdFont* font, + const char* string, + int x, + int y, + int margin, + const EpdFontProperties* properties +) { + assert(properties != NULL); + EpdFontProperties props = *properties; + props.flags |= EPD_DRAW_BACKGROUND; + EpdRect temp = { .x = x, .y = y, .width = 0, .height = 0 }; + if (*string == '\0') + return temp; + int minx = 100000, miny = 100000, maxx = -1, maxy = -1; + int temp_x = x; + int temp_y = y + font->ascender; + + // Go through each line and get it's co-ordinates + uint32_t c; + while ((c = next_cp((const uint8_t**)&string))) { + if (c == 0x000A) // newline + { + temp_x = x; + temp_y += font->advance_y; + } else + get_char_bounds(font, c, &temp_x, &temp_y, &minx, &miny, &maxx, &maxy, &props); + } + temp.width = maxx - x + (margin * 2); + temp.height = maxy - miny + (margin * 2); + return temp; +} + +void epd_get_text_bounds( + const EpdFont* font, + const char* string, + const int* x, + const int* y, + int* x1, + int* y1, + int* w, + int* h, + const EpdFontProperties* properties +) { + // FIXME: Does not respect alignment! + + assert(properties != NULL); + EpdFontProperties props = *properties; + + if (*string == '\0') { + *w = 0; + *h = 0; + *y1 = *y; + *x1 = *x; + return; + } + int minx = 100000, miny = 100000, maxx = -1, maxy = -1; + int original_x = *x; + int temp_x = *x; + int temp_y = *y; + uint32_t c; + while ((c = next_cp((const uint8_t**)&string))) { + get_char_bounds(font, c, &temp_x, &temp_y, &minx, &miny, &maxx, &maxy, &props); + } + *x1 = min(original_x, minx); + *w = maxx - *x1; + *y1 = miny; + *h = maxy - miny; +} + +static enum EpdDrawError epd_write_line( + const EpdFont* font, + const char* string, + int* cursor_x, + int* cursor_y, + uint8_t* framebuffer, + const EpdFontProperties* properties +) { + assert(framebuffer != NULL); + + if (*string == '\0') { + return EPD_DRAW_SUCCESS; + } + + assert(properties != NULL); + EpdFontProperties props = *properties; + enum EpdFontFlags alignment_mask + = EPD_DRAW_ALIGN_LEFT | EPD_DRAW_ALIGN_RIGHT | EPD_DRAW_ALIGN_CENTER; + enum EpdFontFlags alignment = props.flags & alignment_mask; + + // alignments are mutually exclusive! + if ((alignment & (alignment - 1)) != 0) { + return EPD_DRAW_INVALID_FONT_FLAGS; + } + + int x1 = 0, y1 = 0, w = 0, h = 0; + int tmp_cur_x = *cursor_x; + int tmp_cur_y = *cursor_y; + epd_get_text_bounds(font, string, &tmp_cur_x, &tmp_cur_y, &x1, &y1, &w, &h, &props); + + // no printable characters + if (w < 0 || h < 0) { + return EPD_DRAW_NO_DRAWABLE_CHARACTERS; + } + + uint8_t* buffer = framebuffer; + int local_cursor_x = *cursor_x; + int local_cursor_y = *cursor_y; + uint32_t c; + + int cursor_x_init = local_cursor_x; + int cursor_y_init = local_cursor_y; + + switch (alignment) { + case EPD_DRAW_ALIGN_LEFT: { + break; + } + case EPD_DRAW_ALIGN_CENTER: { + local_cursor_x -= w / 2; + break; + } + case EPD_DRAW_ALIGN_RIGHT: { + local_cursor_x -= w; + break; + } + default: + break; + } + + uint8_t bg = props.bg_color; + if (props.flags & EPD_DRAW_BACKGROUND) { + for (int l = local_cursor_y - font->ascender; l < local_cursor_y - font->descender; l++) { + epd_draw_hline(local_cursor_x, l, w, bg << 4, buffer); + } + } + enum EpdDrawError err = EPD_DRAW_SUCCESS; + while ((c = next_cp((const uint8_t**)&string))) { + err |= draw_char(font, buffer, &local_cursor_x, local_cursor_y, c, &props); + } + + *cursor_x += local_cursor_x - cursor_x_init; + *cursor_y += local_cursor_y - cursor_y_init; + return err; +} + +enum EpdDrawError epd_write_default( + const EpdFont* font, const char* string, int* cursor_x, int* cursor_y, uint8_t* framebuffer +) { + const EpdFontProperties props = epd_font_properties_default(); + return epd_write_string(font, string, cursor_x, cursor_y, framebuffer, &props); +} + +enum EpdDrawError epd_write_string( + const EpdFont* font, + const char* string, + int* cursor_x, + int* cursor_y, + uint8_t* framebuffer, + const EpdFontProperties* properties +) { + char *token, *newstring, *tofree; + if (string == NULL) { + ESP_LOGE("font.c", "cannot draw a NULL string!"); + return EPD_DRAW_STRING_INVALID; + } + tofree = newstring = strdup(string); + if (newstring == NULL) { + ESP_LOGE("font.c", "cannot allocate string copy!"); + return EPD_DRAW_FAILED_ALLOC; + } + + enum EpdDrawError err = EPD_DRAW_SUCCESS; + // taken from the strsep manpage + int line_start = *cursor_x; + while ((token = strsep(&newstring, "\n")) != NULL) { + *cursor_x = line_start; + err |= epd_write_line(font, token, cursor_x, cursor_y, framebuffer, properties); + *cursor_y += font->advance_y; + } + + free(tofree); + return err; +} diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/src/hacks.cmake b/firmware/targets/mindreset_read_pico/components/epdiy/src/hacks.cmake new file mode 100755 index 0000000..9e4964f --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/src/hacks.cmake @@ -0,0 +1,67 @@ +# +# Applies CMAKE_CXX_FLAGS to all targets in the current CMake directory. +# After this operation, CMAKE_CXX_FLAGS is cleared. +# +macro(apply_global_cxx_flags_to_all_targets) + separate_arguments(_global_cxx_flags_list UNIX_COMMAND ${CMAKE_CXX_FLAGS}) + get_property(_targets DIRECTORY PROPERTY BUILDSYSTEM_TARGETS) + foreach(_target ${_targets}) + target_compile_options(${_target} PUBLIC ${_global_cxx_flags_list}) + endforeach() + unset(CMAKE_CXX_FLAGS) + set(_flag_sync_required TRUE) +endmacro() + +# +# Removes the specified compiler flag from the specified file. +# _target - The target that _file belongs to +# _file - The file to remove the compiler flag from +# _flag - The compiler flag to remove. +# +# Pre: apply_global_cxx_flags_to_all_targets() must be invoked. +# +macro(remove_flag_from_file _target _file _flag) + get_target_property(_target_sources ${_target} SOURCES) + # Check if a sync is required, in which case we'll force a rewrite of the cache variables. + if(_flag_sync_required) + unset(_cached_${_target}_cxx_flags CACHE) + unset(_cached_${_target}_${_file}_cxx_flags CACHE) + endif() + get_target_property(_${_target}_cxx_flags ${_target} COMPILE_OPTIONS) + # On first entry, cache the target compile flags and apply them to each source file + # in the target. + if(NOT _cached_${_target}_cxx_flags) + # Obtain and cache the target compiler options, then clear them. + get_target_property(_target_cxx_flags ${_target} COMPILE_OPTIONS) + set(_cached_${_target}_cxx_flags "${_target_cxx_flags}" CACHE INTERNAL "") + set_target_properties(${_target} PROPERTIES COMPILE_OPTIONS "") + # Apply the target compile flags to each source file. + foreach(_source_file ${_target_sources}) + # Check for pre-existing flags set by set_source_files_properties(). + get_source_file_property(_source_file_cxx_flags ${_source_file} COMPILE_FLAGS) + if(_source_file_cxx_flags) + separate_arguments(_source_file_cxx_flags UNIX_COMMAND ${_source_file_cxx_flags}) + list(APPEND _source_file_cxx_flags "${_target_cxx_flags}") + else() + set(_source_file_cxx_flags "${_target_cxx_flags}") + endif() + # Apply the compile flags to the current source file. + string(REPLACE ";" " " _source_file_cxx_flags_string "${_source_file_cxx_flags}") + set_source_files_properties(${_source_file} PROPERTIES COMPILE_FLAGS "${_source_file_cxx_flags_string}") + endforeach() + endif() + list(FIND _target_sources ${_file} _file_found_at) + if(_file_found_at GREATER -1) + if(NOT _cached_${_target}_${_file}_cxx_flags) + # Cache the compile flags for the specified file. + # This is the list that we'll be removing flags from. + get_source_file_property(_source_file_cxx_flags ${_file} COMPILE_FLAGS) + separate_arguments(_source_file_cxx_flags UNIX_COMMAND ${_source_file_cxx_flags}) + set(_cached_${_target}_${_file}_cxx_flags ${_source_file_cxx_flags} CACHE INTERNAL "") + endif() + # Remove the specified flag, then re-apply the rest. + list(REMOVE_ITEM _cached_${_target}_${_file}_cxx_flags ${_flag}) + string(REPLACE ";" " " _cached_${_target}_${_file}_cxx_flags_string "${_cached_${_target}_${_file}_cxx_flags}") + set_source_files_properties(${_file} PROPERTIES COMPILE_FLAGS "${_cached_${_target}_${_file}_cxx_flags_string}") + endif() +endmacro() diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/src/highlevel.c b/firmware/targets/mindreset_read_pico/components/epdiy/src/highlevel.c new file mode 100755 index 0000000..73f4bbc --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/src/highlevel.c @@ -0,0 +1,326 @@ +/** + * High-level API implementation for epdiy. + */ + +#include +#include +#include +#include +#include +#include + +#include "epd_highlevel.h" +#include "epdiy.h" + +#ifndef _swap_int +#define _swap_int(a, b) \ + { \ + int t = a; \ + a = b; \ + b = t; \ + } +#endif + +static bool already_initialized = 0; + +EpdiyHighlevelState epd_hl_init(const EpdWaveform* waveform) { + assert(!already_initialized); + if (waveform == NULL) { + waveform = epd_get_display()->default_waveform; + } + + int fb_size = epd_width() / 2 * epd_height(); + +#if !( \ + defined(CONFIG_ESP32_SPIRAM_SUPPORT) || defined(CONFIG_ESP32S3_SPIRAM_SUPPORT) \ + || defined(CONFIG_SPIRAM) \ +) + ESP_LOGW( + "EPDiy", "Please enable PSRAM for the ESP32 (menuconfig→ Component config→ ESP32-specific)" + ); +#endif + EpdiyHighlevelState state; + state.back_fb = heap_caps_aligned_alloc(16, fb_size, MALLOC_CAP_SPIRAM); + assert(state.back_fb != NULL); + state.front_fb = heap_caps_aligned_alloc(16, fb_size, MALLOC_CAP_SPIRAM); + assert(state.front_fb != NULL); + state.difference_fb = heap_caps_aligned_alloc(16, 2 * fb_size, MALLOC_CAP_SPIRAM); + assert(state.difference_fb != NULL); + state.dirty_lines = malloc(epd_height() * sizeof(bool)); + assert(state.dirty_lines != NULL); + state.dirty_columns + = heap_caps_aligned_alloc(16, epd_width() / 2, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + assert(state.dirty_columns != NULL); + state.waveform = waveform; + + memset(state.front_fb, 0xFF, fb_size); + memset(state.back_fb, 0xFF, fb_size); + + already_initialized = true; + return state; +} + +uint8_t* epd_hl_get_framebuffer(EpdiyHighlevelState* state) { + assert(state != NULL); + return state->front_fb; +} + +// 最近一次更新的耗时分解,给上层做性能分析用。 +static int s_last_diff_ms, s_last_draw_ms, s_last_copy_ms; + +static void hl_record_timing(int diff_ms, int draw_ms, int copy_ms) { + s_last_diff_ms = diff_ms; + s_last_draw_ms = draw_ms; + s_last_copy_ms = copy_ms; +} + +void epd_hl_last_timing(int* diff_ms, int* draw_ms, int* copy_ms) { + if (diff_ms) *diff_ms = s_last_diff_ms; + if (draw_ms) *draw_ms = s_last_draw_ms; + if (copy_ms) *copy_ms = s_last_copy_ms; +} + +enum EpdDrawError epd_hl_update_screen(EpdiyHighlevelState* state, enum EpdDrawMode mode, int temperature) { + return epd_hl_update_area(state, mode, temperature, epd_full_screen()); +} + +enum EpdDrawError epd_hl_update_screen_full( + EpdiyHighlevelState* state, enum EpdDrawMode mode, int temperature +) { + assert(state != NULL); + + EpdRect area = epd_full_screen(); + uint32_t ts = esp_timer_get_time() / 1000; + + epd_difference_image_cropped( + state->front_fb, + state->back_fb, + area, + state->difference_fb, + state->dirty_lines, + state->dirty_columns + ); + + int fb_height = epd_height(); + int col_bytes = epd_width() / 2; + for (int y = 0; y < fb_height; y++) { + state->dirty_lines[y] = true; + } + memset(state->dirty_columns, 0xFF, col_bytes); + + uint32_t t1 = esp_timer_get_time() / 1000; + + EpdRect full = epd_full_screen(); + enum EpdDrawError err = epd_draw_base( + full, + state->difference_fb, + full, + MODE_PACKING_1PPB_DIFFERENCE | mode, + temperature, + state->dirty_lines, + state->dirty_columns, + state->waveform + ); + + uint32_t t2 = esp_timer_get_time() / 1000; + + memcpy(state->back_fb, state->front_fb, (size_t)col_bytes * fb_height); + + uint32_t t3 = esp_timer_get_time() / 1000; + hl_record_timing(t1 - ts, t2 - t1, t3 - t2); + ESP_LOGI( + "epdiy", + "full diff: %dms, draw: %dms, buffer update: %dms, total: %dms", + t1 - ts, + t2 - t1, + t3 - t2, + t3 - ts + ); + return err; +} + +enum EpdDrawError epd_hl_update_screen_from_white( + EpdiyHighlevelState* state, enum EpdDrawMode mode, int temperature +) { + assert(state != NULL); + memset(state->back_fb, 0xFF, (size_t)epd_width() / 2 * epd_height()); + return epd_hl_update_screen_full(state, mode, temperature); +} + +EpdRect _inverse_rotated_area(uint16_t x, uint16_t y, uint16_t w, uint16_t h) { + // If partial update uses full screen do not rotate anything + if (!(x == 0 && y == 0 && epd_width() == w && epd_height() == h)) { + // invert the current display rotation + switch (epd_get_rotation()) { + // 0 landscape: Leave it as is + case EPD_ROT_LANDSCAPE: + break; + // 1 90 ° clockwise + case EPD_ROT_PORTRAIT: + _swap_int(x, y); + _swap_int(w, h); + x = epd_width() - x - w; + break; + + case EPD_ROT_INVERTED_LANDSCAPE: + // 3 180° + x = epd_width() - x - w; + y = epd_height() - y - h; + break; + + case EPD_ROT_INVERTED_PORTRAIT: + // 3 270 ° + _swap_int(x, y); + _swap_int(w, h); + y = epd_height() - y - h; + break; + } + } + + EpdRect rotated = { x, y, w, h }; + return rotated; +} + +static enum EpdDrawError hl_update_area( + EpdiyHighlevelState* state, enum EpdDrawMode mode, int temperature, EpdRect area, bool force_full +) { + assert(state != NULL); + // Not right to rotate here since this copies part of buffer directly + + // Check rotation FIX + EpdRect rotated_area = _inverse_rotated_area(area.x, area.y, area.width, area.height); + area.x = rotated_area.x; + area.y = rotated_area.y; + area.width = rotated_area.width; + area.height = rotated_area.height; + + uint32_t ts = esp_timer_get_time() / 1000; + + // FIXME: use crop information here, if available + EpdRect diff_area = epd_difference_image_cropped( + state->front_fb, + state->back_fb, + area, + state->difference_fb, + state->dirty_lines, + state->dirty_columns + ); + + if (force_full) { + int x_start, x_stop; + epd_difference_column_range(area, &x_start, &x_stop); + const int fb_h = epd_height(); + const int y0 = area.y < 0 ? 0 : area.y; + const int y1 = area.y + area.height > fb_h ? fb_h : area.y + area.height; + for (int y = y0; y < y1; y++) state->dirty_lines[y] = true; + if (x_stop > x_start) { + memset(state->dirty_columns + x_start / 2, 0xFF, (size_t)((x_stop - x_start) / 2)); + } + diff_area.width = x_stop - x_start; + diff_area.height = y1 - y0; + } + + if (diff_area.height == 0 || diff_area.width == 0) { + epd_leading_skip_discard(); + return EPD_DRAW_SUCCESS; + } + + uint32_t t1 = esp_timer_get_time() / 1000; + + diff_area.x = 0; + diff_area.y = 0; + diff_area.width = epd_width(); + diff_area.height = epd_height(); + + enum EpdDrawError err = EPD_DRAW_SUCCESS; + err = epd_draw_base( + epd_full_screen(), + state->difference_fb, + diff_area, + MODE_PACKING_1PPB_DIFFERENCE | mode, + temperature, + state->dirty_lines, + state->dirty_columns, + state->waveform + ); + + uint32_t t2 = esp_timer_get_time() / 1000; + + // 回写范围和差分实际算过的列段一致:段外的像素没被驱动,back_fb 不能跟着改。 + int x_start, x_stop; + epd_difference_column_range(area, &x_start, &x_stop); + diff_area.x = x_start; + diff_area.y = 0; + diff_area.width = x_stop - x_start; + diff_area.height = epd_height(); + + int buf_width = epd_width(); + + for (int l = diff_area.y; diff_area.width > 0 && l < diff_area.y + diff_area.height; l++) { + if (state->dirty_lines[l] > 0) { + uint8_t* lfb = state->front_fb + buf_width / 2 * l; + uint8_t* lbb = state->back_fb + buf_width / 2 * l; + + int x = diff_area.x; + int x_last = diff_area.x + diff_area.width - 1; + + if (x % 2) { + *(lbb + x / 2) = (*(lfb + x / 2) & 0xF0) | (*(lbb + x / 2) & 0x0F); + x += 1; + } + + if (!(x_last % 2)) { + *(lbb + x_last / 2) = (*(lfb + x_last / 2) & 0x0F) | (*(lbb + x_last / 2) & 0xF0); + x_last -= 1; + } + + memcpy(lbb + (x / 2), lfb + (x / 2), (x_last - x + 1) / 2); + } + } + + uint32_t t3 = esp_timer_get_time() / 1000; + hl_record_timing(t1 - ts, t2 - t1, t3 - t2); + + ESP_LOGI( + "epdiy", + "diff: %dms, draw: %dms, buffer update: %dms, total: %dms", + t1 - ts, + t2 - t1, + t3 - t2, + t3 - ts + ); + return err; +} + +enum EpdDrawError epd_hl_update_area( + EpdiyHighlevelState* state, enum EpdDrawMode mode, int temperature, EpdRect area +) { + return hl_update_area(state, mode, temperature, area, false); +} + +enum EpdDrawError epd_hl_update_area_full( + EpdiyHighlevelState* state, enum EpdDrawMode mode, int temperature, EpdRect area +) { + return hl_update_area(state, mode, temperature, area, true); +} + +void epd_hl_set_all_white(EpdiyHighlevelState* state) { + assert(state != NULL); + int fb_size = epd_width() / 2 * epd_height(); + memset(state->front_fb, 0xFF, fb_size); +} + +void epd_fullclear(EpdiyHighlevelState* state, int temperature) { + assert(state != NULL); + epd_hl_set_all_white(state); + enum EpdDrawError err = epd_hl_update_screen(state, MODE_GC16, temperature); + assert(err == EPD_DRAW_SUCCESS); + epd_clear(); +} + +void epd_hl_waveform(EpdiyHighlevelState* state, const EpdWaveform* waveform) { + if (waveform == NULL) { + waveform = epd_get_display()->default_waveform; + } + state->waveform = waveform; +} \ No newline at end of file diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/line_queue.c b/firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/line_queue.c new file mode 100755 index 0000000..5a2dfa6 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/line_queue.c @@ -0,0 +1,86 @@ +#include +#include +#include +#include +#include +#include + +#include "line_queue.h" +#include "render_method.h" + +static inline int ceil_div(int x, int y) { + return x / y + (x % y != 0); +} + +/// Initialize the line queue and allocate memory. +LineQueue_t lq_init(int queue_len, int element_size) { + LineQueue_t queue; + queue.element_size = element_size; + queue.size = queue_len; + queue.current = 0; + queue.last = 0; + + int elem_buf_size = ceil_div(element_size, 16) * 16; + + queue.bufs = calloc(queue.size, elem_buf_size); + assert(queue.bufs != NULL); + + for (int i = 0; i < queue.size; i++) { + queue.bufs[i] = heap_caps_aligned_alloc(16, elem_buf_size, MALLOC_CAP_INTERNAL); + assert(queue.bufs[i] != NULL); + } + + return queue; +} + +/// Deinitialize the line queue and free memory. +void lq_free(LineQueue_t* queue) { + for (int i = 0; i < queue->size; i++) { + heap_caps_free(queue->bufs[i]); + } + + free(queue->bufs); +} + +uint8_t* IRAM_ATTR lq_current(LineQueue_t* queue) { + int current = atomic_load_explicit(&queue->current, memory_order_acquire); + int last = atomic_load_explicit(&queue->last, memory_order_acquire); + + if ((current + 1) % queue->size == last) { + return NULL; + } + return queue->bufs[current]; +} + +void IRAM_ATTR lq_commit(LineQueue_t* queue) { + int current = atomic_load_explicit(&queue->current, memory_order_acquire); + + if (current == queue->size - 1) { + queue->current = 0; + } else { + atomic_fetch_add(&queue->current, 1); + } +} + +int IRAM_ATTR lq_read(LineQueue_t* queue, uint8_t* dst) { + int current = atomic_load_explicit(&queue->current, memory_order_acquire); + int last = atomic_load_explicit(&queue->last, memory_order_acquire); + + if (current == last) { + return -1; + } + + memcpy(dst, queue->bufs[last], queue->element_size); + + if (last == queue->size - 1) { + queue->last = 0; + } else { + atomic_fetch_add(&queue->last, 1); + } + return 0; +} + +void IRAM_ATTR lq_reset(LineQueue_t* queue) { + queue->current = 0; + queue->last = 0; +} diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/line_queue.h b/firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/line_queue.h new file mode 100755 index 0000000..e6fa81d --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/line_queue.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include +#include + +/// Circular line queue with atomic read / write operations +/// and accelerated masking on the output buffer. +typedef struct { + int size; + atomic_int current; + atomic_int last; + uint8_t** bufs; + // size of an element + size_t element_size; +} LineQueue_t; + +/// Initialize the line queue and allocate memory. +LineQueue_t lq_init(int queue_len, int element_size); + +/// Deinitialize the line queue and free memory. +void lq_free(LineQueue_t* queue); + +/// Pointer to the next empty element in the line queue. +/// +/// NULL if the queue is currently full. +uint8_t* lq_current(LineQueue_t* queue); + +/// Advance the line queue. +void lq_commit(LineQueue_t* queue); + +/// Read from the line queue. +/// +/// Returns 0 for a successful read to `dst`, -1 for a failed read (empty queue). +int lq_read(LineQueue_t* queue, uint8_t* dst); + +/// Reset the queue into an empty state. +/// This operation is *not* atomic! +void lq_reset(LineQueue_t* queue); diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/lut.S b/firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/lut.S new file mode 100755 index 0000000..3102454 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/lut.S @@ -0,0 +1,145 @@ +#include +#include +#include "sdkconfig.h" + +#ifdef CONFIG_IDF_TARGET_ESP32S3 + +.text +.align 4 +.global calc_epd_input_1ppB_1k_S3_VE_aligned +.type calc_epd_input_1ppB_1k_S3_VE_aligned,@function + +// // CRASH AND BURN for debugging +// EE.MOVI.32.A q3, a2, 0 +// EE.MOVI.32.A q3, a3, 1 +// EE.MOVI.32.A q3, a4, 2 +// EE.MOVI.32.A q3, a5, 3 +// l8ui a10, a10, 0 + +// void calc_epd_input_1ppB_1k_S3_VE_aligned( +// const uint32_t *ld, +// uint8_t *epd_input, +// const uint8_t *conversion_lut, +// uint32_t epd_width +//); +calc_epd_input_1ppB_1k_S3_VE_aligned: +// input - a2 +// output - a3 +// lut - a4 +// len - a5 + + entry a1, 32 + + // divide by 16 and do one loop lesss, + // because the last loop is special + srli a5, a5, 4 + addi.n a5, a5, -1 + + + // bitmasks for bit shift by multiplication + movi.n a10, 0x40001000 + EE.MOVI.32.Q q4,a10,0 + movi.n a10, 0x04000100 + EE.MOVI.32.Q q4,a10,1 + movi.n a10, 0x00400010 + EE.MOVI.32.Q q4,a10,2 + movi a10, 0x00040001 + EE.MOVI.32.Q q4,a10,3 + + EE.ZERO.Q q0 + + EE.VLD.128.IP q1, a2, 16 + + // Instructions sometimes are in an unexpected order + // for best pipeline utilization + loopnez a5, .loop_end_lut_lookup + + // q1, q0 contain the input bytes, zero-extended to bits bytes + EE.VZIP.8 q1, q0 + + // load 32-bit LUT results + EE.LDXQ.32 q2, q0, a4, 0, 6 + EE.LDXQ.32 q2, q0, a4, 1, 7 + EE.LDXQ.32 q2, q0, a4, 2, 4 + EE.LDXQ.32 q2, q0, a4, 3, 5 + EE.LDXQ.32 q3, q0, a4, 0, 2 + EE.LDXQ.32 q3, q0, a4, 1, 3 + EE.LDXQ.32 q3, q0, a4, 2, 0 + EE.LDXQ.32 q3, q0, a4, 3, 1 + + EE.ZERO.ACCX + + // zip to have 16bit LUT results in q2, q3 zeroes + EE.VUNZIP.16 q2, q3 + + // combine results with using multiply-add as shift-or + EE.VMULAS.U16.ACCX q2,q4 + + // load 32-bit LUT results + EE.LDXQ.32 q2, q1, a4, 0, 6 + EE.LDXQ.32 q2, q1, a4, 1, 7 + EE.LDXQ.32 q2, q1, a4, 2, 4 + EE.LDXQ.32 q2, q1, a4, 3, 5 + EE.LDXQ.32 q0, q1, a4, 0, 2 + EE.LDXQ.32 q0, q1, a4, 1, 3 + EE.LDXQ.32 q0, q1, a4, 2, 0 + EE.LDXQ.32 q0, q1, a4, 3, 1 + + // store multiplication result in a6 + RUR.ACCX_0 a6 + s16i a6, a3, 2 + + EE.ZERO.ACCX + + // zip to have 16bit LUT results in q2, q0 zeroes + EE.VUNZIP.16 q2, q0 + + // Combine second set of results and load the next data + EE.VMULAS.U16.ACCX.LD.IP q1, a2, 16, q2, q4 + + // store result in a6 + RUR.ACCX_0 a6 + s16i a6, a3, 0 + + addi.n a3, a3, 4 +.loop_end_lut_lookup: + + // Same as above, but in the last iteration + // we do not load to not access out of bounds. + EE.VZIP.8 q1, q0 + + EE.LDXQ.32 q2, q0, a4, 0, 6 + EE.LDXQ.32 q2, q0, a4, 1, 7 + EE.LDXQ.32 q2, q0, a4, 2, 4 + EE.LDXQ.32 q2, q0, a4, 3, 5 + EE.LDXQ.32 q3, q0, a4, 0, 2 + EE.LDXQ.32 q3, q0, a4, 1, 3 + EE.LDXQ.32 q3, q0, a4, 2, 0 + EE.LDXQ.32 q3, q0, a4, 3, 1 + + EE.ZERO.ACCX + EE.VUNZIP.16 q2, q3 + EE.VMULAS.U16.ACCX q2,q4 + + EE.LDXQ.32 q2, q1, a4, 0, 6 + EE.LDXQ.32 q2, q1, a4, 1, 7 + EE.LDXQ.32 q2, q1, a4, 2, 4 + EE.LDXQ.32 q2, q1, a4, 3, 5 + EE.LDXQ.32 q0, q1, a4, 0, 2 + EE.LDXQ.32 q0, q1, a4, 1, 3 + EE.LDXQ.32 q0, q1, a4, 2, 0 + EE.LDXQ.32 q0, q1, a4, 3, 1 + + RUR.ACCX_0 a6 + s16i a6, a3, 2 + EE.ZERO.ACCX + + EE.VUNZIP.16 q2, q0 + EE.VMULAS.U16.ACCX q2, q4 + RUR.ACCX_0 a6 + s16i a6, a3, 0 + + movi.n a2, 0 // return status ESP_OK + retw.n + +#endif \ No newline at end of file diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/lut.c b/firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/lut.c new file mode 100755 index 0000000..4641bd9 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/lut.c @@ -0,0 +1,474 @@ +#include "lut.h" + +#include "epdiy.h" +#include "esp_attr.h" +#include "render_context.h" +#include "render_method.h" + +#include +#include +#include + +#include "esp_timer.h" + +/* + * Build Lookup tables and translate via LUTs. + * WARNING: These functions must only ever write to internal memory, + * Since we disable the PSRAM workaround here for performance reasons. + */ + +/* Python script for generating the 8ppB, starting at white lookup table: + for i in range(256): + number = 0; + for b in range(8): + if not (i & (1 << b)): + number |= 1 << (2*b) + print ('0x%04x,'%number) + */ +const uint32_t lut_8ppB_start_at_white[256] = { + 0x5555, 0x5554, 0x5551, 0x5550, 0x5545, 0x5544, 0x5541, 0x5540, 0x5515, 0x5514, 0x5511, 0x5510, + 0x5505, 0x5504, 0x5501, 0x5500, 0x5455, 0x5454, 0x5451, 0x5450, 0x5445, 0x5444, 0x5441, 0x5440, + 0x5415, 0x5414, 0x5411, 0x5410, 0x5405, 0x5404, 0x5401, 0x5400, 0x5155, 0x5154, 0x5151, 0x5150, + 0x5145, 0x5144, 0x5141, 0x5140, 0x5115, 0x5114, 0x5111, 0x5110, 0x5105, 0x5104, 0x5101, 0x5100, + 0x5055, 0x5054, 0x5051, 0x5050, 0x5045, 0x5044, 0x5041, 0x5040, 0x5015, 0x5014, 0x5011, 0x5010, + 0x5005, 0x5004, 0x5001, 0x5000, 0x4555, 0x4554, 0x4551, 0x4550, 0x4545, 0x4544, 0x4541, 0x4540, + 0x4515, 0x4514, 0x4511, 0x4510, 0x4505, 0x4504, 0x4501, 0x4500, 0x4455, 0x4454, 0x4451, 0x4450, + 0x4445, 0x4444, 0x4441, 0x4440, 0x4415, 0x4414, 0x4411, 0x4410, 0x4405, 0x4404, 0x4401, 0x4400, + 0x4155, 0x4154, 0x4151, 0x4150, 0x4145, 0x4144, 0x4141, 0x4140, 0x4115, 0x4114, 0x4111, 0x4110, + 0x4105, 0x4104, 0x4101, 0x4100, 0x4055, 0x4054, 0x4051, 0x4050, 0x4045, 0x4044, 0x4041, 0x4040, + 0x4015, 0x4014, 0x4011, 0x4010, 0x4005, 0x4004, 0x4001, 0x4000, 0x1555, 0x1554, 0x1551, 0x1550, + 0x1545, 0x1544, 0x1541, 0x1540, 0x1515, 0x1514, 0x1511, 0x1510, 0x1505, 0x1504, 0x1501, 0x1500, + 0x1455, 0x1454, 0x1451, 0x1450, 0x1445, 0x1444, 0x1441, 0x1440, 0x1415, 0x1414, 0x1411, 0x1410, + 0x1405, 0x1404, 0x1401, 0x1400, 0x1155, 0x1154, 0x1151, 0x1150, 0x1145, 0x1144, 0x1141, 0x1140, + 0x1115, 0x1114, 0x1111, 0x1110, 0x1105, 0x1104, 0x1101, 0x1100, 0x1055, 0x1054, 0x1051, 0x1050, + 0x1045, 0x1044, 0x1041, 0x1040, 0x1015, 0x1014, 0x1011, 0x1010, 0x1005, 0x1004, 0x1001, 0x1000, + 0x0555, 0x0554, 0x0551, 0x0550, 0x0545, 0x0544, 0x0541, 0x0540, 0x0515, 0x0514, 0x0511, 0x0510, + 0x0505, 0x0504, 0x0501, 0x0500, 0x0455, 0x0454, 0x0451, 0x0450, 0x0445, 0x0444, 0x0441, 0x0440, + 0x0415, 0x0414, 0x0411, 0x0410, 0x0405, 0x0404, 0x0401, 0x0400, 0x0155, 0x0154, 0x0151, 0x0150, + 0x0145, 0x0144, 0x0141, 0x0140, 0x0115, 0x0114, 0x0111, 0x0110, 0x0105, 0x0104, 0x0101, 0x0100, + 0x0055, 0x0054, 0x0051, 0x0050, 0x0045, 0x0044, 0x0041, 0x0040, 0x0015, 0x0014, 0x0011, 0x0010, + 0x0005, 0x0004, 0x0001, 0x0000 +}; + +/* Python script for generating the 8ppB, starting at black lookup table: + for i in range(256): + number = 0; + for b in range(8): + if (i & (1 << b)): + number |= 2 << (2*b) + print ('0x%04x,'%number) + */ +const uint32_t lut_8ppB_start_at_black[256] = { + 0x0000, 0x0002, 0x0008, 0x000a, 0x0020, 0x0022, 0x0028, 0x002a, 0x0080, 0x0082, 0x0088, 0x008a, + 0x00a0, 0x00a2, 0x00a8, 0x00aa, 0x0200, 0x0202, 0x0208, 0x020a, 0x0220, 0x0222, 0x0228, 0x022a, + 0x0280, 0x0282, 0x0288, 0x028a, 0x02a0, 0x02a2, 0x02a8, 0x02aa, 0x0800, 0x0802, 0x0808, 0x080a, + 0x0820, 0x0822, 0x0828, 0x082a, 0x0880, 0x0882, 0x0888, 0x088a, 0x08a0, 0x08a2, 0x08a8, 0x08aa, + 0x0a00, 0x0a02, 0x0a08, 0x0a0a, 0x0a20, 0x0a22, 0x0a28, 0x0a2a, 0x0a80, 0x0a82, 0x0a88, 0x0a8a, + 0x0aa0, 0x0aa2, 0x0aa8, 0x0aaa, 0x2000, 0x2002, 0x2008, 0x200a, 0x2020, 0x2022, 0x2028, 0x202a, + 0x2080, 0x2082, 0x2088, 0x208a, 0x20a0, 0x20a2, 0x20a8, 0x20aa, 0x2200, 0x2202, 0x2208, 0x220a, + 0x2220, 0x2222, 0x2228, 0x222a, 0x2280, 0x2282, 0x2288, 0x228a, 0x22a0, 0x22a2, 0x22a8, 0x22aa, + 0x2800, 0x2802, 0x2808, 0x280a, 0x2820, 0x2822, 0x2828, 0x282a, 0x2880, 0x2882, 0x2888, 0x288a, + 0x28a0, 0x28a2, 0x28a8, 0x28aa, 0x2a00, 0x2a02, 0x2a08, 0x2a0a, 0x2a20, 0x2a22, 0x2a28, 0x2a2a, + 0x2a80, 0x2a82, 0x2a88, 0x2a8a, 0x2aa0, 0x2aa2, 0x2aa8, 0x2aaa, 0x8000, 0x8002, 0x8008, 0x800a, + 0x8020, 0x8022, 0x8028, 0x802a, 0x8080, 0x8082, 0x8088, 0x808a, 0x80a0, 0x80a2, 0x80a8, 0x80aa, + 0x8200, 0x8202, 0x8208, 0x820a, 0x8220, 0x8222, 0x8228, 0x822a, 0x8280, 0x8282, 0x8288, 0x828a, + 0x82a0, 0x82a2, 0x82a8, 0x82aa, 0x8800, 0x8802, 0x8808, 0x880a, 0x8820, 0x8822, 0x8828, 0x882a, + 0x8880, 0x8882, 0x8888, 0x888a, 0x88a0, 0x88a2, 0x88a8, 0x88aa, 0x8a00, 0x8a02, 0x8a08, 0x8a0a, + 0x8a20, 0x8a22, 0x8a28, 0x8a2a, 0x8a80, 0x8a82, 0x8a88, 0x8a8a, 0x8aa0, 0x8aa2, 0x8aa8, 0x8aaa, + 0xa000, 0xa002, 0xa008, 0xa00a, 0xa020, 0xa022, 0xa028, 0xa02a, 0xa080, 0xa082, 0xa088, 0xa08a, + 0xa0a0, 0xa0a2, 0xa0a8, 0xa0aa, 0xa200, 0xa202, 0xa208, 0xa20a, 0xa220, 0xa222, 0xa228, 0xa22a, + 0xa280, 0xa282, 0xa288, 0xa28a, 0xa2a0, 0xa2a2, 0xa2a8, 0xa2aa, 0xa800, 0xa802, 0xa808, 0xa80a, + 0xa820, 0xa822, 0xa828, 0xa82a, 0xa880, 0xa882, 0xa888, 0xa88a, 0xa8a0, 0xa8a2, 0xa8a8, 0xa8aa, + 0xaa00, 0xaa02, 0xaa08, 0xaa0a, 0xaa20, 0xaa22, 0xaa28, 0xaa2a, 0xaa80, 0xaa82, 0xaa88, 0xaa8a, + 0xaaa0, 0xaaa2, 0xaaa8, 0xaaaa, +}; + +static inline int min(int x, int y) { + return x < y ? x : y; +} +static inline int max(int x, int y) { + return x > y ? x : y; +} + +__attribute__((optimize("O3"))) void IRAM_ATTR +bit_shift_buffer_right(uint8_t* buf, uint32_t len, int shift) { + uint8_t carry = 0xFF << (8 - shift); + for (uint32_t i = 0; i < len; i++) { + uint8_t val = buf[i]; + buf[i] = (val >> shift) | carry; + carry = val << (8 - shift); + } +} + +__attribute__((optimize("O3"))) void IRAM_ATTR +nibble_shift_buffer_right(uint8_t* buf, uint32_t len) { + uint8_t carry = 0xF; + for (uint32_t i = 0; i < len; i++) { + uint8_t val = buf[i]; + buf[i] = (val << 4) | carry; + carry = (val & 0xF0) >> 4; + } +} + +__attribute__((optimize("O3"))) void IRAM_ATTR calc_epd_input_8ppB( + const uint32_t* line_data, uint8_t* epd_input, const uint8_t* lut, uint32_t epd_width +) { + uint32_t* wide_epd_input = (uint32_t*)epd_input; + uint8_t* data_ptr = (uint8_t*)line_data; + uint32_t* lut_32 = (uint32_t*)lut; + // this is reversed for little-endian, but this is later compensated + // through the output peripheral. + for (int j = 0; j < epd_width / 16; j++) { + uint8_t v1 = *(data_ptr++); + uint8_t v2 = *(data_ptr++); + wide_epd_input[j] = (lut_32[v2] << 16) | lut_32[v1]; + } + + // Account for missing line end if epd_width is not divisible by 16. + // We assume divisibility by 4. + for (int j = 0; j < (epd_width % 16) / 4; j++) { + uint8_t nibble = *data_ptr; + if (j % 2 == 1) { + nibble = nibble >> 4; + data_ptr++; + } else { + nibble = nibble & 0xF; + } + epd_input[(epd_width / 16) * 4 + j] = lut_32[nibble]; + } +} + +/** + * Look up 4 pixels of a differential image in a LUT constructed for use with vector extensions. + */ +__attribute__((optimize("O3"))) static inline uint8_t lookup_pixels_in_VE_LUT( + const uint32_t in, const uint8_t* conversion_lut +) { + uint32_t* padded_lut = (uint32_t*)conversion_lut; + uint8_t out = padded_lut[(in >> 24) & 0xFF] << 6; + out |= padded_lut[(in >> 16) & 0xFF] << 4; + out |= padded_lut[(in >> 8) & 0xFF] << 2; + out |= padded_lut[(in >> 0) & 0xFF]; + return out; +} + +/** + * Lookup accelerated by the S3 Vector Extensions. + * Expects aligned buffers and a length that is divisible by 16. + */ +void IRAM_ATTR calc_epd_input_1ppB_1k_S3_VE_aligned( + const uint32_t* ld, uint8_t* epd_input, const uint8_t* conversion_lut, uint32_t epd_width +); + +/** + * Lookup accelerated by the S3 Vector Extensions. + * Uses a 1K padded LUT (each entry takes up 32 bits) + */ +void IRAM_ATTR calc_epd_input_1ppB_1k_S3_VE( + const uint32_t* ld, uint8_t* epd_input, const uint8_t* conversion_lut, uint32_t epd_width +) { + // alignment boundaries in pixels + int unaligned_len_front = (16 - (uint32_t)ld % 16) % 16; + int unaligned_len_back = ((uint32_t)ld + epd_width) % 16; + int aligned_len = epd_width - unaligned_len_front - unaligned_len_back; + + if (unaligned_len_front) { + for (int i = 0; i < unaligned_len_front / 4; i++) { + (*epd_input++) = lookup_pixels_in_VE_LUT((*ld++), conversion_lut); + } + } + calc_epd_input_1ppB_1k_S3_VE_aligned(ld, epd_input, conversion_lut, aligned_len); + + ld += aligned_len / 4; + epd_input += aligned_len / 4; + + if (unaligned_len_back) { + for (int i = 0; i < unaligned_len_back / 4; i++) { + (*epd_input++) = lookup_pixels_in_VE_LUT((*ld++), conversion_lut); + } + } +} + +/** + * Calculate EPD input for a difference image with one pixel per byte. + */ +__attribute__((optimize("O3"))) void IRAM_ATTR calc_epd_input_1ppB_64k( + const uint32_t* ld, uint8_t* epd_input, const uint8_t* conversion_lut, uint32_t epd_width +) { + const uint16_t* lp = (uint16_t*)ld; + for (uint32_t j = 0; j < epd_width / 4; j++) { + epd_input[j] = (conversion_lut[lp[2 * j + 1]] << 4) | conversion_lut[lp[2 * j]]; + } +} + +__attribute__((optimize("O3"))) void IRAM_ATTR calc_epd_input_2ppB_lut_64k( + const uint32_t* line_data, uint8_t* epd_input, const uint8_t* conversion_lut, uint32_t epd_width +) { + const uint16_t* line_data_16 = (const uint16_t*)line_data; + + for (uint32_t j = 0; j < epd_width / 4; j++) { + epd_input[j] = conversion_lut[*(line_data_16++)]; + } +} + +/** + * Look up 4 pixels in a 1K LUT with fixed "from" value. + */ +__attribute__((optimize("O3"))) static uint8_t lookup_pixels_2ppB_1k( + uint16_t in, const uint8_t* conversion_lut, uint8_t from +) { + uint8_t v; + uint8_t out; + + v = ((in << 4) | from); + out = conversion_lut[v & 0xFF]; + v = ((in & 0xF0) | from); + out |= (conversion_lut + 0x100)[v & 0xFF]; + in = in >> 8; + v = ((in << 4) | from); + out |= (conversion_lut + 0x200)[v & 0xFF]; + v = ((in & 0xF0) | from); + out |= (conversion_lut + 0x300)[v]; + return out; +} + +/** + * Calculate EPD input for a 2ppB buffer, but with a difference image LUT. + * This is used for small-LUT mode. + */ +__attribute__((optimize("O3"))) void IRAM_ATTR calc_epd_input_2ppB_1k_lut( + const uint32_t* ld, + uint8_t* epd_input, + const uint8_t* conversion_lut, + uint8_t from, + uint32_t epd_width +) { + const uint16_t* line_data_16 = (const uint16_t*)ld; + + for (uint32_t j = 0; j < epd_width / 4; j++) { + epd_input[j] = lookup_pixels_2ppB_1k(*(line_data_16++), conversion_lut, from); + }; +} + +__attribute__((optimize("O3"))) void IRAM_ATTR calc_epd_input_2ppB_1k_lut_white( + const uint32_t* ld, uint8_t* epd_input, const uint8_t* conversion_lut, uint32_t epd_width +) { + calc_epd_input_2ppB_1k_lut(ld, epd_input, conversion_lut, 0xF, epd_width); +} + +__attribute__((optimize("O3"))) void IRAM_ATTR calc_epd_input_2ppB_1k_lut_black( + const uint32_t* ld, uint8_t* epd_input, const uint8_t* conversion_lut, uint32_t epd_width +) { + calc_epd_input_2ppB_1k_lut(ld, epd_input, conversion_lut, 0x0, epd_width); +} + +///////////////////////////// Calculate Lookup Tables +////////////////////////////////// + +/** + * Unpack the waveform data into a lookup table, with bit shifted copies. + */ +__attribute__((optimize("O3"))) static void IRAM_ATTR +build_2ppB_lut_1k(uint8_t* lut, const EpdWaveformPhases* phases, int frame) { + const uint8_t* p_lut = phases->luts + (16 * 4 * frame); + for (uint8_t to = 0; to < 16; to++) { + for (uint8_t from_packed = 0; from_packed < 4; from_packed++) { + uint8_t index = (to << 4) | (from_packed * 4); + uint8_t packed = *(p_lut++); + lut[index] = (packed >> 6) & 3; + lut[index + 1] = (packed >> 4) & 3; + lut[index + 2] = (packed >> 2) & 3; + lut[index + 3] = (packed >> 0) & 3; + // printf("%2X%2X%2X%2X (%d)", lut[index], lut[index + 1], lut[index + 2], + // lut[index + 3], index); + } + // printf("\n"); + } + uint32_t index = 0x100; + for (uint8_t s = 2; s <= 6; s += 2) { + for (int i = 0; i < 0x100; i++) { + lut[index] = lut[index % 0x100] << s; + index++; + } + } +} + +/** + * Unpack the waveform data into a lookup table, + * 64k to loop up two bytes at once + */ +__attribute__((optimize("O3"))) static void IRAM_ATTR +build_1ppB_lut_64k(uint8_t* lut, const EpdWaveformPhases* phases, int frame) { + const uint8_t* p_lut = phases->luts + (16 * 4 * frame); + for (uint8_t to = 0; to < 16; to++) { + for (uint8_t from_packed = 0; from_packed < 4; from_packed++) { + uint8_t index = (to << 4) | (from_packed * 4); + uint8_t packed = *(p_lut++); + lut[index] = (packed >> 6) & 3; + lut[index + 1] = (packed >> 4) & 3; + lut[index + 2] = (packed >> 2) & 3; + lut[index + 3] = (packed >> 0) & 3; + // printf("%2X%2X%2X%2X (%d)", lut[index], lut[index + 1], lut[index + 2], + // lut[index + 3], index); + } + // printf("\n"); + } + + for (int outer = 0xFF; outer >= 0; outer--) { + uint32_t outer_result = lut[outer] << 2; + outer_result |= (outer_result << 16); + outer_result |= (outer_result << 8); + uint32_t* lut_section = (uint32_t*)(&lut[outer << 8]); + memcpy(lut_section, lut, 0x100); + for (int i = 0; i < 0x100 / 4; i++) { + lut_section[i] = lut_section[i] | outer_result; + } + } +} + +/** + * A 32bit aligned lookup table for lookup using the ESP32-S3 vector extensions. + */ +__attribute__((optimize("O3"))) void IRAM_ATTR +epd_build_1ppB_lut_1k(uint8_t* lut, const EpdWaveformPhases* phases, int frame) { + uint32_t* lut32 = (uint32_t*)lut; + const uint8_t* p_lut = phases->luts + (16 * 4 * frame); + for (uint8_t to = 0; to < 16; to++) { + for (uint8_t from_packed = 0; from_packed < 4; from_packed++) { + uint8_t index = (to << 4) | (from_packed * 4); + uint8_t packed = *(p_lut++); + lut32[index] = (packed >> 6) & 3; + lut32[index + 1] = (packed >> 4) & 3; + lut32[index + 2] = (packed >> 2) & 3; + lut32[index + 3] = (packed >> 0) & 3; + } + } +} + +/** + * Build a 16-bit LUT from the waveform if the previous color is + * known, e.g. all white or all black. + * This LUT is use to look up 4 pixels at once, as with the epdiy LUT. + */ +__attribute__((optimize("O3"))) static void build_2ppB_lut_64k_static_from( + uint8_t* lut, const EpdWaveformPhases* phases, uint8_t from, int frame +) { + const uint8_t* p_lut = phases->luts + (16 * 4 * frame); + + /// index into the packed "from" row + uint8_t fi = from >> 2; + /// bit shift amount for the packed "from" row + uint8_t fs = 6 - 2 * (from & 3); + + // populate the first 4096 bytes + uint8_t v1 = 0; + uint32_t s1 = 0; + for (uint8_t t2 = 0; t2 < 16; t2++) { + uint8_t v2 = ((p_lut[(t2 << 2) + fi] >> fs) & 0x03) << 4; + uint32_t s2 = t2 << 8; + for (uint8_t t3 = 0; t3 < 16; t3++) { + uint8_t v3 = ((p_lut[(t3 << 2) + fi] >> fs) & 0x03) << 2; + uint32_t s3 = t3 << 4; + for (uint8_t t4 = 0; t4 < 16; t4++) { + uint8_t v4 = ((p_lut[(t4 << 2) + fi] >> fs) & 0x03) << 0; + uint32_t s4 = t4; + lut[s1 | s2 | s3 | s4] = v1 | v2 | v3 | v4; + } + } + } + + // now just copy and the first 4096 bytes and add the upper two bits + for (uint8_t t1 = 1; t1 < 16; t1++) { + memcpy(&lut[t1 << 12], lut, 1 << 12); + } + + for (int i = 0; i < 16; i++) { + uint32_t v1 = ((p_lut[(i << 2) + fi] >> fs) & 0x03); + uint32_t mask = (v1 << 30) | (v1 << 22) | (v1 << 14) | (v1 << 6); + for (int j = 0; j < 16 * 16 * 16 / 4; j++) { + ((uint32_t*)lut)[(i << 10) + j] |= mask; + } + } +} + +static void build_2ppB_lut_64k_from_0(uint8_t* lut, const EpdWaveformPhases* phases, int frame) { + build_2ppB_lut_64k_static_from(lut, phases, 0, frame); +} + +static void build_2ppB_lut_64k_from_15(uint8_t* lut, const EpdWaveformPhases* phases, int frame) { + build_2ppB_lut_64k_static_from(lut, phases, 0xF, frame); +} + +static void build_8ppB_lut_256b_from_white( + uint8_t* lut, const EpdWaveformPhases* phases, int frame +) { + memcpy(lut, lut_8ppB_start_at_white, sizeof(lut_8ppB_start_at_white)); +} + +static void build_8ppB_lut_256b_from_black( + uint8_t* lut, const EpdWaveformPhases* phases, int frame +) { + memcpy(lut, lut_8ppB_start_at_black, sizeof(lut_8ppB_start_at_black)); +} + +void IRAM_ATTR epd_apply_line_mask(uint8_t* buf, const uint8_t* mask, int len) { + for (int i = 0; i < len / 4; i++) { + ((uint32_t*)buf)[i] &= ((uint32_t*)mask)[i]; + } +} + +LutFunctionPair find_lut_functions(enum EpdDrawMode mode, uint32_t lut_size) { + LutFunctionPair pair; + pair.build_func = NULL; + pair.lookup_func = NULL; + + if (mode & MODE_PACKING_1PPB_DIFFERENCE) { + if (!(mode & MODE_FORCE_NO_PIE) && lut_size >= 1024) { + pair.build_func = &epd_build_1ppB_lut_1k; + pair.lookup_func = &calc_epd_input_1ppB_1k_S3_VE; + return pair; + } else if (lut_size >= 1 << 16) { + pair.build_func = &build_1ppB_lut_64k; + pair.lookup_func = &calc_epd_input_1ppB_64k; + return pair; + } + } else if (mode & MODE_PACKING_2PPB) { + if (lut_size >= 1 << 16) { + if (mode & PREVIOUSLY_WHITE) { + pair.build_func = &build_2ppB_lut_64k_from_15; + pair.lookup_func = &calc_epd_input_2ppB_lut_64k; + return pair; + } else if (mode & PREVIOUSLY_BLACK) { + pair.build_func = &build_2ppB_lut_64k_from_0; + pair.lookup_func = &calc_epd_input_2ppB_lut_64k; + return pair; + } + } else if (lut_size >= 1024) { + if (mode & PREVIOUSLY_WHITE) { + pair.build_func = &build_2ppB_lut_1k; + pair.lookup_func = &calc_epd_input_2ppB_1k_lut_white; + return pair; + } else if (mode & PREVIOUSLY_BLACK) { + pair.build_func = &build_2ppB_lut_1k; + pair.lookup_func = &calc_epd_input_2ppB_1k_lut_black; + return pair; + } + } + } else if (mode & MODE_PACKING_8PPB) { + if (lut_size < sizeof(lut_8ppB_start_at_white)) { + return pair; + } + + if (mode & PREVIOUSLY_WHITE) { + pair.build_func = &build_8ppB_lut_256b_from_white; + pair.lookup_func = &calc_epd_input_8ppB; + return pair; + } else if (mode & PREVIOUSLY_BLACK) { + pair.build_func = &build_8ppB_lut_256b_from_black; + pair.lookup_func = &calc_epd_input_8ppB; + return pair; + } + } + + return pair; +} diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/lut.h b/firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/lut.h new file mode 100755 index 0000000..1fba665 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/lut.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include "epdiy.h" + +// Make a block of 4 pixels lighter on the EPD. +#define CLEAR_BYTE 0B10101010 +// Make a block of 4 pixels darker on the EPD. +#define DARK_BYTE 0B01010101 + +/** + * Type signature of a framebuffer to display output lookup function. + */ +typedef void (*lut_func_t)( + const uint32_t* line_buffer, uint8_t* epd_input, const uint8_t* lut, uint32_t epd_width +); + +/** + * Type signature of a LUT preparation function. + */ +typedef void (*lut_build_func_t)(uint8_t* lut, const EpdWaveformPhases* phases, int frame); + +typedef struct { + lut_build_func_t build_func; + lut_func_t lookup_func; +} LutFunctionPair; + +/** + * Select the appropriate LUT building and lookup function + * for the selected draw mode and allocated LUT size. + */ +LutFunctionPair find_lut_functions(enum EpdDrawMode mode, uint32_t lut_size); + +/// 1ppB + S3 VE:256 项 × 4 字节。index = (to << 4) | from。 +#define EPD_1PPB_LUT_1K_SIZE 1024 +void epd_build_1ppB_lut_1k(uint8_t* lut, const EpdWaveformPhases* phases, int frame); + +/** + * Apply a mask to a line buffer. + * `len` must be divisible by 4. + */ +void epd_apply_line_mask(uint8_t* buf, const uint8_t* mask, int len); + +// legacy functions +void bit_shift_buffer_right(uint8_t* buf, uint32_t len, int shift); +void nibble_shift_buffer_right(uint8_t* buf, uint32_t len); diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/render_context.c b/firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/render_context.c new file mode 100755 index 0000000..5db0a97 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/render_context.c @@ -0,0 +1,110 @@ +#include "render_context.h" + +#include +#include "esp_log.h" + +#include "../epdiy.h" +#include "lut.h" +#include "render_method.h" + +/// For waveforms without per-phase timing, the default hold time for each line is 12us +const static int DEFAULT_FRAME_TIME = 120; + +static inline int min(int x, int y) { + return x < y ? x : y; +} + +void get_buffer_params( + RenderContext_t* ctx, + int* bytes_per_line, + const uint8_t** start_ptr, + int* min_y, + int* max_y, + int* pixels_per_byte +) { + EpdRect area = ctx->area; + + enum EpdDrawMode mode = ctx->mode; + const EpdRect crop_to = ctx->crop_to; + const bool horizontally_cropped = !(crop_to.x == 0 && crop_to.width == area.width); + const bool vertically_cropped = !(crop_to.y == 0 && crop_to.height == area.height); + + // number of pixels per byte of input data + int width_divider = 0; + + if (mode & MODE_PACKING_1PPB_DIFFERENCE) { + *bytes_per_line = area.width; + width_divider = 1; + } else if (mode & MODE_PACKING_2PPB) { + *bytes_per_line = area.width / 2 + area.width % 2; + width_divider = 2; + } else if (mode & MODE_PACKING_8PPB) { + *bytes_per_line = (area.width / 8 + (area.width % 8 > 0)); + width_divider = 8; + } else { + ctx->error |= EPD_DRAW_INVALID_PACKING_MODE; + } + + int crop_x = (horizontally_cropped ? crop_to.x : 0); + int crop_y = (vertically_cropped ? crop_to.y : 0); + int crop_h = (vertically_cropped ? crop_to.height : 0); + + const uint8_t* ptr_start = ctx->data_ptr; + + // Adjust for negative starting coordinates with optional crop + if (area.x - crop_x < 0) { + ptr_start += -(area.x - crop_x) / width_divider; + } + + if (area.y - crop_y < 0) { + ptr_start += -(area.y - crop_y) * *bytes_per_line; + } + + // calculate start and end row with crop + *min_y = area.y + crop_y; + *max_y = min(*min_y + (vertically_cropped ? crop_h : area.height), area.height); + *start_ptr = ptr_start; + *pixels_per_byte = width_divider; +} + +void IRAM_ATTR prepare_context_for_next_frame(RenderContext_t* ctx) { + int frame_time = DEFAULT_FRAME_TIME; + if (ctx->phase_times != NULL) { + frame_time = ctx->phase_times[ctx->current_frame]; + } + + if (ctx->mode & MODE_EPDIY_MONOCHROME) { + frame_time = MONOCHROME_FRAME_TIME; + } + ctx->frame_time = frame_time; + + const EpdWaveformPhases* phases + = ctx->waveform->mode_data[ctx->waveform_index]->range_data[ctx->waveform_range]; + + assert(ctx->lut_build_func != NULL); + ctx->lut_build_func(ctx->conversion_lut, phases, ctx->current_frame); + + ctx->lines_prepared = 0; + ctx->lines_consumed = 0; + if (ctx->line_threads != NULL) { + for (int i = 0; i < ctx->lines_total; i++) { + ctx->line_threads[i] = i % NUM_RENDER_THREADS; + } + } +} + +void epd_populate_line_mask(uint8_t* line_mask, const uint8_t* dirty_columns, int mask_len) { + if (dirty_columns == NULL) { + memset(line_mask, 0xFF, mask_len); + } else { + int pixels = mask_len * 4; + for (int c = 0; c < pixels / 2; c += 2) { + uint8_t mask = 0; + mask |= (dirty_columns[c + 1] & 0xF0) != 0 ? 0xC0 : 0x00; + mask |= (dirty_columns[c + 1] & 0x0F) != 0 ? 0x30 : 0x00; + mask |= (dirty_columns[c] & 0xF0) != 0 ? 0x0C : 0x00; + mask |= (dirty_columns[c] & 0x0F) != 0 ? 0x03 : 0x00; + line_mask[c / 2] = mask; + } + } +} \ No newline at end of file diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/render_context.h b/firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/render_context.h new file mode 100755 index 0000000..d697b96 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/render_context.h @@ -0,0 +1,107 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "../epdiy.h" +#include "line_queue.h" +#include "lut.h" + +#define NUM_RENDER_THREADS 2 + +typedef struct { + EpdRect area; + EpdRect crop_to; + const bool* drawn_lines; + const uint8_t* data_ptr; + + /// The display width for quick access. + int display_width; + /// The display height for quick access. + int display_height; + + /// index of the next line of data to process + atomic_int lines_prepared; + volatile int lines_consumed; + int lines_total; + + /// frame currently in the current update cycle + int current_frame; + /// number of frames in the current update cycle + int cycle_frames; + + TaskHandle_t feed_tasks[NUM_RENDER_THREADS]; + SemaphoreHandle_t feed_done_smphr[NUM_RENDER_THREADS]; + SemaphoreHandle_t frame_done; + /// Line buffers for feed tasks + uint8_t* feed_line_buffers[NUM_RENDER_THREADS]; + + /// index of the waveform mode when using vendor waveforms. + /// This is not necessarily the mode number if the waveform header + // only contains a selection of modes! + int waveform_index; + /// waveform range when using vendor waveforms + int waveform_range; + /// Draw time for the current frame in 1/10ths of us. + int frame_time; + + const int* phase_times; + + const EpdWaveform* waveform; + enum EpdDrawMode mode; + enum EpdDrawError error; + + // Lookup table size. + size_t conversion_lut_size; + // Lookup table space. + uint8_t* conversion_lut; + + /// LUT lookup function. Must not be NULL. + lut_func_t lut_lookup_func; + /// LUT building function. Must not be NULL + lut_build_func_t lut_build_func; + + /// Queue of lines prepared for output to the display, + /// one for each thread. + LineQueue_t line_queues[NUM_RENDER_THREADS]; + uint8_t* line_threads; + + // Output line mask + uint8_t* line_mask; + + /// line buffer when using epd_push_pixels + uint8_t* static_line_buffer; +} RenderContext_t; + +/** + * Based on the render context, assign the bytes per line, + * framebuffer start pointer, min and max vertical positions and the pixels per byte. + */ +void get_buffer_params( + RenderContext_t* ctx, + int* bytes_per_line, + const uint8_t** start_ptr, + int* min_y, + int* max_y, + int* pixels_per_byte +); + +/** + * Prepare the render context for drawing the next frame. + * + * (Reset counters, etc) + */ +void prepare_context_for_next_frame(RenderContext_t* ctx); + +/** + * Populate an output line mask from line dirtyness with two bits per pixel. + * If the dirtyness data is NULL, set the mask to neutral. + * + * don't inline for to ensure availability in tests. + */ +void __attribute__((noinline)) epd_populate_line_mask( + uint8_t* line_mask, const uint8_t* dirty_columns, int mask_len +); \ No newline at end of file diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/render_method.h b/firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/render_method.h new file mode 100755 index 0000000..6ba31b7 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/render_method.h @@ -0,0 +1,15 @@ +#pragma once + +#include "sdkconfig.h" + +// 本 fork 只保留 ESP32-S3 的 LCD/CAM 外设输出路径。 +#ifndef CONFIG_IDF_TARGET_ESP32S3 +#error "this epdiy fork only supports ESP32-S3 (LCD render path)" +#endif +#define RENDER_METHOD_LCD 1 + +#ifdef __clang__ +#define IRAM_ATTR +// define this if we're using clangd to make it accept the GCC builtin +void __assert_func(const char* file, int line, const char* func, const char* failedexpr); +#endif diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/rmt_compat.c b/firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/rmt_compat.c new file mode 100644 index 0000000..e895855 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/rmt_compat.c @@ -0,0 +1,227 @@ +#include "rmt_compat.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) +#include +// esp-idf-configdep may strip esp_private headers; declare the needed API locally. +esp_err_t esp_clk_tree_enable_src(soc_module_clk_t clk_src, bool enable); +#endif + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) +#include +#ifndef SOC_RMT_MEM_WORDS_PER_CHANNEL +#define SOC_RMT_MEM_WORDS_PER_CHANNEL 48 +#endif + +typedef struct { + struct { + uint32_t data32[SOC_RMT_MEM_WORDS_PER_CHANNEL]; + } chan[RMT_LL_CHANS_PER_INST]; +} rmt_mem_block_t; + +extern rmt_mem_block_t RMTMEM; +#else +#include +#include +typedef rmt_mem_t rmt_mem_block_t; +extern rmt_mem_block_t RMTMEM; +#endif + +static gpio_hal_context_t s_gpio_hal = { .dev = GPIO_HAL_GET_HW(GPIO_PORT_0) }; + +static void rmt_compat_set_module_enabled(bool enable) { +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(6, 0, 0) + if (enable) { + periph_module_enable(PERIPH_RMT_MODULE); + } else { + periph_module_disable(PERIPH_RMT_MODULE); + } +#else + PERIPH_RCC_ATOMIC() { + rmt_ll_enable_bus_clock(0, enable); + } +#endif +} + +static void rmt_compat_set_periph_clock_enabled(bool enable) { +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) + // IDF 6.0 removed rmt_ll_enable_periph_clock — the replacement + // rmt_ll_enable_group_clock only sets sclk_active (already default 1). + // We must also enable the register clock gate and memory clock, + // otherwise RMT register writes are silently ignored. + rmt_ll_enable_group_clock(&RMT, enable); +#if CONFIG_IDF_TARGET_ESP32S3 + RMT.sys_conf.clk_en = enable; + RMT.sys_conf.mem_clk_force_on = enable; +#endif +#else + rmt_ll_enable_periph_clock(&RMT, enable); +#endif +} + +static void rmt_compat_reset_module_regs(void) { +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(6, 0, 0) + periph_module_reset(PERIPH_RMT_MODULE); +#else + PERIPH_RCC_ATOMIC() { + rmt_ll_reset_register(0); + } +#endif +} + +void rmt_compat_enable_clock(rmt_compat_channel_t channel) { + (void)channel; +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) + PERIPH_RCC_ATOMIC() { + rmt_ll_reset_register(0); + } + rmt_compat_set_module_enabled(true); +#else + rmt_compat_reset_module_regs(); + rmt_compat_set_module_enabled(true); +#endif + rmt_compat_set_periph_clock_enabled(true); +} + +void rmt_compat_disable_clock(rmt_compat_channel_t channel) { + (void)channel; + rmt_compat_set_periph_clock_enabled(false); + rmt_compat_set_module_enabled(false); +} + +void rmt_compat_enable_periph_clock(bool enable) { + rmt_compat_set_periph_clock_enabled(enable); +} + +void rmt_compat_reset_module(void) { + rmt_compat_reset_module_regs(); +} + +void rmt_compat_enable_module(bool enable) { + rmt_compat_set_module_enabled(enable); +} + +void rmt_compat_connect_gpio(rmt_compat_channel_t channel, gpio_num_t gpio) { + gpio_hal_func_sel(&s_gpio_hal, gpio, PIN_FUNC_GPIO); + gpio_set_direction(gpio, GPIO_MODE_OUTPUT); +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) + esp_rom_gpio_connect_out_signal(gpio, soc_rmt_signals[0].channels[channel].tx_sig, false, 0); +#else + esp_rom_gpio_connect_out_signal( + gpio, rmt_periph_signals.groups[0].channels[channel].tx_sig, false, 0 + ); +#endif +} + +void rmt_compat_set_group_clock_src(rmt_compat_channel_t channel) { +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) + // Match esp_driver_rmt: enable source in clock tree and use (1,1,0) dividers. + esp_clk_tree_enable_src((soc_module_clk_t)RMT_CLK_SRC_DEFAULT, true); + PERIPH_RCC_ATOMIC() { + rmt_ll_set_group_clock_src(&RMT, channel, RMT_CLK_SRC_DEFAULT, 1, 1, 0); + rmt_ll_enable_group_clock(&RMT, true); + } +#else + rmt_ll_set_group_clock_src(&RMT, channel, RMT_CLK_SRC_DEFAULT, 1, 0, 0); +#endif +} + +void rmt_compat_set_clock_div(rmt_compat_channel_t channel, uint8_t div) { + rmt_ll_tx_set_channel_clock_div(&RMT, channel, div); +} + +void rmt_compat_set_mem_blocks(rmt_compat_channel_t channel, uint8_t blocks) { + rmt_ll_tx_set_mem_blocks(&RMT, channel, blocks); +} + +void rmt_compat_enable_mem_access_nonfifo(bool enable) { + rmt_ll_enable_mem_access_nonfifo(&RMT, enable); +} + +void rmt_compat_tx_set_idle_level(rmt_compat_channel_t channel, uint8_t level, bool enable) { + rmt_ll_tx_fix_idle_level(&RMT, channel, level, enable); +} + +void rmt_compat_tx_enable_carrier(rmt_compat_channel_t channel, bool enable) { + rmt_ll_tx_enable_carrier_modulation(&RMT, channel, enable); +} + +void rmt_compat_tx_enable_loop(rmt_compat_channel_t channel, bool enable) { + rmt_ll_tx_enable_loop(&RMT, channel, enable); +} + +void IRAM_ATTR rmt_compat_tx_start(rmt_compat_channel_t channel) { + rmt_ll_tx_reset_pointer(&RMT, channel); + rmt_ll_tx_start(&RMT, channel); +} + +void IRAM_ATTR rmt_compat_tx_reset_mem(rmt_compat_channel_t channel) { + rmt_ll_tx_reset_pointer(&RMT, channel); +} + +void IRAM_ATTR rmt_compat_tx_configure_finite_loop(rmt_compat_channel_t channel, uint32_t count) { +#if defined(SOC_RMT_SUPPORT_TX_LOOP_COUNT) && SOC_RMT_SUPPORT_TX_LOOP_COUNT +#if defined(SOC_RMT_SUPPORT_TX_LOOP_AUTO_STOP) && SOC_RMT_SUPPORT_TX_LOOP_AUTO_STOP + rmt_ll_tx_enable_loop_autostop(&RMT, channel, true); +#endif + rmt_ll_tx_reset_loop_count(&RMT, channel); + rmt_ll_tx_enable_loop_count(&RMT, channel, true); + rmt_ll_tx_set_loop_count(&RMT, channel, count); +#elif defined(CONFIG_IDF_TARGET_ESP32S3) +#error "ESP32-S3 LCD output requires RMT TX loop count support" +#else + (void)channel; + (void)count; +#endif +} + +int rmt_compat_get_irq_source(void) { +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) + return soc_rmt_signals[0].irq; +#else + return rmt_periph_signals.groups[0].irq; +#endif +} + +void rmt_compat_tx_enable_interrupt(rmt_compat_channel_t channel, bool enable) { + const uint32_t tx_end_bit = 1u << (channel * 3); + if (enable) { + RMT.int_ena.val |= tx_end_bit; + } else { + RMT.int_ena.val &= ~tx_end_bit; + } +} + +void IRAM_ATTR rmt_compat_clear_interrupts(void) { + RMT.int_clr.val = RMT.int_st.val; +} + +void IRAM_ATTR rmt_compat_write_single_item( + rmt_compat_channel_t channel, + uint16_t duration0, + bool level0, + uint16_t duration1, + bool level1, + bool terminate +) { + volatile epdiy_rmt_item_t* rmt_mem_ptr = (void*)&(RMTMEM.chan[channel].data32[0]); + + rmt_mem_ptr[0].duration0 = duration0; + rmt_mem_ptr[0].level0 = level0; + rmt_mem_ptr[0].duration1 = duration1; + rmt_mem_ptr[0].level1 = level1; + + if (terminate) { + rmt_mem_ptr[1].val = 0; + } +} diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/rmt_compat.h b/firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/rmt_compat.h new file mode 100644 index 0000000..d10b35f --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/rmt_compat.h @@ -0,0 +1,75 @@ +#ifndef EPDIY_RMT_COMPAT_H +#define EPDIY_RMT_COMPAT_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + RMT_COMPAT_CHANNEL_0 = 0, + RMT_COMPAT_CHANNEL_1 = 1, + RMT_COMPAT_CHANNEL_2 = 2, + RMT_COMPAT_CHANNEL_3 = 3, + RMT_COMPAT_CHANNEL_MAX +} rmt_compat_channel_t; + +typedef struct { + union { + struct { + uint32_t duration0 : 15; + uint32_t level0 : 1; + uint32_t duration1 : 15; + uint32_t level1 : 1; + }; + uint32_t val; + }; +} epdiy_rmt_item_t; + +// Lifecycle / Clock +void rmt_compat_enable_clock(rmt_compat_channel_t channel); +void rmt_compat_disable_clock(rmt_compat_channel_t channel); +void rmt_compat_enable_periph_clock(bool enable); +void rmt_compat_reset_module(void); +void rmt_compat_enable_module(bool enable); + +// GPIO Routing +void rmt_compat_connect_gpio(rmt_compat_channel_t channel, gpio_num_t gpio); + +// TX Channel Config +void rmt_compat_set_group_clock_src(rmt_compat_channel_t channel); +void rmt_compat_set_clock_div(rmt_compat_channel_t channel, uint8_t div); +void rmt_compat_set_mem_blocks(rmt_compat_channel_t channel, uint8_t blocks); +void rmt_compat_enable_mem_access_nonfifo(bool enable); +void rmt_compat_tx_set_idle_level(rmt_compat_channel_t channel, uint8_t level, bool enable); +void rmt_compat_tx_enable_carrier(rmt_compat_channel_t channel, bool enable); +void rmt_compat_tx_enable_loop(rmt_compat_channel_t channel, bool enable); + +// TX Execution +void rmt_compat_tx_start(rmt_compat_channel_t channel); +void rmt_compat_tx_reset_mem(rmt_compat_channel_t channel); +void rmt_compat_tx_configure_finite_loop(rmt_compat_channel_t channel, uint32_t count); + +// Interrupts +int rmt_compat_get_irq_source(void); +void rmt_compat_tx_enable_interrupt(rmt_compat_channel_t channel, bool enable); +void rmt_compat_clear_interrupts(void); + +// Memory Writes +void rmt_compat_write_single_item( + rmt_compat_channel_t channel, + uint16_t duration0, + bool level0, + uint16_t duration1, + bool level1, + bool terminate +); + +#ifdef __cplusplus +} +#endif + +#endif // EPDIY_RMT_COMPAT_H diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/src/output_lcd/idf-4-backports.h b/firmware/targets/mindreset_read_pico/components/epdiy/src/output_lcd/idf-4-backports.h new file mode 100755 index 0000000..916789a --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/src/output_lcd/idf-4-backports.h @@ -0,0 +1,22 @@ +/** + * Backported functions to make the LCD-based driver compile with IDF < 5.0 + */ + +#define RMT_BASECLK_DEFAULT RMT_BASECLK_APB +typedef int rmt_clock_source_t; + +static inline void rmt_ll_enable_periph_clock(rmt_dev_t* dev, bool enable) { + dev->sys_conf.clk_en = enable; // register clock gating + dev->sys_conf.mem_clk_force_on = enable; // memory clock gating +} + +static inline void rmt_ll_enable_mem_access_nonfifo(rmt_dev_t* dev, bool enable) { + dev->sys_conf.apb_fifo_mask = enable; +} + +__attribute__((always_inline)) static inline void rmt_ll_tx_fix_idle_level( + rmt_dev_t* dev, uint32_t channel, uint8_t level, bool enable +) { + dev->chnconf0[channel].idle_out_en_n = enable; + dev->chnconf0[channel].idle_out_lv_n = level; +} diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/src/output_lcd/lcd_driver.c b/firmware/targets/mindreset_read_pico/components/epdiy/src/output_lcd/lcd_driver.c new file mode 100755 index 0000000..755dcc9 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/src/output_lcd/lcd_driver.c @@ -0,0 +1,853 @@ +#include "lcd_driver.h" +#include "epdiy.h" + +#include "../output_common/render_method.h" +#include "../output_common/rmt_compat.h" +#include "esp_heap_caps.h" +#include "esp_intr_alloc.h" +#include "hal/gpio_types.h" + +#ifdef RENDER_METHOD_LCD + +#include +#include +#include +#include +#include +#include + +#include +#include +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) +#include +// esp-idf-configdep may strip esp_private headers; declare the needed API locally. +esp_err_t esp_clk_tree_enable_src(soc_module_clk_t clk_src, bool enable); +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) +#if __has_include() +#include +#elif __has_include() +#include +#endif +typedef struct { + const shared_periph_module_t module; + const int irq_id; + const int data_sigs[LCD_LL_GET(RGB_BUS_WIDTH)]; + const int hsync_sig; + const int vsync_sig; + const int pclk_sig; + const int de_sig; + const int disp_sig; +} soc_lcd_rgb_signal_desc_t; +extern const soc_lcd_rgb_signal_desc_t soc_lcd_rgb_signals[LCD_LL_GET(RGB_PANEL_NUM)]; +#else +#include +#include +#endif + +#include "hal/gpio_hal.h" + +gpio_hal_context_t hal = { .dev = GPIO_HAL_GET_HW(GPIO_PORT_0) }; + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) +#undef __DECLARE_RCC_ATOMIC_ENV +#endif + +#define TAG "epdiy" + +// In IDF 5.3.2+, lcd_periph_signals was renamed to lcd_periph_rgb_signals +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) +#define LCD_PERIPH_SIG(member) soc_lcd_rgb_signals[0].member +#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 2) +#define LCD_PERIPH_SIG(member) lcd_periph_rgb_signals.panels[0].member +#else +#define LCD_PERIPH_SIG(member) lcd_periph_signals.panels[0].member +#endif + +static inline int min(int x, int y) { + return x < y ? x : y; +} +static inline int max(int x, int y) { + return x > y ? x : y; +} + +#define S3_LCD_PIN_NUM_BK_LIGHT -1 +// #define S3_LCD_PIN_NUM_MODE 4 + +#define LINE_BATCH 1000 +#define BOUNCE_BUF_LINES 4 + +#define RMT_CKV_CHAN RMT_COMPAT_CHANNEL_1 + +#if defined(CONFIG_ESP32S3_DATA_CACHE_LINE_SIZE) +#define EPDIY_DATA_CACHE_LINE_SIZE CONFIG_ESP32S3_DATA_CACHE_LINE_SIZE +#elif defined(CONFIG_DATA_CACHE_LINE_SIZE) +#define EPDIY_DATA_CACHE_LINE_SIZE CONFIG_DATA_CACHE_LINE_SIZE +#else +#define EPDIY_DATA_CACHE_LINE_SIZE 64 +#endif + +// spinlock for protecting the critical section at frame start +static portMUX_TYPE frame_start_spinlock = portMUX_INITIALIZER_UNLOCKED; + +typedef struct { + lcd_hal_context_t hal; + intr_handle_t vsync_intr; + intr_handle_t done_intr; + + frame_done_func_t frame_done_cb; + line_cb_func_t line_source_cb; + void* line_cb_payload; + void* frame_cb_payload; + + int line_length_us; + int line_time_01us; + int line_cycles; + int lcd_res_h; + + /// 实际发给 RMT 的 CKV 高电平时长,单位 0.1us。行时间会随 pclk 变化, + /// 所以不能直接用 config 里那个按默认 pclk 标定的固定值。 + int ckv_high_time; + + LcdEpdConfig_t config; + + uint8_t* bounce_buffer[2]; + // size of a single bounce buffer + size_t bb_size; + size_t bb_eof_count; + size_t batches; + + // Number of DMA descriptors that used to carry the frame buffer + size_t num_dma_nodes; + // DMA channel handle + gdma_channel_handle_t dma_chan; + // DMA descriptors pool + dma_descriptor_t* dma_nodes; + + /// LCD peripheral source clock frequency (Hz), from clock tree when available. + uint32_t src_clk_hz; + + /// The number of bytes in a horizontal display register line. + int line_bytes; + + // With 8 bit bus width, we need a dummy cycle before the actual data, + // because the LCD peripheral behaves weirdly. + // Also see: + // https://blog.adafruit.com/2022/06/14/esp32uesday-hacking-the-esp32-s3-lcd-peripheral/ + int dummy_bytes; + + /// The number of lines of the display + int display_lines; +} s3_lcd_t; + +static s3_lcd_t lcd = { 0 }; + +void IRAM_ATTR epd_lcd_line_source_cb(line_cb_func_t line_source, void* payload) { + lcd.line_source_cb = line_source; + lcd.line_cb_payload = payload; +} + +void IRAM_ATTR epd_lcd_frame_done_cb(frame_done_func_t cb, void* payload) { + lcd.frame_done_cb = cb; + lcd.frame_cb_payload = payload; +} + +static IRAM_ATTR bool fill_bounce_buffer(uint8_t* buffer) { + bool task_awoken = false; + + for (int i = 0; i < BOUNCE_BUF_LINES; i++) { + if (lcd.line_source_cb != NULL) { + // 8-bit needs a true dummy byte in FIFO; 16-bit still needs a dummy cycle but the + // first FIFO byte is already correct (read_pico / epdiy historical behavior). + int buffer_offset = i * (lcd.line_bytes + lcd.dummy_bytes) + (lcd.dummy_bytes % 2); + task_awoken |= lcd.line_source_cb(lcd.line_cb_payload, &buffer[buffer_offset]); + } else { + memset(&buffer[i * lcd.line_bytes], 0x00, lcd.line_bytes); + } + } + return task_awoken; +} + +static void IRAM_ATTR start_ckv_cycles(int cycles) { + rmt_compat_tx_configure_finite_loop(RMT_CKV_CHAN, cycles); + rmt_compat_tx_reset_mem(RMT_CKV_CHAN); + rmt_compat_tx_start(RMT_CKV_CHAN); +} + +/** + * Build the RMT signal according to the timing set in the lcd object. + */ +// RMT 的时间单位是 0.1us(80MHz 时钟 8 分频)。config 里的 ckv_high_time 是按 +// 默认 pclk 的行时间标定的,pclk 调高后行时间变短,高电平会顶满甚至超过一整行, +// 低电平算出负数——RMT 的 duration 是无符号字段,信号会彻底跑飞,面板收不到行 +// 时钟,表现是画面完全不动而 MCU 侧毫无报错。所以这里给低电平留出下限, +// 让高电平跟着行时间一起收缩。 +#define CKV_MIN_LOW_TIME 9 + +static void rebuild_line_geometry(int pclk_mhz) { + if (pclk_mhz < 1) pclk_mhz = 1; + const LcdLineTiming_t* t = &lcd.config.line; + int end = t->line_end > 0 ? t->line_end : 4; + lcd.line_cycles = lcd.lcd_res_h + t->le_high_time + t->line_front_porch + end; + lcd.line_time_01us = (lcd.line_cycles * 10 + pclk_mhz / 2) / pclk_mhz; + if (lcd.line_time_01us < 1) lcd.line_time_01us = 1; + lcd.line_length_us = (lcd.line_time_01us + 9) / 10; +} + +static void ckv_rmt_build_signal() { + int total_time = lcd.line_time_01us; + // 行时间还没算出来(init 阶段),等 epd_lcd_set_pixel_clock_MHz 再来构造 + if (total_time < 2 * CKV_MIN_LOW_TIME) { + return; + } + + int high_time = lcd.config.line.ckv_high_time; + if (high_time > total_time - CKV_MIN_LOW_TIME) { + high_time = total_time - CKV_MIN_LOW_TIME; + } + if (high_time < CKV_MIN_LOW_TIME) { + high_time = CKV_MIN_LOW_TIME; + } + lcd.ckv_high_time = high_time; + + rmt_compat_write_single_item( + RMT_CKV_CHAN, high_time, true, total_time - high_time, false, true + ); +} + +/** + * Configure the RMT peripheral for use as the CKV clock. + */ +static void init_ckv_rmt() { + rmt_compat_reset_module(); + rmt_compat_enable_module(true); + + rmt_compat_enable_periph_clock(true); + rmt_compat_set_group_clock_src(RMT_CKV_CHAN); + rmt_compat_set_clock_div(RMT_CKV_CHAN, 8); + rmt_compat_set_mem_blocks(RMT_CKV_CHAN, 2); + rmt_compat_enable_mem_access_nonfifo(true); + rmt_compat_tx_set_idle_level(RMT_CKV_CHAN, 0, true); + rmt_compat_tx_enable_carrier(RMT_CKV_CHAN, false); + rmt_compat_tx_enable_loop(RMT_CKV_CHAN, true); + + rmt_compat_connect_gpio(RMT_CKV_CHAN, lcd.config.bus.ckv); + + ckv_rmt_build_signal(); +} + +/** + * Reset the CKV RMT configuration. + */ +static void deinit_ckv_rmt() { + rmt_compat_reset_module(); + rmt_compat_enable_periph_clock(false); + rmt_compat_enable_module(false); + gpio_reset_pin(lcd.config.bus.ckv); +} + +__attribute__((optimize("O3"))) IRAM_ATTR static void lcd_isr_vsync(void* args) { + bool need_yield = false; + + uint32_t intr_status = lcd_ll_get_interrupt_status(lcd.hal.dev); + lcd_ll_clear_interrupt_status(lcd.hal.dev, intr_status); + + if (intr_status & LCD_LL_EVENT_VSYNC_END) { + // start_frame() already kicked off the first batch. This counter is the + // number of *additional* full LINE_BATCH chunks still needed after that + // first batch (integer division, not ceil). Using ceil made short panels + // (e.g. 688 < 1000) run a duplicate second batch and roughly 2x frame time. + int batches_needed = lcd.display_lines / LINE_BATCH; + if (lcd.batches >= batches_needed) { + lcd_ll_stop(lcd.hal.dev); + if (lcd.frame_done_cb != NULL) { + (*lcd.frame_done_cb)(lcd.frame_cb_payload); + } + } else { + int ckv_cycles = 0; + // last batch + if (lcd.batches == batches_needed - 1) { + int last_lines = lcd.display_lines % LINE_BATCH; + if (last_lines == 0) { + last_lines = LINE_BATCH; + } + lcd_ll_enable_auto_next_frame(lcd.hal.dev, false); + lcd_ll_set_vertical_timing(lcd.hal.dev, 1, 0, last_lines, 10); + ckv_cycles = last_lines + 10; + } else { + lcd_ll_set_vertical_timing(lcd.hal.dev, 1, 0, LINE_BATCH, 1); + ckv_cycles = LINE_BATCH + 1; + } + // apparently, this is needed for the new timing to take effect. + lcd_ll_start(lcd.hal.dev); + + // skip the LCD front porch line, which is not actual data + esp_rom_delay_us(lcd.line_length_us); + start_ckv_cycles(ckv_cycles); + } + + lcd.batches += 1; + } + + if (need_yield) { + portYIELD_FROM_ISR(); + } +}; + +// ISR handling bounce buffer refill +static IRAM_ATTR bool lcd_rgb_panel_eof_handler( + gdma_channel_handle_t dma_chan, gdma_event_data_t* event_data, void* user_data +) { + (void)dma_chan; + (void)event_data; + (void)user_data; + + int bb = lcd.bb_eof_count % 2; + lcd.bb_eof_count++; + return fill_bounce_buffer(lcd.bounce_buffer[bb]); +} + +static esp_err_t init_dma_trans_link() { + lcd.dma_nodes[0].dw0.suc_eof = 1; + lcd.dma_nodes[0].dw0.size = lcd.bb_size; + lcd.dma_nodes[0].dw0.length = lcd.bb_size; + lcd.dma_nodes[0].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_CPU; + lcd.dma_nodes[0].buffer = lcd.bounce_buffer[0]; + + lcd.dma_nodes[1].dw0.suc_eof = 1; + lcd.dma_nodes[1].dw0.size = lcd.bb_size; + lcd.dma_nodes[1].dw0.length = lcd.bb_size; + lcd.dma_nodes[1].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_CPU; + lcd.dma_nodes[1].buffer = lcd.bounce_buffer[1]; + + // loop end back to start + lcd.dma_nodes[0].next = &lcd.dma_nodes[1]; + lcd.dma_nodes[1].next = &lcd.dma_nodes[0]; + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) + gdma_channel_alloc_config_t dma_chan_config = { 0 }; + ESP_RETURN_ON_ERROR( + gdma_new_ahb_channel(&dma_chan_config, &lcd.dma_chan, NULL), TAG, "alloc DMA channel failed" + ); + // IDF 6.0's gdma_connect no longer implicitly resets the channel (removed). + // Do it explicitly to match IDF 5.4 behavior. + gdma_reset(lcd.dma_chan); +#else + // alloc DMA channel and connect to LCD peripheral + gdma_channel_alloc_config_t dma_chan_config = { + .direction = GDMA_CHANNEL_DIRECTION_TX, + }; + ESP_RETURN_ON_ERROR( + gdma_new_channel(&dma_chan_config, &lcd.dma_chan), TAG, "alloc DMA channel failed" + ); +#endif + gdma_trigger_t trigger = GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_LCD, 0); + ESP_RETURN_ON_ERROR(gdma_connect(lcd.dma_chan, trigger), TAG, "dma connect error"); +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) + gdma_strategy_config_t dma_strategy = { + .eof_till_data_popped = false, + }; + gdma_apply_strategy(lcd.dma_chan, &dma_strategy); + + gdma_transfer_config_t trans_cfg = { + .max_data_burst_size = 64, + .access_ext_mem = true, + }; + ESP_RETURN_ON_ERROR(gdma_config_transfer(lcd.dma_chan, &trans_cfg), TAG, "dma setup error"); +#else + gdma_transfer_ability_t ability = { + .psram_trans_align = 64, + .sram_trans_align = 4, + }; + ESP_RETURN_ON_ERROR(gdma_set_transfer_ability(lcd.dma_chan, &ability), TAG, "dma setup error"); +#endif + + gdma_tx_event_callbacks_t cbs = { + .on_trans_eof = lcd_rgb_panel_eof_handler, + }; + ESP_RETURN_ON_ERROR( + gdma_register_tx_event_callbacks(lcd.dma_chan, &cbs, NULL), TAG, "dma setup error" + ); + + return ESP_OK; +} + +void deinit_dma_trans_link() { + gdma_reset(lcd.dma_chan); + gdma_disconnect(lcd.dma_chan); + gdma_del_channel(lcd.dma_chan); +} + +/** + * Configure LCD peripheral and auxiliary GPIOs + */ +static esp_err_t init_bus_gpio() { + const int DATA_LINES[16] = { + lcd.config.bus.data[14], lcd.config.bus.data[15], lcd.config.bus.data[12], + lcd.config.bus.data[13], lcd.config.bus.data[10], lcd.config.bus.data[11], + lcd.config.bus.data[8], lcd.config.bus.data[9], lcd.config.bus.data[6], + lcd.config.bus.data[7], lcd.config.bus.data[4], lcd.config.bus.data[5], + lcd.config.bus.data[2], lcd.config.bus.data[3], lcd.config.bus.data[0], + lcd.config.bus.data[1], + }; + + // connect peripheral signals via GPIO matrix + for (size_t i = (16 - lcd.config.bus_width); i < 16; i++) { + gpio_hal_func_sel(&hal, DATA_LINES[i], PIN_FUNC_GPIO); + gpio_set_direction(DATA_LINES[i], GPIO_MODE_OUTPUT); + esp_rom_gpio_connect_out_signal(DATA_LINES[i], LCD_PERIPH_SIG(data_sigs[i]), false, false); + } + gpio_hal_func_sel(&hal, lcd.config.bus.leh, PIN_FUNC_GPIO); + gpio_set_direction(lcd.config.bus.leh, GPIO_MODE_OUTPUT); + gpio_hal_func_sel(&hal, lcd.config.bus.clock, PIN_FUNC_GPIO); + gpio_set_direction(lcd.config.bus.clock, GPIO_MODE_OUTPUT); + gpio_hal_func_sel(&hal, lcd.config.bus.start_pulse, PIN_FUNC_GPIO); + gpio_set_direction(lcd.config.bus.start_pulse, GPIO_MODE_OUTPUT); + + esp_rom_gpio_connect_out_signal(lcd.config.bus.leh, LCD_PERIPH_SIG(hsync_sig), false, false); + esp_rom_gpio_connect_out_signal(lcd.config.bus.clock, LCD_PERIPH_SIG(pclk_sig), false, false); + esp_rom_gpio_connect_out_signal( + lcd.config.bus.start_pulse, LCD_PERIPH_SIG(de_sig), false, false + ); + + gpio_config_t vsync_gpio_conf = { + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = 1ull << lcd.config.bus.stv, + }; + gpio_config(&vsync_gpio_conf); + gpio_set_level(lcd.config.bus.stv, 1); + return ESP_OK; +} + +/** + * Reset bus GPIO pin functions. + */ +static void deinit_bus_gpio() { + for (size_t i = (16 - lcd.config.bus_width); i < 16; i++) { + gpio_reset_pin(lcd.config.bus.data[i]); + } + + gpio_reset_pin(lcd.config.bus.leh); + gpio_reset_pin(lcd.config.bus.clock); + gpio_reset_pin(lcd.config.bus.start_pulse); + gpio_reset_pin(lcd.config.bus.stv); +} + +/** + * Check if the PSRAM cache is properly configured. + */ +static void check_cache_configuration() { + if (EPDIY_DATA_CACHE_LINE_SIZE < 64) { + ESP_LOGE( + "epdiy", + "cache line size is set to %d (< 64B)! This will degrade performance, please update " + "this option in menuconfig.", + EPDIY_DATA_CACHE_LINE_SIZE + ); + ESP_LOGE( + "epdiy", + "If you are on arduino, you can't set this option yourself, you'll need to use a lower " + "speed." + ); + ESP_LOGE( + "epdiy", + "Reducing the pixel clock from %d MHz to %d MHz for now!", + lcd.config.pixel_clock / 1000 / 1000, + lcd.config.pixel_clock / 1000 / 1000 / 2 + ); + lcd.config.pixel_clock = lcd.config.pixel_clock / 2; + + // fixme: this would be nice, but doesn't work :( + // uint32_t d_autoload = Cache_Suspend_DCache(); + /// Cache_Set_DCache_Mode(CACHE_SIZE_FULL, CACHE_4WAYS_ASSOC, CACHE_LINE_SIZE_32B); + // Cache_Invalidate_DCache_All(); + // Cache_Resume_DCache(d_autoload); + } +} + +/** + * Assign LCD configuration parameters from a given configuration, without allocating memory or + * touching the LCD peripheral config. + */ +static void assign_lcd_parameters_from_config( + const LcdEpdConfig_t* config, int display_width, int display_height +) { + // copy over the configuraiton object + memcpy(&lcd.config, config, sizeof(LcdEpdConfig_t)); + + // Make sure the bounce buffers divide the display height evenly. + lcd.display_lines = (((display_height + 7) / 8) * 8); + + lcd.line_bytes = display_width / 4; + lcd.lcd_res_h = lcd.line_bytes / (lcd.config.bus_width / 8); + + // With 8 bit bus width, we need a dummy cycle before the actual data, + // because the LCD peripheral behaves weirdly. + // Also see: + // https://blog.adafruit.com/2022/06/14/esp32uesday-hacking-the-esp32-s3-lcd-peripheral/ + lcd.dummy_bytes = lcd.config.bus_width / 8; + + // each bounce buffer holds a number of lines with data + dummy bytes each + lcd.bb_size = BOUNCE_BUF_LINES * (lcd.line_bytes + lcd.dummy_bytes); + + check_cache_configuration(); + + ESP_LOGI(TAG, "using resolution %dx%d", lcd.lcd_res_h, lcd.display_lines); +} + +/** + * Allocate buffers for LCD driver operation. + */ +static esp_err_t allocate_lcd_buffers() { + uint32_t dma_flags = MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA; + + // allocate bounce buffers + for (int i = 0; i < 2; i++) { + lcd.bounce_buffer[i] = heap_caps_aligned_calloc(4, 1, lcd.bb_size, dma_flags); + ESP_RETURN_ON_FALSE(lcd.bounce_buffer[i], ESP_ERR_NO_MEM, TAG, "install interrupt failed"); + } + + // So far, I haven't seen any displays with > 4096 pixels per line, + // so we only need one DMA node for now. + assert(lcd.bb_size < DMA_DESCRIPTOR_BUFFER_MAX_SIZE); + lcd.dma_nodes = heap_caps_calloc(1, sizeof(dma_descriptor_t) * 2, dma_flags); + ESP_RETURN_ON_FALSE(lcd.dma_nodes, ESP_ERR_NO_MEM, TAG, "no mem for dma nodes"); + return ESP_OK; +} + +static void free_lcd_buffers() { + for (int i = 0; i < 2; i++) { + uint8_t* buf = lcd.bounce_buffer[i]; + if (buf != NULL) { + heap_caps_free(buf); + lcd.bounce_buffer[i] = NULL; + } + } + + if (lcd.dma_nodes != NULL) { + heap_caps_free(lcd.dma_nodes); + lcd.dma_nodes = NULL; + } +} + +/** + * Initialize the LCD peripheral itself and install interrupts. + */ +static esp_err_t init_lcd_peripheral() { + esp_err_t ret = ESP_OK; + + // enable APB to access LCD registers +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) + PERIPH_RCC_ACQUIRE_ATOMIC(soc_lcd_rgb_signals[0].module, ref_count) { + if (ref_count == 0) { + lcd_ll_enable_bus_clock(0, true); + lcd_ll_reset_register(0); + } + } +#else + periph_module_enable(PERIPH_LCD_CAM_MODULE); + periph_module_reset(PERIPH_LCD_CAM_MODULE); +#endif + + lcd_hal_init(&lcd.hal, 0); +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) + // IDF 6 requires explicit clock-tree enable; otherwise PCLK can fall back to a slow source. + ESP_RETURN_ON_ERROR( + esp_clk_tree_enable_src((soc_module_clk_t)LCD_CLK_SRC_PLL240M, true), + TAG, + "enable lcd clk src failed" + ); + ESP_RETURN_ON_ERROR( + esp_clk_tree_src_get_freq_hz( + (soc_module_clk_t)LCD_CLK_SRC_PLL240M, + ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, + &lcd.src_clk_hz + ), + TAG, + "get lcd clk freq failed" + ); + PERIPH_RCC_ATOMIC() { + lcd_ll_enable_clock(lcd.hal.dev, true); + lcd_ll_select_clk_src(lcd.hal.dev, LCD_CLK_SRC_PLL240M); + } +#else + lcd.src_clk_hz = 240000000; + lcd_ll_enable_clock(lcd.hal.dev, true); + lcd_ll_select_clk_src(lcd.hal.dev, LCD_CLK_SRC_PLL240M); +#endif + ESP_LOGI(TAG, "lcd src clk: %u Hz", (unsigned)lcd.src_clk_hz); + ESP_RETURN_ON_ERROR(ret, TAG, "set source clock failed"); + + lcd_ll_fifo_reset(lcd.hal.dev); + lcd_ll_reset(lcd.hal.dev); + + // install interrupt service, (LCD peripheral shares the interrupt source with Camera by + // different mask) + int flags = ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_SHARED + | ESP_INTR_FLAG_LOWMED; + + int source = LCD_PERIPH_SIG(irq_id); + uint32_t status = (uint32_t)lcd_ll_get_interrupt_status_reg(lcd.hal.dev); + ret = esp_intr_alloc_intrstatus( + source, flags, status, LCD_LL_EVENT_VSYNC_END, lcd_isr_vsync, NULL, &lcd.vsync_intr + ); + ESP_RETURN_ON_ERROR(ret, TAG, "install interrupt failed"); + + status = (uint32_t)lcd_ll_get_interrupt_status_reg(lcd.hal.dev); + ret = esp_intr_alloc_intrstatus( + source, flags, status, LCD_LL_EVENT_TRANS_DONE, lcd_isr_vsync, NULL, &lcd.done_intr + ); + ESP_RETURN_ON_ERROR(ret, TAG, "install interrupt failed"); + + // pixel clock phase and polarity + lcd_ll_set_clock_idle_level(lcd.hal.dev, false); + lcd_ll_set_pixel_clock_edge(lcd.hal.dev, false); + + // enable RGB mode and set data width + lcd_ll_enable_rgb_mode(lcd.hal.dev, true); +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) + lcd_ll_set_dma_read_stride(lcd.hal.dev, lcd.config.bus_width); + lcd_ll_set_data_wire_width(lcd.hal.dev, lcd.config.bus_width); +#else + lcd_ll_set_data_width(lcd.hal.dev, lcd.config.bus_width); +#endif + lcd_ll_set_phase_cycles(lcd.hal.dev, 0, (lcd.dummy_bytes > 0), 1); // enable data phase only + + // number of data cycles is controlled by DMA buffer size + lcd_ll_enable_output_always_on(lcd.hal.dev, true); + lcd_ll_set_idle_level(lcd.hal.dev, false, true, true); + + // configure blank region timing + // RGB panel always has a front and back blank (porch region) + lcd_ll_set_blank_cycles(lcd.hal.dev, 1, 1); + + // output hsync even in porch region? + lcd_ll_enable_output_hsync_in_porch_region(lcd.hal.dev, false); + // send next frame automatically in stream mode + lcd_ll_enable_auto_next_frame(lcd.hal.dev, false); + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) + PERIPH_RCC_ATOMIC() { + lcd_ll_enable_interrupt(lcd.hal.dev, LCD_LL_EVENT_VSYNC_END, true); + lcd_ll_enable_interrupt(lcd.hal.dev, LCD_LL_EVENT_TRANS_DONE, true); + } +#else + lcd_ll_enable_interrupt(lcd.hal.dev, LCD_LL_EVENT_VSYNC_END, true); + lcd_ll_enable_interrupt(lcd.hal.dev, LCD_LL_EVENT_TRANS_DONE, true); +#endif + + // clear any stale interrupt events before enabling the ISR + lcd_ll_clear_interrupt_status(lcd.hal.dev, UINT32_MAX); + // enable intr + esp_intr_enable(lcd.vsync_intr); + esp_intr_enable(lcd.done_intr); + return ret; +} + +static void deinit_lcd_peripheral() { + // disable and free interrupts + esp_intr_disable(lcd.vsync_intr); + esp_intr_disable(lcd.done_intr); + esp_intr_free(lcd.vsync_intr); + esp_intr_free(lcd.done_intr); + + lcd_ll_stop(lcd.hal.dev); +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) + PERIPH_RCC_ATOMIC() { + lcd_ll_enable_clock(lcd.hal.dev, false); + } +#else + lcd_ll_enable_clock(lcd.hal.dev, false); +#endif + + lcd_ll_fifo_reset(lcd.hal.dev); + lcd_ll_reset(lcd.hal.dev); + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) + PERIPH_RCC_RELEASE_ATOMIC(soc_lcd_rgb_signals[0].module, ref_count) { + if (ref_count == 0) { + lcd_ll_enable_bus_clock(0, false); + } + } +#else + periph_module_reset(PERIPH_LCD_CAM_MODULE); + periph_module_disable(PERIPH_LCD_CAM_MODULE); +#endif +} + +/** + * Configure the LCD driver for epdiy. + */ +void epd_lcd_init(const LcdEpdConfig_t* config, int display_width, int display_height) { + esp_err_t ret = ESP_OK; + assign_lcd_parameters_from_config(config, display_width, display_height); + + ret = allocate_lcd_buffers(); + ESP_GOTO_ON_ERROR(ret, err, TAG, "lcd buffer allocation failed"); + + ret = init_lcd_peripheral(); + ESP_GOTO_ON_ERROR(ret, err, TAG, "lcd peripheral init failed"); + + ret = init_dma_trans_link(); + ESP_GOTO_ON_ERROR(ret, err, TAG, "install DMA failed"); + + ret = init_bus_gpio(); + ESP_GOTO_ON_ERROR(ret, err, TAG, "configure GPIO failed"); + + init_ckv_rmt(); + + // setup driver state + epd_lcd_set_pixel_clock_MHz(lcd.config.pixel_clock / 1000 / 1000); + epd_lcd_line_source_cb(NULL, NULL); + + ESP_LOGI(TAG, "LCD init done."); + return; +err: + ESP_LOGE(TAG, "LCD initialization failed!"); + abort(); +} + +/** + * Deinitializue the LCD driver, i.e., free resources and peripherals. + */ +void epd_lcd_deinit() { + epd_lcd_line_source_cb(NULL, NULL); + + deinit_bus_gpio(); + deinit_lcd_peripheral(); + deinit_dma_trans_link(); + free_lcd_buffers(); + deinit_ckv_rmt(); + + ESP_LOGI(TAG, "LCD deinitialized."); +} + +void epd_lcd_set_line_timing(const LcdLineTiming_t* timing) { + if (timing == NULL) return; + lcd.config.line = *timing; +} + +void epd_lcd_set_pixel_clock_MHz(int frequency) { + lcd.config.pixel_clock = frequency * 1000 * 1000; + if (lcd.src_clk_hz == 0) { + lcd.src_clk_hz = 240000000; + } + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) + hal_utils_clk_div_t clk_div = {}; +/** + * There was a change in the parameters of this function in this commit: + * https://github.com/espressif/esp-idf/commit/d39388fe4f4c5bfb0b52df9177307b1688f41016#diff-2df607d77e3f6e350bab8eb31cfd914500ae42744564e1640cec47006cc17a9c + * There are different builds with the same IDF minor version, some with, some without the commit. + * So we try to select the correct one by checking if the flag value is defined. + */ +#ifdef LCD_HAL_PCLK_FLAG_ALLOW_EQUAL_SYSCLK + uint32_t freq + = lcd_hal_cal_pclk_freq(&lcd.hal, lcd.src_clk_hz, lcd.config.pixel_clock, 0, &clk_div); +#else + uint32_t freq + = lcd_hal_cal_pclk_freq(&lcd.hal, lcd.src_clk_hz, lcd.config.pixel_clock, &clk_div); +#endif +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) + PERIPH_RCC_ATOMIC() { + lcd_ll_set_group_clock_coeff( + lcd.hal.dev, (int)clk_div.integer, (int)clk_div.denominator, (int)clk_div.numerator + ); + } +#else + lcd_ll_set_group_clock_coeff( + &LCD_CAM, (int)clk_div.integer, (int)clk_div.denominator, (int)clk_div.numerator + ); +#endif +#else + uint32_t freq = lcd_hal_cal_pclk_freq(&lcd.hal, lcd.src_clk_hz, lcd.config.pixel_clock, 0); +#endif + + ESP_LOGI(TAG, "pclk freq: %u Hz (src %u Hz)", (unsigned)freq, (unsigned)lcd.src_clk_hz); + rebuild_line_geometry(frequency); + ckv_rmt_build_signal(); + ESP_LOGI( + TAG, "line width: %d.%dus, %d cycles, ckv high %d.%dus", lcd.line_time_01us / 10, + lcd.line_time_01us % 10, lcd.line_cycles, lcd.ckv_high_time / 10, lcd.ckv_high_time % 10 + ); +} + +void IRAM_ATTR epd_lcd_start_frame() { + int initial_lines = min(LINE_BATCH, lcd.display_lines); + + // hsync: pulse width, back porch, active width, front porch + const LcdLineTiming_t* t = &lcd.config.line; + int end_line = lcd.line_cycles - lcd.lcd_res_h - t->le_high_time - t->line_front_porch; + lcd_ll_set_horizontal_timing( + lcd.hal.dev, + t->le_high_time - (lcd.dummy_bytes > 0), + t->line_front_porch, + // a dummy byte is neeed in 8 bit mode to work around LCD peculiarities + lcd.lcd_res_h + (lcd.dummy_bytes > 0), + end_line + ); + lcd_ll_set_vertical_timing(lcd.hal.dev, 1, 0, initial_lines, 1); + + // generate the hsync at the very beginning of line + lcd_ll_set_hsync_position(lcd.hal.dev, 1); + + // reset FIFO of DMA and LCD, incase there remains old frame data + gdma_reset(lcd.dma_chan); + lcd_ll_stop(lcd.hal.dev); + lcd_ll_reset(lcd.hal.dev); + lcd_ll_fifo_reset(lcd.hal.dev); + lcd_ll_enable_auto_next_frame(lcd.hal.dev, true); + + lcd.batches = 0; + lcd.bb_eof_count = 0; + fill_bounce_buffer(lcd.bounce_buffer[0]); + fill_bounce_buffer(lcd.bounce_buffer[1]); + + // the start of DMA should be prior to the start of LCD engine + gdma_start(lcd.dma_chan, (intptr_t)&lcd.dma_nodes[0]); + + // enter a critical section to ensure the frame start timing is correct + taskENTER_CRITICAL(&frame_start_spinlock); + + // delay 1us is sufficient for DMA to pass data to LCD FIFO + // in fact, this is only needed when LCD pixel clock is set too high + gpio_set_level(lcd.config.bus.stv, 0); + // esp_rom_delay_us(1); + // for picture clarity, it seems to be important to start CKV at a "good" + // time, seemingly start or towards end of line. + start_ckv_cycles(initial_lines + 5); + esp_rom_delay_us(lcd.line_length_us); + gpio_set_level(lcd.config.bus.stv, 1); + esp_rom_delay_us(lcd.line_length_us); + esp_rom_delay_us(lcd.ckv_high_time / 10); + + // start LCD engine + lcd_ll_start(lcd.hal.dev); + + taskEXIT_CRITICAL(&frame_start_spinlock); +} + +#else + +/// Dummy implementation to link on the old ESP32 +void epd_lcd_init(const LcdEpdConfig_t* config, int display_width, int display_height) { + assert(false); +} + +#endif // S3 Target diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/src/output_lcd/lcd_driver.h b/firmware/targets/mindreset_read_pico/components/epdiy/src/output_lcd/lcd_driver.h new file mode 100755 index 0000000..bace925 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/src/output_lcd/lcd_driver.h @@ -0,0 +1,13 @@ +#pragma once + +#include "epd_lcd.h" + +#include +#include + +typedef bool (*line_cb_func_t)(void*, uint8_t*); +typedef void (*frame_done_func_t)(void*); + +void epd_lcd_frame_done_cb(frame_done_func_t, void* payload); +void epd_lcd_line_source_cb(line_cb_func_t, void* payload); +void epd_lcd_start_frame(); diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/src/output_lcd/render_lcd.c b/firmware/targets/mindreset_read_pico/components/epdiy/src/output_lcd/render_lcd.c new file mode 100755 index 0000000..192d564 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/src/output_lcd/render_lcd.c @@ -0,0 +1,282 @@ +#include +#include + +#include "../output_common/render_method.h" + +#ifdef RENDER_METHOD_LCD + +#include +#include +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) +#if __has_include() +#include +#elif __has_include() +#include +#endif +#else +#include +#endif + +#include "../epd_internals.h" +#include "../output_common/line_queue.h" +#include "../output_common/lut.h" +#include "../output_common/render_context.h" +#include "epd_board.h" +#include "epdiy.h" +#include "lcd_driver.h" +#include "render_lcd.h" + +// declare vector optimized line mask application. +void epd_apply_line_mask_VE(uint8_t* line, const uint8_t* mask, int mask_len); + +__attribute__((optimize("O3"))) static bool IRAM_ATTR +retrieve_line_isr(RenderContext_t* ctx, uint8_t* buf) { + if (ctx->lines_consumed >= ctx->lines_total) { + return false; + } + int thread = ctx->line_threads[ctx->lines_consumed]; + if (thread >= NUM_RENDER_THREADS) { + ctx->error |= EPD_DRAW_EMPTY_LINE_QUEUE; + memset(buf, 0x00, ctx->display_width / 4); + ctx->lines_consumed += 1; + return pdFALSE; + } + + LineQueue_t* lq = &ctx->line_queues[thread]; + + BaseType_t awoken = pdFALSE; + + if (lq_read(lq, buf) != 0) { + ctx->error |= EPD_DRAW_EMPTY_LINE_QUEUE; + memset(buf, 0x00, ctx->display_width / 4); + } + + if (ctx->lines_consumed >= ctx->display_height) { + memset(buf, 0x00, ctx->display_width / 4); + } + ctx->lines_consumed += 1; + return awoken; +} + +/// start the next frame in the current update cycle +static void IRAM_ATTR handle_lcd_frame_done(RenderContext_t* ctx) { + epd_lcd_frame_done_cb(NULL, NULL); + epd_lcd_line_source_cb(NULL, NULL); + + // 扫描先于供数结束时解除生产者等待,禁止残留行进入下一相位。 + // Release producers if scan ends before consumption; never carry stale lines into the next phase. + if (ctx->lines_consumed < ctx->lines_total) { + ctx->error |= EPD_DRAW_EMPTY_LINE_QUEUE; + } + + BaseType_t task_awoken = pdFALSE; + xSemaphoreGiveFromISR(ctx->frame_done, &task_awoken); + + portYIELD_FROM_ISR(); +} + +void lcd_do_update(RenderContext_t* ctx) { + epd_set_mode(1); + + for (uint8_t k = 0; k < ctx->cycle_frames; k++) { + epd_lcd_frame_done_cb((frame_done_func_t)handle_lcd_frame_done, ctx); + prepare_context_for_next_frame(ctx); + + // start both feeder tasks + xTaskNotifyGive(ctx->feed_tasks[!xPortGetCoreID()]); + xTaskNotifyGive(ctx->feed_tasks[xPortGetCoreID()]); + + // transmission is started in renderer threads, now wait util it's done + xSemaphoreTake(ctx->frame_done, portMAX_DELAY); + + for (int i = 0; i < NUM_RENDER_THREADS; i++) { + xSemaphoreTake(ctx->feed_done_smphr[i], portMAX_DELAY); + } + + // DMA 回调和生产者均已停止后再清队列,由调用方执行欠载恢复。 + // Reset only after DMA callbacks and producers stop; let the caller recover from underrun. + if (ctx->error) { + for (int i = 0; i < NUM_RENDER_THREADS; i++) { + lq_reset(&ctx->line_queues[i]); + } + break; + } + + ctx->current_frame++; + + // make the watchdog happy. + vTaskDelay(0); + } + + epd_lcd_line_source_cb(NULL, NULL); + epd_lcd_frame_done_cb(NULL, NULL); + + epd_set_mode(0); +} + +__attribute__((optimize("O3"))) static bool IRAM_ATTR +push_pixels_isr(RenderContext_t* ctx, uint8_t* buf) { + // Output no-op outside of drawn area + if (ctx->lines_consumed < ctx->area.y) { + memset(buf, 0, ctx->display_width / 4); + } else if (ctx->lines_consumed >= ctx->area.y + ctx->area.height) { + memset(buf, 0, ctx->display_width / 4); + } else { + memcpy(buf, ctx->static_line_buffer, ctx->display_width / 4); + } + ctx->lines_consumed += 1; + return pdFALSE; +} + +/** + * Populate the line mask for use in epd_push_pixels. + */ +static void push_pixels_populate_line(RenderContext_t* ctx, int color) { + // Select fill pattern by draw color + int fill_byte = 0; + switch (color) { + case 0: + fill_byte = DARK_BYTE; + break; + case 1: + fill_byte = CLEAR_BYTE; + break; + default: + fill_byte = 0x00; + } + + // Compute a line mask based on the drawn area + uint8_t* dirtyness = malloc(ctx->display_width / 2); + assert(dirtyness != NULL); + + memset(dirtyness, 0, ctx->display_width / 2); + + for (int i = 0; i < ctx->display_width; i++) { + if ((i >= ctx->area.x) && (i < ctx->area.x + ctx->area.width)) { + dirtyness[i / 2] |= i % 2 ? 0xF0 : 0x0F; + } + } + epd_populate_line_mask(ctx->line_mask, dirtyness, ctx->display_width / 4); + + // mask the line pattern with the populated mask + memset(ctx->static_line_buffer, fill_byte, ctx->display_width / 4); + epd_apply_line_mask(ctx->static_line_buffer, ctx->line_mask, ctx->display_width / 4); + + free(dirtyness); +} + +void epd_push_pixels_lcd(RenderContext_t* ctx, short time, int color) { + ctx->current_frame = 0; + ctx->lines_total = ctx->display_height; + ctx->lines_consumed = 0; + ctx->static_line_buffer = malloc(ctx->display_width / 4); + assert(ctx->static_line_buffer != NULL); + + push_pixels_populate_line(ctx, color); + epd_lcd_frame_done_cb((frame_done_func_t)handle_lcd_frame_done, ctx); + epd_lcd_line_source_cb((line_cb_func_t)&push_pixels_isr, ctx); + + epd_set_mode(1); + epd_lcd_start_frame(); + xSemaphoreTake(ctx->frame_done, portMAX_DELAY); + epd_set_mode(0); + + free(ctx->static_line_buffer); + ctx->static_line_buffer = NULL; +} + +#define int_min(a, b) (((a) < (b)) ? (a) : (b)) + +// 每帧开扫之前先算好多少行。这段预填是帧与帧之间的空档:行队列只要跑在 DMA 前面 +// 就行,一行 16us 才消费一条,喂线程几微秒就算完一条,所以预填不需要很多;太少了 +// 会在 ISR 延迟抖动时欠载(EPD_DRAW_EMPTY_LINE_QUEUE)。 +static int s_prefill_lines = 127; + +void epd_lcd_set_prefill_lines(int lines) { + if (lines < 4) lines = 4; + s_prefill_lines = lines; +} + +int epd_lcd_prefill_lines(void) { + return s_prefill_lines; +} +__attribute__((optimize("O3"))) void IRAM_ATTR +lcd_calculate_frame(RenderContext_t* ctx, int thread_id) { + assert(ctx->lut_lookup_func != NULL); + uint8_t* input_line = ctx->feed_line_buffers[thread_id]; + + LineQueue_t* lq = &ctx->line_queues[thread_id]; + int l = 0; + + // 另一生产者可能已启动并报告欠载;不得再次启动同一帧。 + // The other producer may have started and underrun already; never start the frame twice. + if (ctx->error) return; + + // line must be able to hold 2-pixel-per-byte or 1-pixel-per-byte data + memset(input_line, 0x00, ctx->display_width); + + EpdRect area = ctx->area; + int min_y, max_y, bytes_per_line, _ppB; + const uint8_t* ptr_start; + get_buffer_params(ctx, &bytes_per_line, &ptr_start, &min_y, &max_y, &_ppB); + + assert(area.width == ctx->display_width && area.x == 0 && !ctx->error); + + // index of the line that triggers the frame output when processed + int trigger_line = int_min(s_prefill_lines, max_y - min_y); + + while (l = atomic_fetch_add(&ctx->lines_prepared, 1), l < ctx->lines_total) { + ctx->line_threads[l] = thread_id; + + // queue is sufficiently filled to fill both bounce buffers, frame + // can begin + if (l - min_y == trigger_line) { + epd_lcd_line_source_cb((line_cb_func_t)&retrieve_line_isr, ctx); + epd_lcd_start_frame(); + } + + if (l < min_y || l >= max_y + || (ctx->drawn_lines != NULL && !ctx->drawn_lines[l - area.y])) { + uint8_t* buf = NULL; + while (buf == NULL) { + // break in case of errors + if (ctx->error & EPD_DRAW_EMPTY_LINE_QUEUE) { + lq_reset(lq); + return; + }; + + buf = lq_current(lq); + } + memset(buf, 0x00, lq->element_size); + lq_commit(lq); + continue; + } + + uint32_t* lp = (uint32_t*)input_line; + const uint8_t* ptr = ptr_start + bytes_per_line * (l - min_y); + + Cache_Start_DCache_Preload((uint32_t)ptr, ctx->display_width, 0); + + lp = (uint32_t*)ptr; + + uint8_t* buf = NULL; + while (buf == NULL) { + // break in case of errors + if (ctx->error & EPD_DRAW_EMPTY_LINE_QUEUE) { + lq_reset(lq); + return; + }; + + buf = lq_current(lq); + } + + ctx->lut_lookup_func(lp, buf, ctx->conversion_lut, ctx->display_width); + + // apply the line mask + epd_apply_line_mask_VE(buf, ctx->line_mask, ctx->display_width / 4); + + lq_commit(lq); + } +} + +#endif diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/src/output_lcd/render_lcd.h b/firmware/targets/mindreset_read_pico/components/epdiy/src/output_lcd/render_lcd.h new file mode 100755 index 0000000..3c521e8 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/src/output_lcd/render_lcd.h @@ -0,0 +1,19 @@ +#pragma once + +#include "../output_common/render_context.h" + +/** + * Lighten / darken picels using the LCD driving method. + */ +void epd_push_pixels_lcd(RenderContext_t* ctx, short time, int color); + +/** + * Do a full update cycle with a configured context. + */ +void lcd_do_update(RenderContext_t* ctx); + +/** + * Worker thread for output calculation. + * In LCD mode, both threads do the same thing. + */ +void lcd_calculate_frame(RenderContext_t* ctx, int thread_id); diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/src/render.c b/firmware/targets/mindreset_read_pico/components/epdiy/src/render.c new file mode 100755 index 0000000..45cc50d --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/src/render.c @@ -0,0 +1,636 @@ +#include "render.h" + +#include "epd_board.h" +#include "epd_internals.h" +#include "epdiy.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "esp_heap_caps.h" +#include "output_common/line_queue.h" +#include "output_common/lut.h" +#include "output_common/render_context.h" +#include "output_common/render_method.h" +#include "output_lcd/render_lcd.h" + +static inline int min(int x, int y) { + return x < y ? x : y; +} +static inline int max(int x, int y) { + return x > y ? x : y; +} + +const int clear_cycle_time = 15; + +#define RTOS_ERROR_CHECK(x) \ + do { \ + esp_err_t __err_rc = (x); \ + if (__err_rc != pdPASS) { \ + abort(); \ + } \ + } while (0) + +static RenderContext_t render_context; + +void epd_push_pixels(EpdRect area, short time, int color) { + render_context.area = area; + epd_push_pixels_lcd(&render_context, time, color); +} + +///////////////////////////// Coordination /////////////////////////////// + +/** + * Find the waveform temperature range index for a given temperature in °C. + * If no range in the waveform data fits the given temperature, return the + * closest one. + * Returns -1 if the waveform does not contain any temperature range. + */ +int waveform_temp_range_index(const EpdWaveform* waveform, int temperature) { + int idx = 0; + if (waveform->num_temp_ranges == 0) { + return -1; + } + while (idx < waveform->num_temp_ranges - 1 && waveform->temp_intervals[idx].min < temperature) { + idx++; + } + return idx; +} + +static int get_waveform_index(const EpdWaveform* waveform, enum EpdDrawMode mode) { + for (int i = 0; i < waveform->num_modes; i++) { + if (waveform->mode_data[i]->type == (mode & 0x3F)) { + return i; + } + } + return -1; +} + +///////////////////////////// API Procedures ////////////////////////////////// + +/// Rounded up display height for even division into multi-line buffers. +// ---- 前导保持相跳过 ---- + +static bool s_leading_skip_enabled; +static int s_last_leading_skip; +static uint8_t s_present[256]; +static const uint8_t* s_present_for; + +// 按波形表缓存每个 (to<<4|from) 最早动作的相号,同一张表反复刷不用重算。 +static const EpdWaveformPhases* s_first_active_phases; +static uint8_t s_first_active[256]; + +void epd_set_leading_skip(bool enable) { + s_leading_skip_enabled = enable; +} + +void epd_leading_skip_discard(void) { + s_present_for = NULL; +} + +int epd_last_leading_skip(void) { + return s_last_leading_skip; +} + +static void build_first_active(const EpdWaveformPhases* phases) { + const uint8_t(*luts)[16][4] = (const uint8_t(*)[16][4])phases->luts; + for (int to = 0; to < 16; to++) { + for (int from = 0; from < 16; from++) { + int first = phases->phases; + for (int f = 0; f < phases->phases; f++) { + if ((luts[f][to][from / 4] >> (6 - 2 * (from % 4))) & 3) { + first = f; + break; + } + } + s_first_active[(to << 4) | from] = (uint8_t)first; + } + } + s_first_active_phases = phases; +} + +// 差分图里出现过哪些 (to<<4)|from 字节。差分图在 PSRAM 里,事后再扫一遍要 15ms 左右, +// 所以尽量在算差分时顺手统计:那时这一行刚写完还在 cache 里。s_present_for 记的是 +// 统计对应的差分缓冲,epd_draw_base 拿到同一块缓冲才用它,否则退回整块扫。 + +// 从 4bpp 的 to / from 源行统计:差分图是写出去的,PSRAM 写不进 cache,回头读一遍 +// 要 13ms;源行刚被差分读过,还在 cache 里。每个 32bit 字装 8 个像素,高低半字节 +// 各拼出 4 个 (to<<4)|from。相邻字都相同就跳过——白底整行都是 0xFF。 +__attribute__((optimize("O3"))) static void mark_present_line( + const uint8_t* to, const uint8_t* from, int fb_width +) { + const uint32_t* tw = (const uint32_t*)to; + const uint32_t* fw = (const uint32_t*)from; + uint32_t prev_t = ~tw[0]; + uint32_t prev_f = 0; + for (int i = 0; i < fb_width / 8; i++) { + const uint32_t t = tw[i]; + const uint32_t f = fw[i]; + if (t == prev_t && f == prev_f) continue; + prev_t = t; + prev_f = f; + const uint32_t hi = (t & 0xF0F0F0F0u) | ((f & 0xF0F0F0F0u) >> 4); + const uint32_t lo = ((t & 0x0F0F0F0Fu) << 4) | (f & 0x0F0F0F0Fu); + s_present[hi & 0xFF] = 1; + s_present[(hi >> 8) & 0xFF] = 1; + s_present[(hi >> 16) & 0xFF] = 1; + s_present[hi >> 24] = 1; + s_present[lo & 0xFF] = 1; + s_present[(lo >> 8) & 0xFF] = 1; + s_present[(lo >> 16) & 0xFF] = 1; + s_present[lo >> 24] = 1; + } +} + +// 取出现过的对里最早动作的相号。data 不是刚统计过的那块时整块扫(含 crop 外的部分, +// 只会让结果更保守)。 +__attribute__((optimize("O3"))) static int leading_skip_frames( + const EpdWaveformPhases* phases, const uint8_t* data, EpdRect area +) { + if (phases != s_first_active_phases) build_first_active(phases); + + uint8_t* present = s_present; + if (s_present_for != data) { + memset(s_present, 0, sizeof(s_present)); + const size_t n = (size_t)area.width * (size_t)area.height; + for (size_t i = 0; i < n; i++) s_present[data[i]] = 1; + } + + int skip = phases->phases; + for (int i = 0; i < 256; i++) { + if (present[i] && s_first_active[i] < skip) skip = s_first_active[i]; + } + // 全是保持也至少扫一相,把上一相残留在像素上的电压写成 0。 + if (skip >= phases->phases) skip = phases->phases - 1; + return skip < 0 ? 0 : skip; +} + +static inline int rounded_display_height() { + return (((epd_height() + 7) / 8) * 8); +} + +// FIXME: fix misleading naming: +// area -> buffer dimensions +// crop -> area taken out of buffer +enum EpdDrawError IRAM_ATTR epd_draw_base( + EpdRect area, + const uint8_t* data, + EpdRect crop_to, + enum EpdDrawMode mode, + int temperature, + const bool* drawn_lines, + const uint8_t* drawn_columns, + const EpdWaveform* waveform +) { + if (waveform == NULL) { + return EPD_DRAW_NO_PHASES_AVAILABLE; + } + int waveform_range = waveform_temp_range_index(waveform, temperature); + if (waveform_range < 0) { + return EPD_DRAW_NO_PHASES_AVAILABLE; + } + int waveform_index = 0; + uint8_t frame_count = 0; + const EpdWaveformPhases* waveform_phases = NULL; + + // no waveform required for monochrome mode + if (!(mode & MODE_EPDIY_MONOCHROME)) { + waveform_index = get_waveform_index(waveform, mode); + if (waveform_index < 0) { + return EPD_DRAW_MODE_NOT_FOUND; + } + + waveform_phases = waveform->mode_data[waveform_index]->range_data[waveform_range]; + // FIXME: error if not present + frame_count = waveform_phases->phases; + } else { + frame_count = 1; + } + + if (crop_to.width < 0 || crop_to.height < 0) { + return EPD_DRAW_INVALID_CROP; + } + + const bool crop = (crop_to.width > 0 && crop_to.height > 0); + if (crop + && (crop_to.width > area.width || crop_to.height > area.height || crop_to.x > area.width + || crop_to.y > area.height)) { + return EPD_DRAW_INVALID_CROP; + } + +#ifdef RENDER_METHOD_LCD + if (mode & MODE_PACKING_1PPB_DIFFERENCE && render_context.conversion_lut_size > 1 << 10) { + ESP_LOGI( + "epdiy", + "Using optimized vector implementation on the ESP32-S3, only 1k of %d LUT in use!", + render_context.conversion_lut_size + ); + } +#endif + + LutFunctionPair lut_functions = find_lut_functions(mode, render_context.conversion_lut_size); + if (lut_functions.build_func == NULL || lut_functions.lookup_func == NULL) { + ESP_LOGE("epdiy", "no output lookup method found for your mode and LUT size!"); + return EPD_DRAW_LOOKUP_NOT_IMPLEMENTED; + } + + render_context.area = area; + render_context.crop_to = crop_to; + render_context.waveform_range = waveform_range; + render_context.waveform_index = waveform_index; + render_context.mode = mode; + render_context.waveform = waveform; + render_context.error = EPD_DRAW_SUCCESS; + render_context.drawn_lines = drawn_lines; + render_context.data_ptr = data; + render_context.lut_build_func = lut_functions.build_func; + render_context.lut_lookup_func = lut_functions.lookup_func; + + render_context.lines_prepared = 0; + render_context.lines_consumed = 0; + render_context.lines_total = rounded_display_height(); + + // 试过按最后一个脏行截短帧长来省扫描时间(局部刷新时尾部大片行没有变化, + // 却照样在按 line length 逐行扫)。结论是不行:连续帧之间面板 gate 的复位 + // 依赖走完完整的一屏,start_frame 里那个 STV 脉冲拽不回第 0 行,于是第二、 + // 三帧的数据接着上一帧停下的行位置继续贴,画面会出现几个沿行方向并列、铺满 + // 全屏的副本。要再动这里得先用示波器确认 STV/CKV 的 gate 复位时序。 + + // 差分模式下先跳过整屏都还在保持的前导相位,见 epd_set_leading_skip()。单相位的 + // 波形(连续 DU 一次只发一相)没有可跳的,别为它扫一遍差分图。 + int skip = 0; + if (s_leading_skip_enabled && waveform_phases != NULL && waveform_phases->phases > 1 + && (mode & MODE_PACKING_1PPB_DIFFERENCE)) { + skip = leading_skip_frames(waveform_phases, data, area); + } + s_last_leading_skip = skip; + s_present_for = NULL; + + render_context.current_frame = skip; + render_context.cycle_frames = frame_count - skip; + render_context.phase_times = NULL; + if (waveform_phases != NULL && waveform_phases->phase_times != NULL) { + render_context.phase_times = waveform_phases->phase_times; + } + + epd_populate_line_mask( + render_context.line_mask, drawn_columns, render_context.display_width / 4 + ); + + lcd_do_update(&render_context); + + if (render_context.error & EPD_DRAW_EMPTY_LINE_QUEUE) { + ESP_LOGE("epdiy", "line buffer underrun occurred!"); + } + + if (render_context.error != EPD_DRAW_SUCCESS) { + return render_context.error; + } + return EPD_DRAW_SUCCESS; +} + +static void IRAM_ATTR render_thread(void* arg) { + int thread_id = (int)arg; + + while (true) { + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + lcd_calculate_frame(&render_context, thread_id); + + xSemaphoreGive(render_context.feed_done_smphr[thread_id]); + } +} + +void epd_clear_area(EpdRect area) { + epd_clear_area_cycles(area, 3, clear_cycle_time); // 3 ---> 1 20251124 test +} + +void epd_clear_area_cycles(EpdRect area, int cycles, int cycle_time) { + const short white_time = cycle_time; + const short dark_time = cycle_time; + + for (int c = 0; c < cycles; c++) { + for (int i = 0; i < 10; i++) { // 10 ---> 3 20251124 test + epd_push_pixels(area, dark_time, 0); + } + // 此面板擦白需要比压黑更多相位,否则容易停在浅灰。 + for (int i = 0; i < 13; i++) { + epd_push_pixels(area, white_time, 1); + } + for (int i = 0; i < 3; i++) { // 3 ---> 1 20251124 test + epd_push_pixels(area, white_time, 2); + } + } +} + +void epd_renderer_init(enum EpdInitOptions options) { + // Either the board should be set in menuconfig or the epd_set_board() must + // be called before epd_init() + assert((epd_current_board() != NULL)); + + epd_current_board()->init(epd_width()); + epd_control_reg_init(); + + render_context.display_width = epd_width(); + render_context.display_height = epd_height(); + + size_t lut_size = 0; + if (options & EPD_LUT_1K) { + lut_size = 1 << 10; + } else if (options & EPD_LUT_64K) { + lut_size = 1 << 16; + } else if (options == EPD_OPTIONS_DEFAULT) { +#ifdef RENDER_METHOD_LCD + lut_size = 1 << 10; +#else + lut_size = 1 << 16; +#endif + } else { + ESP_LOGE("epd", "invalid init options: %d", options); + return; + } + + ESP_LOGI("epd", "Space used for waveform LUT: %dK", lut_size / 1024); + render_context.conversion_lut + = (uint8_t*)heap_caps_malloc(lut_size, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + if (render_context.conversion_lut == NULL) { + ESP_LOGE("epd", "could not allocate LUT!"); + abort(); + } + render_context.conversion_lut_size = lut_size; + render_context.static_line_buffer = NULL; + + render_context.frame_done = xSemaphoreCreateBinary(); + + for (int i = 0; i < NUM_RENDER_THREADS; i++) { + render_context.feed_done_smphr[i] = xSemaphoreCreateBinary(); + } + + // When using the LCD peripheral, we may need padding lines to + // satisfy the bounce buffer size requirements + render_context.line_threads = (uint8_t*)heap_caps_malloc( + rounded_display_height(), MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL + ); + + int queue_len = 32; + if (options & EPD_FEED_QUEUE_32) { + queue_len = 32; + } else if (options & EPD_FEED_QUEUE_8) { + queue_len = 8; + } + if (epd_get_display()->bus_width == 16) { + queue_len = 64; + } + + if (render_context.conversion_lut == NULL) { + ESP_LOGE("epd", "could not allocate line mask!"); + abort(); + } + + render_context.line_mask + = heap_caps_aligned_alloc(16, epd_width() / 4, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + assert(render_context.line_mask != NULL); + + size_t queue_elem_size = render_context.display_width / 4; + + for (int i = 0; i < NUM_RENDER_THREADS; i++) { + render_context.line_queues[i] = lq_init(queue_len, queue_elem_size); + render_context.feed_line_buffers[i] = (uint8_t*)heap_caps_malloc( + render_context.display_width, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL + ); + assert(render_context.feed_line_buffers[i] != NULL); + RTOS_ERROR_CHECK(xTaskCreatePinnedToCore( + render_thread, + "epd_prep", + 1 << 12, + (void*)i, + configMAX_PRIORITIES - 1, + &render_context.feed_tasks[i], + i + )); + } +} + +void epd_renderer_deinit() { + const EpdBoardDefinition* epd_board = epd_current_board(); + + epd_board->poweroff(epd_ctrl_state()); + + for (int i = 0; i < NUM_RENDER_THREADS; i++) { + vTaskDelete(render_context.feed_tasks[i]); + lq_free(&render_context.line_queues[i]); + heap_caps_free(render_context.feed_line_buffers[i]); + vSemaphoreDelete(render_context.feed_done_smphr[i]); + } + + epd_control_reg_deinit(); + + if (epd_board->deinit) { + epd_board->deinit(); + } + + heap_caps_free(render_context.conversion_lut); + heap_caps_free(render_context.line_threads); + heap_caps_free(render_context.line_mask); + vSemaphoreDelete(render_context.frame_done); +} + +#ifdef RENDER_METHOD_LCD +uint32_t epd_interlace_4bpp_line_VE( + const uint8_t* to, + const uint8_t* from, + uint8_t* interlaced, + uint8_t* col_dirtyness, + int fb_width +); +#endif + +/** + * Interlaces `len` nibbles from the buffers `to` and `from` into `interlaced`. + * In the process, tracks which nibbles differ in `col_dirtyness`. + * Returns `1` if there are differences, `0` otherwise. + * Does not require special alignment of the buffers beyond 32 bit alignment. + */ +__attribute__((optimize("O3"))) static inline int _interlace_line_unaligned( + const uint8_t* to, const uint8_t* from, uint8_t* interlaced, uint8_t* col_dirtyness, int len +) { + int dirty = 0; + for (int x = 0; x < len; x++) { + uint8_t t = *(to + x / 2); + uint8_t f = *(from + x / 2); + t = (x % 2) ? (t >> 4) : (t & 0x0f); + f = (x % 2) ? (f >> 4) : (f & 0x0f); + col_dirtyness[x / 2] |= (t ^ f) << (4 * (x % 2)); + dirty |= (t ^ f); + interlaced[x] = (t << 4) | f; + } + return dirty; +} + +/** + * Interlaces the lines at `to`, `from` into `interlaced`. + * returns `1` if there are differences, `0` otherwise. + */ +__attribute__((optimize("O3"))) bool _epd_interlace_line( + const uint8_t* to, + const uint8_t* from, + uint8_t* interlaced, + uint8_t* col_dirtyness, + int fb_width +) { + // Use Vector Extensions with the ESP32-S3. + // Both input buffers should have the same alignment w.r.t. 16 bytes, + // as asserted in epd_difference_image_base. + uint32_t dirty = 0; + + // alignment boundaries in pixels + int unaligned_len_front_px = ((16 - (uint32_t)to % 16) * 2) % 32; + int unaligned_len_back_px = (((uint32_t)to + fb_width / 2) % 16) * 2; + int unaligned_back_start_px = fb_width - unaligned_len_back_px; + int aligned_len_px = fb_width - unaligned_len_front_px - unaligned_len_back_px; + + dirty |= _interlace_line_unaligned(to, from, interlaced, col_dirtyness, unaligned_len_front_px); + dirty |= epd_interlace_4bpp_line_VE( + to + unaligned_len_front_px / 2, + from + unaligned_len_front_px / 2, + interlaced + unaligned_len_front_px, + col_dirtyness + unaligned_len_front_px / 2, + aligned_len_px + ); + dirty |= _interlace_line_unaligned( + to + unaligned_back_start_px / 2, + from + unaligned_back_start_px / 2, + interlaced + unaligned_back_start_px, + col_dirtyness + unaligned_back_start_px / 2, + unaligned_len_back_px + ); + return dirty; +} + +void epd_difference_column_range(EpdRect crop_to, int* x_start, int* x_end) { + const int fb_width = epd_width(); + int start = crop_to.x & ~31; + int end = (crop_to.x + crop_to.width + 31) & ~31; + if (start < 0) start = 0; + if (end > fb_width) end = fb_width; + if (end < start) end = start; + *x_start = start; + *x_end = end; +} + +EpdRect epd_difference_image_base( + const uint8_t* to, + const uint8_t* from, + EpdRect crop_to, + int fb_width, + int fb_height, + uint8_t* interlaced, + bool* dirty_lines, + uint8_t* col_dirtyness +) { + assert(fb_width % 8 == 0); + assert(col_dirtyness != NULL); + + // these buffers should be allocated 16 byte aligned + assert((uint32_t)to % 16 == 0); + assert((uint32_t)from % 16 == 0); + assert((uint32_t)col_dirtyness % 16 == 0); + assert((uint32_t)interlaced % 16 == 0); + + memset(col_dirtyness, 0, fb_width / 2); + memset(dirty_lines, 0, sizeof(bool) * fb_height); + + int x_end = min(fb_width, crop_to.x + crop_to.width); + int y_end = min(fb_height, crop_to.y + crop_to.height); + + // 只算裁剪矩形覆盖的那段列(对齐到 32 像素 = 16 字节,向量实现要求两边缓冲同 + // 对齐)。局部刷新的区域旋转后往往覆盖几乎所有行,按整行算差分白花一倍多时间。 + // 段外的列不进 col_dirtyness,扫描时被行掩码归零,不会被驱动;highlevel 回写 + // back_fb 也只回写这一段,两边一致。 + int x_start, x_stop; + epd_difference_column_range(crop_to, &x_start, &x_stop); + const int seg_px = x_stop - x_start; + + if (s_leading_skip_enabled) memset(s_present, 0, sizeof(s_present)); + for (int y = crop_to.y; y < y_end; y++) { + uint32_t offset = y * fb_width / 2 + x_start / 2; + int dirty = _epd_interlace_line( + to + offset, from + offset, interlaced + offset * 2, col_dirtyness + x_start / 2, seg_px + ); + dirty_lines[y] = dirty; + if (s_leading_skip_enabled) mark_present_line(to + offset, from + offset, seg_px); + } + s_present_for = s_leading_skip_enabled ? interlaced : NULL; + + int min_x, min_y, max_x, max_y; + for (min_x = crop_to.x; min_x < x_end; min_x++) { + uint8_t mask = min_x % 2 ? 0xF0 : 0x0F; + if ((col_dirtyness[min_x / 2] & mask) != 0) + break; + } + for (max_x = x_end - 1; max_x >= crop_to.x; max_x--) { + uint8_t mask = min_x % 2 ? 0xF0 : 0x0F; + if ((col_dirtyness[max_x / 2] & mask) != 0) + break; + } + for (min_y = crop_to.y; min_y < y_end; min_y++) { + if (dirty_lines[min_y] != 0) + break; + } + for (max_y = y_end - 1; max_y >= crop_to.y; max_y--) { + if (dirty_lines[max_y] != 0) + break; + } + + EpdRect crop_rect = { + .x = min_x, + .y = min_y, + .width = max(max_x - min_x + 1, 0), + .height = max(max_y - min_y + 1, 0), + }; + + return crop_rect; +} + +EpdRect epd_difference_image( + const uint8_t* to, + const uint8_t* from, + uint8_t* interlaced, + bool* dirty_lines, + uint8_t* col_dirtyness +) { + return epd_difference_image_base( + to, + from, + epd_full_screen(), + epd_width(), + epd_height(), + interlaced, + dirty_lines, + col_dirtyness + ); +} + +EpdRect epd_difference_image_cropped( + const uint8_t* to, + const uint8_t* from, + EpdRect crop_to, + uint8_t* interlaced, + bool* dirty_lines, + uint8_t* col_dirtyness +) { + EpdRect result = epd_difference_image_base( + to, from, crop_to, epd_width(), epd_height(), interlaced, dirty_lines, col_dirtyness + ); + return result; +} diff --git a/firmware/targets/mindreset_read_pico/components/epdiy/src/render.h b/firmware/targets/mindreset_read_pico/components/epdiy/src/render.h new file mode 100755 index 0000000..9a0810e --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/epdiy/src/render.h @@ -0,0 +1,12 @@ +#pragma once + +#include "epdiy.h" +/** + * Initialize the EPD renderer and its render context. + */ +void epd_renderer_init(enum EpdInitOptions options); + +/** + * Deinitialize the EPD renderer and free up its resources. + */ +void epd_renderer_deinit(); diff --git a/firmware/targets/mindreset_read_pico/components/fca9555/CMakeLists.txt b/firmware/targets/mindreset_read_pico/components/fca9555/CMakeLists.txt new file mode 100644 index 0000000..75510ce --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/fca9555/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRCS "fca9555.c" + INCLUDE_DIRS "include" + REQUIRES esp_driver_gpio esp_driver_i2c +) diff --git a/firmware/targets/mindreset_read_pico/components/fca9555/Kconfig b/firmware/targets/mindreset_read_pico/components/fca9555/Kconfig new file mode 100644 index 0000000..c349913 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/fca9555/Kconfig @@ -0,0 +1,12 @@ +menu "FCA9555 I/O Expander" + + config FCA9555_I2C_TIMEOUT_MS + int "I2C transfer timeout (ms)" + default 100 + range 10 1000 + help + Fail fast on a missing chip. 100 ms is enough for an 8-byte + sequential read at 400 kHz. + 芯片无应答时尽快失败。400 kHz 下连读 8 字节,100 ms 足够。 + +endmenu diff --git a/firmware/targets/mindreset_read_pico/components/fca9555/LICENSE b/firmware/targets/mindreset_read_pico/components/fca9555/LICENSE new file mode 100644 index 0000000..500992f --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/fca9555/LICENSE @@ -0,0 +1,17 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +Copyright 2026 mindreset + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/firmware/targets/mindreset_read_pico/components/fca9555/README.md b/firmware/targets/mindreset_read_pico/components/fca9555/README.md new file mode 100644 index 0000000..ab0f8b0 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/fca9555/README.md @@ -0,0 +1,94 @@ +# fca9555 + +ESP-IDF 下的 **FCA9555** 16 位 I2C GPIO 扩展 C 驱动(徕飞)。寄存器和读写时序与 PCA9555 / TCA9555 / xCA9555 兼容。 + +ESP-IDF C driver for the **FCA9555** 16-bit I2C GPIO expander (Nyfea). Register map and I2C framing match PCA9555 / TCA9555 / xCA9555. + +组件名 / Registry name: `mindreset/fca9555` + +## 能力 / Features + +- Handle:调用方传入已有 `i2c_master` 总线、7 位地址、可选 INT# 脚。/ Handle-based API: pass an existing `i2c_master` bus, 7-bit address, optional INT# GPIO. +- 顺序读取 8 个寄存器,`fca9555_map_t` 覆盖 IN / OUT / INV / CFG。/ Sequential register read; `fca9555_map_t` overlays IN / OUT / INV / CFG. +- 缓存 CFG / INV,轮询时可只读 IN / OUT。/ Cached CFG / INV so polling can read IN / OUT only. +- INT# 配置为输入上拉;开漏、**不锁存**,读取 Input 即释放。/ INT# as input with pull-up; open-drain, **not latched**; read Input to release. +- 方向与极性写入只保留在驱动内,不建议暴露到产品界面。/ Direction and polarity writes stay in the driver; do not expose them on the product UI. + +## 硬件 / Hardware + +| 项目 / Item | 规格 / Spec | +|---|---| +| 芯片 / Part | FCA9555(徕飞),兼容 PCA9555 / FCA9555 (Nyfea), PCA9555-compatible | +| 总线 / Bus | I2C,400 kHz。读:先写 pointer,再 Repeated START / Read: write pointer, then Repeated START | +| 地址 / Address | A2/A1/A0 决定 `0x20`…`0x27`。`FCA9555_ADDR_FROM_A(1,0,0)` → `0x24` / `0x20`…`0x27` from A2/A1/A0 | +| INT# | 任意 GPIO;开漏低有效。GPIO41 不是 RTC 脚,只能浅睡唤醒 / Any GPIO; open-drain, active low. GPIO41 is not RTC, light sleep only | +| 上电 / Power-on | 全部输入(`CFG=0xFFFF`),输出 `0xFFFF`,极性 `0x0000` / All pins input (`CFG=0xFFFF`), outputs `0xFFFF`, invert `0x0000` | + +需要先 STOP 再读的芯片(例如 SY7636A)不能使用这种读法。本芯片要求 Repeated START。 + +Do not use Repeated START on chips that require STOP-then-read (for example SY7636A). This part requires Repeated START. + +## 安装 / Install + +```yaml +dependencies: + mindreset/fca9555: "^1.0.0" +``` + +或:/ Or: + +```bash +idf.py add-dependency "mindreset/fca9555^1.0.0" +``` + +I2C 超时在 `idf.py menuconfig` → Component config → FCA9555 I/O Expander(默认 100 ms)。 + +I2C timeout is in `idf.py menuconfig` → Component config → FCA9555 I/O Expander (default 100 ms). + +## 用法 / Quick start + +```c +#include "fca9555.h" + +fca9555_handle_t ioe = NULL; +fca9555_hw_t hw = FCA9555_HW_DEFAULT(); +hw.bus = i2c_bus; +hw.i2c_addr = FCA9555_ADDR_FROM_A(1, 0, 0); +hw.int_gpio = GPIO_NUM_41; + +if (fca9555_create(&hw, &ioe) == ESP_OK) { + fca9555_set_config(ioe, 0, 0x64); // P0.2 / P0.5 / P0.6 输入。/ Inputs on P0.2 / P0.5 / P0.6. + fca9555_set_output(ioe, 0, 0x81); + + fca9555_map_t map; + fca9555_read_io(ioe, &map); // IN/OUT 实时读取,CFG/INV 取缓存。/ IN/OUT live, CFG/INV cached. + if (fca9555_int_asserted(ioe)) { + fca9555_clear_int(ioe); + } +} +``` + +## 接口分组 / API groups + +| 分组 / Group | 函数 / Functions | +|---|---| +| 生命周期 / Lifecycle | `fca9555_create`, `fca9555_del` | +| 读数 / Read | `fca9555_read_reg`, `fca9555_read_n`, `fca9555_read_all`, `fca9555_read_io`, `fca9555_read_input` | +| 输出 / Output | `fca9555_set_output` | +| INT# | `fca9555_int_asserted`, `fca9555_int_level`, `fca9555_int_gpio`, `fca9555_clear_int` | +| 方向缓存 / Direction cache | `fca9555_dir_cached`, `fca9555_get_dir_cache` | +| 危险 / Dangerous | `fca9555_set_config`, `fca9555_set_inversion` | + +详见 [`include/fca9555.h`](include/fca9555.h)。 + +See [`include/fca9555.h`](include/fca9555.h). + +## 示例 / Example + +[`examples/read_ports`](examples/read_ports) 初始化 I2C、输出 8 个寄存器并轮询 INT#。引脚在 `idf.py menuconfig` 中配置。 + +[`examples/read_ports`](examples/read_ports) brings up I2C, dumps the eight registers, and polls INT#. Pins are in `idf.py menuconfig`. + +## 许可 / License + +Apache-2.0 diff --git a/firmware/targets/mindreset_read_pico/components/fca9555/examples/read_ports/CMakeLists.txt b/firmware/targets/mindreset_read_pico/components/fca9555/examples/read_ports/CMakeLists.txt new file mode 100644 index 0000000..c778506 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/fca9555/examples/read_ports/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 3.16) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(read_ports) diff --git a/firmware/targets/mindreset_read_pico/components/fca9555/examples/read_ports/main/CMakeLists.txt b/firmware/targets/mindreset_read_pico/components/fca9555/examples/read_ports/main/CMakeLists.txt new file mode 100644 index 0000000..93eb7ac --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/fca9555/examples/read_ports/main/CMakeLists.txt @@ -0,0 +1 @@ +idf_component_register(SRCS "main.c" REQUIRES fca9555 driver) diff --git a/firmware/targets/mindreset_read_pico/components/fca9555/examples/read_ports/main/Kconfig.projbuild b/firmware/targets/mindreset_read_pico/components/fca9555/examples/read_ports/main/Kconfig.projbuild new file mode 100644 index 0000000..e817d6a --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/fca9555/examples/read_ports/main/Kconfig.projbuild @@ -0,0 +1,25 @@ +menu "FCA9555 read_ports" + + config FCA9555_EXAMPLE_I2C_PORT + int "I2C port" + default 0 + range 0 1 + + config FCA9555_EXAMPLE_SDA + int "SDA GPIO" + default 39 + + config FCA9555_EXAMPLE_SCL + int "SCL GPIO" + default 40 + + config FCA9555_EXAMPLE_ADDR + hex "7-bit I2C address (A2=1 A1=0 A0=0 → 0x24)" + default 0x24 + + config FCA9555_EXAMPLE_INT + int "INT# GPIO (-1 = skip)" + default 41 + range -1 48 + +endmenu diff --git a/firmware/targets/mindreset_read_pico/components/fca9555/examples/read_ports/main/idf_component.yml b/firmware/targets/mindreset_read_pico/components/fca9555/examples/read_ports/main/idf_component.yml new file mode 100644 index 0000000..1b0d55a --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/fca9555/examples/read_ports/main/idf_component.yml @@ -0,0 +1,4 @@ +dependencies: + mindreset/fca9555: + version: "^1.0.0" + override_path: "../../.." diff --git a/firmware/targets/mindreset_read_pico/components/fca9555/examples/read_ports/main/main.c b/firmware/targets/mindreset_read_pico/components/fca9555/examples/read_ports/main/main.c new file mode 100644 index 0000000..1b2b993 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/fca9555/examples/read_ports/main/main.c @@ -0,0 +1,66 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * 转储 FCA9555 寄存器并轮询 INT#。 + * 引脚:idf.py menuconfig → FCA9555 read_ports + * + * Dump FCA9555 registers and poll INT#. + * Pins: idf.py menuconfig → FCA9555 read_ports + */ + +#include "fca9555.h" + +#include "driver/i2c_master.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "sdkconfig.h" + +static const char* TAG = "read_ports"; + +void app_main(void) { + i2c_master_bus_handle_t bus; + i2c_master_bus_config_t bus_cfg = { + .i2c_port = (i2c_port_t)CONFIG_FCA9555_EXAMPLE_I2C_PORT, + .sda_io_num = (gpio_num_t)CONFIG_FCA9555_EXAMPLE_SDA, + .scl_io_num = (gpio_num_t)CONFIG_FCA9555_EXAMPLE_SCL, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .flags.enable_internal_pullup = true, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&bus_cfg, &bus)); + + fca9555_handle_t ioe = NULL; + fca9555_config_t config = FCA9555_CONFIG_DEFAULT(); + config.i2c_addr = (uint8_t)CONFIG_FCA9555_EXAMPLE_ADDR; + if (CONFIG_FCA9555_EXAMPLE_INT >= 0) { + config.int_gpio = (gpio_num_t)CONFIG_FCA9555_EXAMPLE_INT; + } + if (fca9555_init(bus, &config, &ioe) != ESP_OK) { + ESP_LOGE(TAG, "init failed"); + return; + } + + fca9555_map_t map; + if (fca9555_read_all(ioe, &map) == ESP_OK) { + ESP_LOGI( + TAG, "IN=%04X OUT=%04X INV=%04X CFG=%04X INT=%d", + map.input, map.output, map.invert, map.config, + fca9555_int_level(ioe) + ); + } else { + ESP_LOGE(TAG, "read_all failed"); + } + + for (int i = 0; i < 40; i++) { + if (fca9555_int_asserted(ioe) && fca9555_read_io(ioe, &map) == ESP_OK) { + ESP_LOGI(TAG, "irq IN=%04X OUT=%04X", map.input, map.output); + (void)fca9555_clear_int(ioe); + } + vTaskDelay(pdMS_TO_TICKS(50)); + } + + fca9555_deinit(ioe); + ESP_ERROR_CHECK(i2c_del_master_bus(bus)); +} diff --git a/firmware/targets/mindreset_read_pico/components/fca9555/examples/read_ports/sdkconfig.defaults b/firmware/targets/mindreset_read_pico/components/fca9555/examples/read_ports/sdkconfig.defaults new file mode 100644 index 0000000..b01fdbe --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/fca9555/examples/read_ports/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_IDF_TARGET="esp32s3" diff --git a/firmware/targets/mindreset_read_pico/components/fca9555/fca9555.c b/firmware/targets/mindreset_read_pico/components/fca9555/fca9555.c new file mode 100644 index 0000000..ded4dd2 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/fca9555/fca9555.c @@ -0,0 +1,206 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * FCA9555 主机:写指针 + Repeated START 读,INT# 读 Input 松开。 + * + * FCA9555 host: pointer write + Repeated START read; reading Input releases INT#. + */ + +#include "fca9555.h" + +#include + +#include "esp_log.h" +#include "sdkconfig.h" + +#ifndef CONFIG_FCA9555_I2C_TIMEOUT_MS +#define CONFIG_FCA9555_I2C_TIMEOUT_MS FCA9555_I2C_TIMEOUT_MS_DEFAULT +#endif + +static const char* TAG = "fca9555"; + +struct fca9555_dev_t { + i2c_master_dev_handle_t dev; + gpio_num_t int_gpio; + int timeout_ms; + uint16_t invert; + uint16_t config; + bool dir_cached; +}; + +static bool gpio_ok(gpio_num_t gpio) { + return gpio != GPIO_NUM_NC && GPIO_IS_VALID_GPIO(gpio); +} + +static bool port_ok(int port) { + return port == 0 || port == 1; +} + +static void cache_dir(fca9555_handle_t h, uint16_t invert, uint16_t config) { + h->invert = invert; + h->config = config; + h->dir_cached = true; +} + +static void cache_port(uint16_t* word, int port, uint8_t value) { + if (port) { + *word = (uint16_t)((*word & 0x00FF) | ((uint16_t)value << 8)); + } else { + *word = (uint16_t)((*word & 0xFF00) | value); + } +} + +static esp_err_t cfg_int_gpio(gpio_num_t gpio) { + if (!gpio_ok(gpio)) return ESP_OK; + gpio_config_t io = { + .pin_bit_mask = 1ULL << gpio, + .mode = GPIO_MODE_INPUT, + .pull_up_en = GPIO_PULLUP_ENABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + return gpio_config(&io); +} + +static esp_err_t xfer_read(fca9555_handle_t h, uint8_t start, uint8_t* data, size_t n) { + // 先写指针再 Repeated START(手册图 5/6)。 + // / Pointer write then Repeated START (datasheet Figure 5/6). + return i2c_master_transmit_receive(h->dev, &start, 1, data, n, h->timeout_ms); +} + +static esp_err_t xfer_write(fca9555_handle_t h, uint8_t reg, uint8_t value) { + uint8_t data[2] = { reg, value }; + return i2c_master_transmit(h->dev, data, sizeof(data), h->timeout_ms); +} + +esp_err_t fca9555_init(i2c_master_bus_handle_t bus_handle, const fca9555_config_t* config, + fca9555_handle_t* handle) { + if (bus_handle == NULL || config == NULL || handle == NULL) return ESP_ERR_INVALID_ARG; + + fca9555_handle_t h = calloc(1, sizeof(*h)); + if (h == NULL) return ESP_ERR_NO_MEM; + + h->int_gpio = config->int_gpio; + h->timeout_ms = config->i2c_timeout_ms ? (int)config->i2c_timeout_ms + : CONFIG_FCA9555_I2C_TIMEOUT_MS; + h->config = 0xFFFF; + h->invert = 0; + + esp_err_t err = cfg_int_gpio(h->int_gpio); + if (err != ESP_OK) { + free(h); + return err; + } + + i2c_device_config_t dev_cfg = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = config->i2c_addr ? config->i2c_addr : FCA9555_ADDR_DEFAULT, + .scl_speed_hz = config->scl_speed_hz ? config->scl_speed_hz : FCA9555_I2C_HZ_DEFAULT, + }; + err = i2c_master_bus_add_device(bus_handle, &dev_cfg, &h->dev); + if (err != ESP_OK) { + ESP_LOGE(TAG, "I2C add failed: %s", esp_err_to_name(err)); + free(h); + return err; + } + + *handle = h; + return ESP_OK; +} + +esp_err_t fca9555_deinit(fca9555_handle_t h) { + if (h == NULL) return ESP_OK; + if (h->dev) i2c_master_bus_rm_device(h->dev); + free(h); + return ESP_OK; +} + +esp_err_t fca9555_read_reg(fca9555_handle_t h, uint8_t reg, uint8_t* value) { + if (h == NULL || value == NULL || reg > FCA9555_REG_CFG1) { + return ESP_ERR_INVALID_ARG; + } + return xfer_read(h, reg, value, 1); +} + +esp_err_t fca9555_read_n(fca9555_handle_t h, uint8_t start, uint8_t* data, size_t n) { + if (h == NULL || data == NULL || n == 0 || start > FCA9555_REG_CFG1) { + return ESP_ERR_INVALID_ARG; + } + return xfer_read(h, start, data, n); +} + +esp_err_t fca9555_read_all(fca9555_handle_t h, fca9555_map_t* map) { + if (h == NULL || map == NULL) return ESP_ERR_INVALID_ARG; + esp_err_t err = xfer_read(h, FCA9555_REG_IN0, (uint8_t*)map, sizeof(*map)); + if (err == ESP_OK) cache_dir(h, map->invert, map->config); + return err; +} + +esp_err_t fca9555_read_io(fca9555_handle_t h, fca9555_map_t* map) { + if (h == NULL || map == NULL) return ESP_ERR_INVALID_ARG; + esp_err_t err = xfer_read(h, FCA9555_REG_IN0, (uint8_t*)map, 4); + if (err == ESP_OK) { + map->invert = h->invert; + map->config = h->config; + } + return err; +} + +esp_err_t fca9555_read_input(fca9555_handle_t h, int port, uint8_t* value) { + if (h == NULL || value == NULL || !port_ok(port)) return ESP_ERR_INVALID_ARG; + return xfer_read(h, (uint8_t)(FCA9555_REG_IN0 + port), value, 1); +} + +esp_err_t fca9555_set_output(fca9555_handle_t h, int port, uint8_t value) { + if (h == NULL || !port_ok(port)) return ESP_ERR_INVALID_ARG; + return xfer_write(h, (uint8_t)(FCA9555_REG_OUT0 + port), value); +} + +esp_err_t fca9555_clear_int(fca9555_handle_t h) { + uint8_t in0 = 0; + return fca9555_read_input(h, 0, &in0); +} + +int fca9555_int_level(fca9555_handle_t h) { + if (h == NULL || !gpio_ok(h->int_gpio)) return 1; + return gpio_get_level(h->int_gpio); +} + +bool fca9555_int_asserted(fca9555_handle_t h) { + return fca9555_int_level(h) == 0; +} + +gpio_num_t fca9555_int_gpio(fca9555_handle_t h) { + return h ? h->int_gpio : GPIO_NUM_NC; +} + +bool fca9555_dir_cached(fca9555_handle_t h) { + return h != NULL && h->dir_cached; +} + +void fca9555_get_dir_cache(fca9555_handle_t h, uint16_t* invert, uint16_t* config) { + if (h == NULL) return; + if (invert) *invert = h->invert; + if (config) *config = h->config; +} + +esp_err_t fca9555_set_config(fca9555_handle_t h, int port, uint8_t value) { + if (h == NULL || !port_ok(port)) return ESP_ERR_INVALID_ARG; + esp_err_t err = xfer_write(h, (uint8_t)(FCA9555_REG_CFG0 + port), value); + if (err == ESP_OK) { + cache_port(&h->config, port, value); + h->dir_cached = true; + } + return err; +} + +esp_err_t fca9555_set_inversion(fca9555_handle_t h, int port, uint8_t value) { + if (h == NULL || !port_ok(port)) return ESP_ERR_INVALID_ARG; + esp_err_t err = xfer_write(h, (uint8_t)(FCA9555_REG_INV0 + port), value); + if (err == ESP_OK) { + cache_port(&h->invert, port, value); + h->dir_cached = true; + } + return err; +} diff --git a/firmware/targets/mindreset_read_pico/components/fca9555/idf_component.yml b/firmware/targets/mindreset_read_pico/components/fca9555/idf_component.yml new file mode 100644 index 0000000..f115ead --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/fca9555/idf_component.yml @@ -0,0 +1,14 @@ +version: "1.0.0" +description: "FCA9555 / xCA9555 16-bit I2C GPIO expander (Nyfea, PCA9555-compatible)" +url: "https://github.com/mindreset/fca9555" +repository: "https://github.com/mindreset/fca9555.git" +issues: "https://github.com/mindreset/fca9555/issues" +license: "Apache-2.0" +tags: + - gpio + - expander + - fca9555 + - pca9555 + - i2c +dependencies: + idf: ">=5.3" diff --git a/firmware/targets/mindreset_read_pico/components/fca9555/include/fca9555.h b/firmware/targets/mindreset_read_pico/components/fca9555/include/fca9555.h new file mode 100644 index 0000000..cbb680a --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/fca9555/include/fca9555.h @@ -0,0 +1,133 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * FCA9555 / xCA9555 16 位 I2C GPIO 扩展(Nyfea,兼容 PCA9555)。 + * 总线由调用方持有。读走写指针 + Repeated START(手册图 5/6)。 + * INT# 开漏、不锁存;读 Input 就松开。 + * + * FCA9555 / xCA9555 16-bit I2C GPIO expander (Nyfea, PCA9555-compatible). + * Caller owns the i2c_master bus. Read uses write pointer + Repeated START + * (datasheet Figure 5/6). INT# is open-drain, not latched; reading Input + * releases it. + */ + +#pragma once + +#include +#include +#include + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct fca9555_dev_t* fca9555_handle_t; + +#define FCA9555_ADDR_BASE 0x20 +#define FCA9555_ADDR_DEFAULT 0x24 +#define FCA9555_I2C_HZ_DEFAULT 400000 + +#define FCA9555_I2C_TIMEOUT_MS_DEFAULT 100 + +/// 由 A2/A1/A0 得到 7 位地址。A2=1 A1=0 A0=0 → 0x24。 +/// / 7-bit address from A2/A1/A0. A2=1 A1=0 A0=0 → 0x24. +#define FCA9555_ADDR_FROM_A(a2, a1, a0) \ + ((uint8_t)(FCA9555_ADDR_BASE | (((a2) & 1) << 2) | (((a1) & 1) << 1) | ((a0) & 1))) + +#define FCA9555_P00 0x0001 +#define FCA9555_P01 0x0002 +#define FCA9555_P02 0x0004 +#define FCA9555_P03 0x0008 +#define FCA9555_P04 0x0010 +#define FCA9555_P05 0x0020 +#define FCA9555_P06 0x0040 +#define FCA9555_P07 0x0080 +#define FCA9555_P10 0x0100 +#define FCA9555_P11 0x0200 +#define FCA9555_P12 0x0400 +#define FCA9555_P13 0x0800 +#define FCA9555_P14 0x1000 +#define FCA9555_P15 0x2000 +#define FCA9555_P16 0x4000 +#define FCA9555_P17 0x8000 + +#define FCA9555_REG_IN0 0 +#define FCA9555_REG_IN1 1 +#define FCA9555_REG_OUT0 2 +#define FCA9555_REG_OUT1 3 +#define FCA9555_REG_INV0 4 +#define FCA9555_REG_INV1 5 +#define FCA9555_REG_CFG0 6 +#define FCA9555_REG_CFG1 7 + +/// 手册顺序 IN0/1 OUT0/1 INV0/1 CFG0/1。小端:Port0 在低字节。 +/// / Datasheet order IN0/1 OUT0/1 INV0/1 CFG0/1. Little-endian: Port0 in the low byte. +typedef struct { + uint16_t input; + uint16_t output; + uint16_t invert; + uint16_t config; +} fca9555_map_t; + +_Static_assert(sizeof(fca9555_map_t) == 8, "FCA9555 map must be 8 bytes"); + +typedef struct { + uint8_t i2c_addr; + uint32_t scl_speed_hz; + gpio_num_t int_gpio; + uint16_t i2c_timeout_ms; +} fca9555_config_t; + +#define FCA9555_CONFIG_DEFAULT() \ + { \ + .i2c_addr = FCA9555_ADDR_DEFAULT, \ + .scl_speed_hz = FCA9555_I2C_HZ_DEFAULT, \ + .int_gpio = GPIO_NUM_NC, \ + .i2c_timeout_ms = FCA9555_I2C_TIMEOUT_MS_DEFAULT, \ + } + +esp_err_t fca9555_init(i2c_master_bus_handle_t bus_handle, const fca9555_config_t* config, + fca9555_handle_t* handle); +esp_err_t fca9555_deinit(fca9555_handle_t handle); + +esp_err_t fca9555_read_reg(fca9555_handle_t h, uint8_t reg, uint8_t* value); +esp_err_t fca9555_read_n(fca9555_handle_t h, uint8_t start, uint8_t* data, size_t n); +/// 连读全部 8 寄存器。刷新 CFG/INV 缓存。 +/// / Sequential read of all 8 registers. Refreshes CFG/INV cache. +esp_err_t fca9555_read_all(fca9555_handle_t h, fca9555_map_t* map); +/// 连读 IN/OUT。CFG/INV 走缓存。 +/// / Sequential read of IN/OUT. CFG/INV come from cache. +esp_err_t fca9555_read_io(fca9555_handle_t h, fca9555_map_t* map); +esp_err_t fca9555_read_input(fca9555_handle_t h, int port, uint8_t* value); +esp_err_t fca9555_set_output(fca9555_handle_t h, int port, uint8_t value); + +/// 读 Input 以松开 INT#。Port1 空闲的板子只读 port 0。 +/// / Read Input to release INT#. Port1 unused boards only need port 0. +esp_err_t fca9555_clear_int(fca9555_handle_t h); + +int fca9555_int_level(fca9555_handle_t h); +bool fca9555_int_asserted(fca9555_handle_t h); +gpio_num_t fca9555_int_gpio(fca9555_handle_t h); + +bool fca9555_dir_cached(fca9555_handle_t h); +void fca9555_get_dir_cache(fca9555_handle_t h, uint16_t* invert, uint16_t* config); + +// --------------------------------------------------------------------------- +// 危险:改方向可能把感应脚(PGOOD、卡检测)打成输出。 +// 极性翻转会把同样这些信号反过来。不要挂到产品界面。 +// CFG 1=输入,0=输出。INV 1=翻转 Input。 +// Dangerous: changing direction can drive a sense pin (PGOOD, card detect). +// Inverting polarity flips those same signals. Do not expose on product UI. +// CFG 1 = input, 0 = output. INV 1 = invert Input. +// --------------------------------------------------------------------------- +esp_err_t fca9555_set_config(fca9555_handle_t h, int port, uint8_t value); +esp_err_t fca9555_set_inversion(fca9555_handle_t h, int port, uint8_t value); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/mindreset_read_pico/components/pwm_audio/CMakeLists.txt b/firmware/targets/mindreset_read_pico/components/pwm_audio/CMakeLists.txt new file mode 100644 index 0000000..b76ad71 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/pwm_audio/CMakeLists.txt @@ -0,0 +1,6 @@ +idf_component_register( + SRCS "pwm_audio.c" + INCLUDE_DIRS "include" + REQUIRES esp_driver_gpio esp_driver_gptimer esp_driver_ledc + PRIV_REQUIRES heap +) diff --git a/firmware/targets/mindreset_read_pico/components/pwm_audio/LICENSE b/firmware/targets/mindreset_read_pico/components/pwm_audio/LICENSE new file mode 100644 index 0000000..0eaed35 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/pwm_audio/LICENSE @@ -0,0 +1,20 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +Copyright 2022-2024 Espressif Systems (Shanghai) CO LTD + +This directory is a trimmed copy of Espressif pwm_audio +(https://github.com/espressif/esp-iot-solution/tree/master/components/audio/pwm_audio). + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/firmware/targets/mindreset_read_pico/components/pwm_audio/README.md b/firmware/targets/mindreset_read_pico/components/pwm_audio/README.md new file mode 100644 index 0000000..e5d955b --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/pwm_audio/README.md @@ -0,0 +1,30 @@ +# pwm_audio + +LEDC PWM 音频:环形缓冲加定时器 ISR,把 PCM 写成占空比。本仓库用作蜂鸣器底层之一。 + +LEDC PWM audio helper: a ring buffer and a timer ISR that writes PCM samples as duty cycles. Used as one of the buzzer backends. + +这是 Espressif +[pwm_audio](https://github.com/espressif/esp-iot-solution/tree/master/components/audio/pwm_audio) +的裁剪副本(Apache-2.0)。 + +This is a trimmed copy of Espressif's +[pwm_audio](https://github.com/espressif/esp-iot-solution/tree/master/components/audio/pwm_audio) +(Apache-2.0). + +## 接口 / API + +- `pwm_audio_init()` / `pwm_audio_deinit()` +- `pwm_audio_start()` / `pwm_audio_stop()` +- `pwm_audio_write()`:把 PCM 推进环形缓冲。/ Push PCM into the ring buffer. +- `pwm_audio_set_param()` / `pwm_audio_set_sample_rate()` / `pwm_audio_set_volume()` + +见 `include/pwm_audio.h`。 + +See `include/pwm_audio.h`. + +## 许可 / License + +Apache-2.0,见 [LICENSE](LICENSE)。版权归 Espressif Systems (Shanghai) CO LTD。 + +Apache-2.0, see [LICENSE](LICENSE). Copyright Espressif Systems (Shanghai) CO LTD. diff --git a/firmware/targets/mindreset_read_pico/components/pwm_audio/include/pwm_audio.h b/firmware/targets/mindreset_read_pico/components/pwm_audio/include/pwm_audio.h new file mode 100644 index 0000000..22519e6 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/pwm_audio/include/pwm_audio.h @@ -0,0 +1,75 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-License-Identifier: Apache-2.0 + * + * LEDC PWM 音频:环形缓冲、定时器 ISR 把 PCM 写成占空比。 + * + * LEDC PWM audio: ring buffer plus a timer ISR that writes PCM as duty. + */ + +#ifndef _PWM_AUDIO_H_ +#define _PWM_AUDIO_H_ + +#define PWM_AUDIO_VER_MAJOR 1 +#define PWM_AUDIO_VER_MINOR 2 +#define PWM_AUDIO_VER_PATCH 0 + +#include "esp_idf_version.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/ledc.h" +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) +#include "driver/gptimer.h" +#else +#include "driver/timer.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + timer_group_t tg_num; ///< 定时器组 / Timer group + timer_idx_t timer_num; ///< 定时器编号 / Timer index +#endif + int gpio_num_left; ///< 左声道 GPIO / Left GPIO + int gpio_num_right; ///< 右声道 GPIO / Right GPIO + ledc_channel_t ledc_channel_left; ///< 左 LEDC 通道 / Left LEDC channel + ledc_channel_t ledc_channel_right; ///< 右 LEDC 通道 / Right LEDC channel + ledc_timer_t ledc_timer_sel; ///< LEDC 定时器 / LEDC timer + ledc_timer_bit_t duty_resolution; ///< 占空比分辨率 / Duty resolution + uint32_t ringbuf_len; ///< 环形缓冲字节数 / Ring-buffer bytes +} pwm_audio_config_t; + +typedef enum { + PWM_AUDIO_STATUS_UN_INIT = 0, ///< 未初始化 / Uninitialized + PWM_AUDIO_STATUS_IDLE = 1, ///< 空闲 / Idle + PWM_AUDIO_STATUS_BUSY = 2, ///< 播放中 / Playing +} pwm_audio_status_t; + +typedef enum { + PWM_AUDIO_CH_MONO = 0, ///< 单声道 / Mono + PWM_AUDIO_CH_STEREO = 1, ///< 立体声 / Stereo + PWM_AUDIO_CH_MAX, +} pwm_audio_channel_t; + +esp_err_t pwm_audio_init(const pwm_audio_config_t *cfg); +esp_err_t pwm_audio_deinit(void); +esp_err_t pwm_audio_start(void); +esp_err_t pwm_audio_stop(void); +esp_err_t pwm_audio_write( + uint8_t *inbuf, size_t len, size_t *bytes_written, TickType_t ticks_to_wait +); +esp_err_t pwm_audio_set_param(int rate, ledc_timer_bit_t bits, int ch); +esp_err_t pwm_audio_set_sample_rate(int rate); +esp_err_t pwm_audio_set_volume(int8_t volume); +esp_err_t pwm_audio_get_volume(int8_t *volume); +esp_err_t pwm_audio_get_param(int *rate, int *bits, int *ch); +esp_err_t pwm_audio_get_status(pwm_audio_status_t *status); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/firmware/targets/mindreset_read_pico/components/pwm_audio/pwm_audio.c b/firmware/targets/mindreset_read_pico/components/pwm_audio/pwm_audio.c new file mode 100644 index 0000000..e63857e --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/pwm_audio/pwm_audio.c @@ -0,0 +1,827 @@ +/* + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-License-Identifier: Apache-2.0 + * + * LEDC PWM 音频实现:环形缓冲与定时器 ISR。 + * + * LEDC PWM audio: ring buffer and timer ISR. + */ + +#include +#include +#include "esp_idf_version.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "driver/gpio.h" +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "soc/ledc_struct.h" +#include "pwm_audio.h" + +static const char *TAG = "pwm_audio"; + +#define PWM_AUDIO_CHECK(a, str, ret_val) \ + if (!(a)) \ + { \ + ESP_LOGE(TAG, "%s(%d): %s", __FUNCTION__, __LINE__, str); \ + return (ret_val); \ + } + +static const char *PWM_AUDIO_PARAM_ADDR_ERROR = "PWM AUDIO PARAM ADDR ERROR"; +static const char *PWM_AUDIO_FRAMERATE_ERROR = "PWM AUDIO FRAMERATE ERROR"; +static const char *PWM_AUDIO_STATUS_ERROR = "PWM AUDIO STATUS ERROR"; +static const char *PWM_AUDIO_ALLOC_ERROR = "PWM AUDIO ALLOC ERROR"; +static const char *PWM_AUDIO_RESOLUTION_ERROR = "PWM AUDIO RESOLUTION ERROR"; +static const char *PWM_AUDIO_NOT_INITIALIZED = "PWM AUDIO Uninitialized"; +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) +static const char *PWM_AUDIO_TG_NUM_ERROR = "PWM AUDIO TIMER GROUP NUMBER ERROR"; +static const char *PWM_AUDIO_TIMER_NUM_ERROR = "PWM AUDIO TIMER NUMBER ERROR"; +#endif + +#define BUFFER_MIN_SIZE (256UL) +#define SAMPLE_RATE_MAX (48000) +#define SAMPLE_RATE_MIN (8000) +#define CHANNEL_LEFT_INDEX (0) +#define CHANNEL_RIGHT_INDEX (1) +#define CHANNEL_LEFT_MASK (0x01) +#define CHANNEL_RIGHT_MASK (0x02) +#define VOLUME_0DB (16) + +#ifndef TIMER_BASE_CLK +#define TIMER_BASE_CLK 80000000 +#endif +#define TIMER_DIVIDER 16 + +// 调试开关。/ Debug switch. +#define ISR_DEBUG 0 +#define ISR_DEBUG_IO_MASK 0x8000 + +typedef struct { + char *buf; // 缓冲起点。/ Buffer base. + volatile uint32_t head; // 写指针。/ Write pointer. + volatile uint32_t tail; // 读指针。/ Read pointer. + volatile uint32_t size; // 缓冲字节数。/ Buffer size. + volatile uint32_t is_give; // 信号量已给出。/ Semaphore already given. + SemaphoreHandle_t semaphore_rb; // 环形缓冲信号量。/ Ring-buffer semaphore. +} ringbuf_handle_t; + +typedef struct { + pwm_audio_config_t config; // pwm_audio 配置。/ pwm_audio config. + ledc_channel_config_t ledc_channel[PWM_AUDIO_CH_MAX]; // LEDC 通道。/ LEDC channels. + ledc_timer_config_t ledc_timer; // LEDC 定时器。/ LEDC timer. + ringbuf_handle_t *ringbuf; // 音频环形缓冲。/ Audio ring buffer. + uint32_t channel_mask; // 通道 GPIO 掩码。/ Channel GPIO mask. + uint32_t channel_set_num; // 声道数。/ Channel count. + int32_t framerate; // 采样率 Hz。/ Sample rate in Hz. + int32_t bits_per_sample; // 每样位数(8/16/32)。/ Bits per sample (8, 16, 32). + int32_t volume; // 音量(-VOLUME_0DB ~ VOLUME_0DB)。/ Volume (-VOLUME_0DB ~ VOLUME_0DB). + pwm_audio_status_t status; +} pwm_audio_data_t; + +// LEDC 寄存器指针。/ LEDC register pointers. +static volatile uint32_t *g_ledc_left_conf0_val = NULL; +static volatile uint32_t *g_ledc_left_conf1_val = NULL; +static volatile uint32_t *g_ledc_left_duty_val = NULL; +static volatile uint32_t *g_ledc_right_conf0_val = NULL; +static volatile uint32_t *g_ledc_right_conf1_val = NULL; +static volatile uint32_t *g_ledc_right_duty_val = NULL; + +// pwm_audio 句柄。/ pwm_audio handle. +static pwm_audio_data_t *g_pwm_audio_handle = NULL; + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) +static gptimer_handle_t g_gptimer; +#endif + +// pwm_audio 环形缓冲。/ pwm_audio ring buffer. +static esp_err_t rb_destroy(ringbuf_handle_t *rb) +{ + if (rb == NULL) { + return ESP_ERR_INVALID_ARG; + } + + if (rb->buf) { + heap_caps_free(rb->buf); + } + + if (rb->semaphore_rb) { + vSemaphoreDelete(rb->semaphore_rb); + } + + heap_caps_free(rb); + rb = NULL; + return ESP_OK; +} + +static ringbuf_handle_t *rb_create(uint32_t size) +{ + if (size < (BUFFER_MIN_SIZE << 2)) { + ESP_LOGE(TAG, "Invalid buffer size, Minimum = %"PRIi32"", (int32_t)(BUFFER_MIN_SIZE << 2)); + return NULL; + } + + ringbuf_handle_t *rb = NULL; + char *buf = NULL; + + do { + bool _success = + ( + (rb = heap_caps_malloc(sizeof(ringbuf_handle_t), MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL)) && + (buf = heap_caps_malloc(size, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL)) && + (rb->semaphore_rb = xSemaphoreCreateBinary()) + + ); + + if (!_success) { + break; + } + + rb->is_give = 0; + rb->buf = buf; + rb->head = rb->tail = 0; + rb->size = size; + return rb; + + } while (0); + + rb_destroy(rb); + return NULL; +} + +static uint32_t IRAM_ATTR rb_get_count(ringbuf_handle_t *rb) +{ + uint32_t tail = rb->tail; + + if (rb->head >= tail) { + return (rb->head - tail); + } + + return (rb->size - (tail - rb->head)); +} + +static uint32_t IRAM_ATTR rb_get_free(ringbuf_handle_t *rb) +{ + // 留一字节判断环形方向。/ Leave one byte free so wrap direction is unambiguous. + return (rb->size - rb_get_count(rb) - 1); +} + +static esp_err_t rb_flush(ringbuf_handle_t *rb) +{ + rb->tail = rb->head = 0; + return ESP_OK; +} + +static esp_err_t IRAM_ATTR rb_read_byte(ringbuf_handle_t *rb, uint8_t *outdata) +{ + uint32_t tail = rb->tail; + + if (tail == rb->head) { + return ESP_FAIL; + } + + // 从缓冲取出一字节。/ Pop one byte from the buffer. + *outdata = rb->buf[tail]; + + // 推进读指针。/ Advance the read pointer. + tail++; + + if (tail == rb->size) { + tail = 0; + } + + rb->tail = tail; + return ESP_OK; +} + +static esp_err_t rb_write_byte(ringbuf_handle_t *rb, const uint8_t indata) +{ + // 下一写指针。/ Next write pointer. + uint32_t next_head = rb->head + 1; + + if (next_head == rb->size) { + next_head = 0; + } + + if (next_head == rb->tail) { + return ESP_FAIL; + } + + // 写入并推进写指针。/ Store the byte and advance the write pointer. + rb->buf[rb->head] = indata; + rb->head = next_head; + return ESP_OK; +} + +static esp_err_t rb_wait_semaphore(ringbuf_handle_t *rb, TickType_t ticks_to_wait) +{ + rb->is_give = 0; // 有写入后允许再给信号量。/ After a write, the semaphore may be given again. + + if (xSemaphoreTake(rb->semaphore_rb, ticks_to_wait) == pdTRUE) { + return ESP_OK; + } + + return ESP_FAIL; +} + +// 直接写寄存器以提高效率。/ Write registers directly for speed. +static inline void ledc_set_left_duty_fast(uint32_t duty_val) +{ + *g_ledc_left_duty_val = (duty_val) << 4; // 丢掉小数。/ Discard the fractional part. + *g_ledc_left_conf0_val |= 0x00000014; + *g_ledc_left_conf1_val |= 0x80000000; +} +static inline void ledc_set_right_duty_fast(uint32_t duty_val) +{ + *g_ledc_right_duty_val = (duty_val) << 4; // 丢掉小数。/ Discard the fractional part. + *g_ledc_right_conf0_val |= 0x00000014; + *g_ledc_right_conf1_val |= 0x80000000; +} + +// 定时器组 ISR。/ Timer-group ISR. + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) +static bool IRAM_ATTR timer_group_isr(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx) +{ + pwm_audio_data_t *handle = g_pwm_audio_handle; + if (handle == NULL) { + return false; + } +#else +static void IRAM_ATTR timer_group_isr(void *para) +{ + pwm_audio_data_t *handle = g_pwm_audio_handle; + if (handle == NULL) { + return ; + } + // 清中断。/ Clear the interrupt. + timer_group_clr_intr_status_in_isr(handle->config.tg_num, handle->config.timer_num); + // 闹钟触发后再使能,好迎下一次。/ Re-enable the alarm so the next tick fires. + timer_group_enable_alarm_in_isr(handle->config.tg_num, handle->config.timer_num); +#endif + static uint8_t wave_h, wave_l; + static uint16_t value; + ringbuf_handle_t *rb = handle->ringbuf; +#if (1==ISR_DEBUG) + GPIO.out_w1ts = ISR_DEBUG_IO_MASK; +#endif + + // 配了 GPIO 的通道就出声。/ A channel with a GPIO assigned must output sound. + if (handle->channel_mask & CHANNEL_LEFT_MASK) { + if (handle->config.duty_resolution > 8) { + if (rb_get_count(rb) > 1) { + rb_read_byte(rb, &wave_l); + rb_read_byte(rb, &wave_h); + value = ((wave_h << 8) | wave_l); + ledc_set_left_duty_fast(value); // 写 PWM 占空比。/ Set PWM duty. + } + } else { + if (ESP_OK == rb_read_byte(rb, &wave_h)) { + ledc_set_left_duty_fast(wave_h); // 写 PWM 占空比。/ Set PWM duty. + } + } + } + + // 配了两路 GPIO 但数据是单声道:右声道复制左声道;否则丢掉右声道数据。 + // / Two GPIOs but mono data: copy to the right. Otherwise drop the right channel. + if (handle->channel_mask & CHANNEL_RIGHT_MASK) { + if (handle->channel_set_num == 1) { + if (handle->config.duty_resolution > 8) { + ledc_set_right_duty_fast(value); // 写 PWM 占空比。/ Set PWM duty. + } else { + ledc_set_right_duty_fast(wave_h); // 写 PWM 占空比。/ Set PWM duty. + } + } else { + if (handle->config.duty_resolution > 8) { + if (rb_get_count(rb) > 1) { + rb_read_byte(rb, &wave_l); + rb_read_byte(rb, &wave_h); + value = ((wave_h << 8) | wave_l); + ledc_set_right_duty_fast(value); // 写 PWM 占空比。/ Set PWM duty. + } + } else { + if (ESP_OK == rb_read_byte(rb, &wave_h)) { + ledc_set_right_duty_fast(wave_h); // 写 PWM 占空比。/ Set PWM duty. + } + } + } + } else { + if (handle->channel_set_num == 2) { + // 只配了左声道但数据是立体声:读掉右声道,不输出。 + // / Left-only GPIO but stereo data: consume the right channel and drop it. + if (handle->config.duty_resolution > 8) { + if (rb_get_count(rb) > 1) { + rb_read_byte(rb, &wave_h); + rb_read_byte(rb, &wave_h); + } + } else { + rb_read_byte(rb, &wave_h); + } + } + } + + // 空闲超过 BUFFER_MIN_SIZE 再给信号量。 + // / Give the semaphore when free space exceeds BUFFER_MIN_SIZE. + uint32_t free_size = rb_get_free(rb); + if (0 == rb->is_give && free_size > BUFFER_MIN_SIZE) { + // 下面这段约 2.71 µs。/ The next few lines take ~2.71 µs. + rb->is_give = 1; // 防止重复给信号量。/ Prevent giving the semaphore twice. + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xSemaphoreGiveFromISR(rb->semaphore_rb, &xHigherPriorityTaskWoken); + + if (xHigherPriorityTaskWoken == pdTRUE) { + portYIELD_FROM_ISR(); + } + } +#if (1==ISR_DEBUG) + GPIO.out_w1tc = ISR_DEBUG_IO_MASK; +#endif +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + return true; +#else + return; +#endif +} + +esp_err_t pwm_audio_get_status(pwm_audio_status_t *status) +{ + pwm_audio_data_t *handle = g_pwm_audio_handle; + PWM_AUDIO_CHECK(NULL != handle, PWM_AUDIO_NOT_INITIALIZED, ESP_ERR_INVALID_STATE); + *status = handle->status; + return ESP_OK; +} + +esp_err_t pwm_audio_get_param(int *rate, int *bits, int *ch) +{ + pwm_audio_data_t *handle = g_pwm_audio_handle; + PWM_AUDIO_CHECK(NULL != handle, PWM_AUDIO_NOT_INITIALIZED, ESP_ERR_INVALID_STATE); + + if (NULL != rate) { + *rate = handle->framerate; + } + + if (NULL != bits) { + *bits = (int)handle->bits_per_sample; + } + + if (NULL != ch) { + *ch = handle->channel_set_num; + } + + return ESP_OK; +} + +esp_err_t pwm_audio_init(const pwm_audio_config_t *cfg) +{ + ESP_LOGI(TAG, "PWM Audio Version: %d.%d.%d", PWM_AUDIO_VER_MAJOR, PWM_AUDIO_VER_MINOR, PWM_AUDIO_VER_PATCH); + esp_err_t res = ESP_OK; +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + PWM_AUDIO_CHECK(cfg->tg_num < TIMER_GROUP_MAX, PWM_AUDIO_TG_NUM_ERROR, ESP_ERR_INVALID_ARG); + PWM_AUDIO_CHECK(cfg->timer_num < TIMER_MAX, PWM_AUDIO_TIMER_NUM_ERROR, ESP_ERR_INVALID_ARG); +#endif + PWM_AUDIO_CHECK(cfg != NULL, PWM_AUDIO_PARAM_ADDR_ERROR, ESP_ERR_INVALID_ARG); + PWM_AUDIO_CHECK(cfg->duty_resolution <= 10 && cfg->duty_resolution >= 8, PWM_AUDIO_RESOLUTION_ERROR, ESP_ERR_INVALID_ARG); + PWM_AUDIO_CHECK(NULL == g_pwm_audio_handle, "Already initiate", ESP_ERR_INVALID_STATE); + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + ESP_LOGI(TAG, "left io: %d | right io: %d | resolution: %dBIT", + cfg->gpio_num_left, cfg->gpio_num_right, cfg->duty_resolution); +#else + ESP_LOGI(TAG, "timer: %d:%d | left io: %d | right io: %d | resolution: %dBIT", + cfg->tg_num, cfg->timer_num, cfg->gpio_num_left, cfg->gpio_num_right, cfg->duty_resolution); +#endif + + pwm_audio_data_t *handle = NULL; + + handle = heap_caps_malloc(sizeof(pwm_audio_data_t), MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + PWM_AUDIO_CHECK(handle != NULL, PWM_AUDIO_ALLOC_ERROR, ESP_ERR_NO_MEM); + memset(handle, 0, sizeof(pwm_audio_data_t)); + + handle->ringbuf = rb_create(cfg->ringbuf_len); + PWM_AUDIO_CHECK(handle->ringbuf != NULL, PWM_AUDIO_ALLOC_ERROR, ESP_ERR_NO_MEM); + + handle->config = *cfg; + g_pwm_audio_handle = handle; +#if (1==ISR_DEBUG) + gpio_config_t io_conf; + // 关中断。/ Disable interrupt. + io_conf.intr_type = GPIO_PIN_INTR_DISABLE; + // 输出。/ Output. + io_conf.mode = GPIO_MODE_OUTPUT; + // 调试脚掩码。/ Debug pin mask. + io_conf.pin_bit_mask = ISR_DEBUG_IO_MASK; + // 关下拉。/ Disable pulldown. + io_conf.pull_down_en = 0; + // 关上拉。/ Disable pullup. + io_conf.pull_up_en = 0; + // 按上面的配置写 GPIO。/ Apply the GPIO config. + gpio_config(&io_conf); +#endif + + // 配 LEDC 出 PWM。/ Configure LEDC for PWM. + handle->channel_mask = 0; + handle->ledc_channel[CHANNEL_LEFT_INDEX].gpio_num = -1; + handle->ledc_channel[CHANNEL_RIGHT_INDEX].gpio_num = -1; + + if (handle->config.gpio_num_left >= 0) { + handle->ledc_channel[CHANNEL_LEFT_INDEX].channel = handle->config.ledc_channel_left; + handle->ledc_channel[CHANNEL_LEFT_INDEX].duty = 0; + handle->ledc_channel[CHANNEL_LEFT_INDEX].gpio_num = handle->config.gpio_num_left; + handle->ledc_channel[CHANNEL_LEFT_INDEX].speed_mode = LEDC_LOW_SPEED_MODE; + handle->ledc_channel[CHANNEL_LEFT_INDEX].hpoint = 0; + handle->ledc_channel[CHANNEL_LEFT_INDEX].timer_sel = handle->config.ledc_timer_sel; + res = ledc_channel_config(&handle->ledc_channel[CHANNEL_LEFT_INDEX]); + PWM_AUDIO_CHECK(ESP_OK == res, "LEDC channel left configuration failed", ESP_ERR_INVALID_ARG); + handle->channel_mask |= CHANNEL_LEFT_MASK; + } + + if (handle->config.gpio_num_right >= 0) { + handle->ledc_channel[CHANNEL_RIGHT_INDEX].channel = handle->config.ledc_channel_right; + handle->ledc_channel[CHANNEL_RIGHT_INDEX].duty = 0; + handle->ledc_channel[CHANNEL_RIGHT_INDEX].gpio_num = handle->config.gpio_num_right; + handle->ledc_channel[CHANNEL_RIGHT_INDEX].speed_mode = LEDC_LOW_SPEED_MODE; + handle->ledc_channel[CHANNEL_RIGHT_INDEX].hpoint = 0; + handle->ledc_channel[CHANNEL_RIGHT_INDEX].timer_sel = handle->config.ledc_timer_sel; + res = ledc_channel_config(&handle->ledc_channel[CHANNEL_RIGHT_INDEX]); + PWM_AUDIO_CHECK(ESP_OK == res, "LEDC channel right configuration failed", ESP_ERR_INVALID_ARG); + handle->channel_mask |= CHANNEL_RIGHT_MASK; + } + + PWM_AUDIO_CHECK(0 != handle->channel_mask, "Assign at least one channel gpio", ESP_ERR_INVALID_ARG); + +#ifdef CONFIG_IDF_TARGET_ESP32S2 + handle->ledc_timer.clk_cfg = LEDC_USE_APB_CLK; +#endif + handle->ledc_timer.speed_mode = LEDC_LOW_SPEED_MODE; + handle->ledc_timer.duty_resolution = handle->config.duty_resolution; + handle->ledc_timer.timer_num = handle->config.ledc_timer_sel; + uint32_t freq = (APB_CLK_FREQ / (1 << handle->ledc_timer.duty_resolution)); + handle->ledc_timer.freq_hz = freq - (freq % 1000); // 固定 PWM 频率,取 1000 的倍数。/ Fixed PWM frequency, multiple of 1000. + res = ledc_timer_config(&handle->ledc_timer); + PWM_AUDIO_CHECK(res == ESP_OK, "LEDC timer configuration failed", ESP_ERR_INVALID_ARG); + + // 缓存 LEDC 寄存器地址,缩短寻址。/ Cache LEDC register addresses to cut lookup time. +#if defined (CONFIG_IDF_TARGET_ESP32P4) || defined (CONFIG_IDF_TARGET_ESP32C5) || defined (CONFIG_IDF_TARGET_ESP32C61) + g_ledc_left_duty_val = &LEDC.channel_group[handle->ledc_timer.speed_mode]. + channel[handle->ledc_channel[CHANNEL_LEFT_INDEX].channel].duty_init.val; +#else + g_ledc_left_duty_val = &LEDC.channel_group[handle->ledc_timer.speed_mode]. + channel[handle->ledc_channel[CHANNEL_LEFT_INDEX].channel].duty.val; +#endif + g_ledc_left_conf0_val = &LEDC.channel_group[handle->ledc_timer.speed_mode]. + channel[handle->ledc_channel[CHANNEL_LEFT_INDEX].channel].conf0.val; + g_ledc_left_conf1_val = &LEDC.channel_group[handle->ledc_timer.speed_mode]. + channel[handle->ledc_channel[CHANNEL_LEFT_INDEX].channel].conf1.val; +#if defined (CONFIG_IDF_TARGET_ESP32P4) || defined (CONFIG_IDF_TARGET_ESP32C5) || defined (CONFIG_IDF_TARGET_ESP32C61) + g_ledc_right_duty_val = &LEDC.channel_group[handle->ledc_timer.speed_mode]. + channel[handle->ledc_channel[CHANNEL_RIGHT_INDEX].channel].duty_init.val; +#else + g_ledc_right_duty_val = &LEDC.channel_group[handle->ledc_timer.speed_mode]. + channel[handle->ledc_channel[CHANNEL_RIGHT_INDEX].channel].duty.val; +#endif + g_ledc_right_conf0_val = &LEDC.channel_group[handle->ledc_timer.speed_mode]. + channel[handle->ledc_channel[CHANNEL_RIGHT_INDEX].channel].conf0.val; + g_ledc_right_conf1_val = &LEDC.channel_group[handle->ledc_timer.speed_mode]. + channel[handle->ledc_channel[CHANNEL_RIGHT_INDEX].channel].conf1.val; + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + gptimer_config_t timer_config = { + .clk_src = GPTIMER_CLK_SRC_DEFAULT, + .direction = GPTIMER_COUNT_UP, + .resolution_hz = TIMER_BASE_CLK / TIMER_DIVIDER, + }; + + gptimer_event_callbacks_t cbs = { + .on_alarm = timer_group_isr, + }; + + res = gptimer_new_timer(&timer_config, &g_gptimer); + PWM_AUDIO_CHECK(ESP_OK == res, "gptimer configuration failed", res); + res = gptimer_register_event_callbacks(g_gptimer, &cbs, NULL); + PWM_AUDIO_CHECK(ESP_OK == res, "gptimer register event callback failed", res); +#else + // 选定时器并写基本参数。/ Select and initialize the timer. + timer_config_t config = { + .divider = TIMER_DIVIDER, + .counter_dir = TIMER_COUNT_UP, + .counter_en = TIMER_PAUSE, + .alarm_en = TIMER_ALARM_EN, + .intr_type = TIMER_INTR_LEVEL, + .auto_reload = 1, + }; +#ifdef TIMER_GROUP_SUPPORTS_XTAL_CLOCK + config.clk_src = TIMER_SRC_CLK_APB; // ESP32-S2 专用时钟位。/ ESP32-S2-only clock bit. +#endif + res = timer_init(handle->config.tg_num, handle->config.timer_num, &config); + PWM_AUDIO_CHECK(ESP_OK == res, "Timer group configuration failed", res); + res = timer_isr_register(handle->config.tg_num, handle->config.timer_num, timer_group_isr, NULL, ESP_INTR_FLAG_IRAM, NULL); + PWM_AUDIO_CHECK(ESP_OK == res, "Timer register event callback failed", res); +#endif + // 写一组初始参数。/ Set an initial parameter set. + res = pwm_audio_set_param(16000, 8, 2); + PWM_AUDIO_CHECK(ESP_OK == res, "Set parameter failed", ESP_FAIL); + + pwm_audio_set_volume(0); + + handle->status = PWM_AUDIO_STATUS_IDLE; + + return ESP_OK; +} + +esp_err_t pwm_audio_set_param(int rate, ledc_timer_bit_t bits, int ch) +{ + esp_err_t res = ESP_OK; + + PWM_AUDIO_CHECK(g_pwm_audio_handle->status != PWM_AUDIO_STATUS_BUSY, PWM_AUDIO_STATUS_ERROR, ESP_ERR_INVALID_ARG); + PWM_AUDIO_CHECK(rate <= SAMPLE_RATE_MAX && rate >= SAMPLE_RATE_MIN, PWM_AUDIO_FRAMERATE_ERROR, ESP_ERR_INVALID_ARG); + PWM_AUDIO_CHECK(bits == 32 || bits == 16 || bits == 8, " Unsupported Bit width, only support 8, 16, 32", ESP_ERR_INVALID_ARG); + PWM_AUDIO_CHECK(ch <= 2 && ch >= 1, "Unsupported channel number, only support mono and stereo", ESP_ERR_INVALID_ARG); + + pwm_audio_data_t *handle = g_pwm_audio_handle; + + handle->framerate = rate; + handle->bits_per_sample = bits; + handle->channel_set_num = ch; + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + gptimer_set_raw_count(g_gptimer, 0x00000000ULL); + // 配闹钟值和闹钟中断。/ Configure the alarm value and alarm interrupt. + gptimer_alarm_config_t alarm_config = { + .reload_count = 0, + .alarm_count = (TIMER_BASE_CLK / TIMER_DIVIDER) / handle->framerate, + .flags.auto_reload_on_alarm = true, // 闹钟到点自动重装。/ Auto-reload on alarm. + }; + + res = gptimer_set_alarm_action(g_gptimer, &alarm_config); +#else + // 计数从下面的值起;auto_reload 时闹钟到点会重装。 + // / Counter starts at the value below; auto_reload restores it on alarm. + timer_set_counter_value(handle->config.tg_num, handle->config.timer_num, 0x00000000ULL); + // 配闹钟值和闹钟中断。/ Configure the alarm value and alarm interrupt. + res = timer_set_alarm_value(handle->config.tg_num, handle->config.timer_num, (TIMER_BASE_CLK / TIMER_DIVIDER) / handle->framerate); +#endif + PWM_AUDIO_CHECK(ESP_OK == res, "pwm_audio set param failed", res); + return ESP_OK; +} + +esp_err_t pwm_audio_set_sample_rate(int rate) +{ + esp_err_t res; + PWM_AUDIO_CHECK(NULL != g_pwm_audio_handle, PWM_AUDIO_NOT_INITIALIZED, ESP_ERR_INVALID_STATE); + PWM_AUDIO_CHECK(g_pwm_audio_handle->status != PWM_AUDIO_STATUS_BUSY, PWM_AUDIO_STATUS_ERROR, ESP_ERR_INVALID_ARG); + PWM_AUDIO_CHECK(rate <= SAMPLE_RATE_MAX && rate >= SAMPLE_RATE_MIN, PWM_AUDIO_FRAMERATE_ERROR, ESP_ERR_INVALID_ARG); + + pwm_audio_data_t *handle = g_pwm_audio_handle; + handle->framerate = rate; +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + gptimer_alarm_config_t alarm_config = { + .reload_count = 0, + .alarm_count = (TIMER_BASE_CLK / TIMER_DIVIDER) / handle->framerate, + .flags.auto_reload_on_alarm = true, // 闹钟到点自动重装。/ Auto-reload on alarm. + }; + res = gptimer_set_alarm_action(g_gptimer, &alarm_config); +#else + res = timer_set_alarm_value(handle->config.tg_num, handle->config.timer_num, (TIMER_BASE_CLK / TIMER_DIVIDER) / handle->framerate); +#endif + PWM_AUDIO_CHECK(ESP_OK == res, "pwm_audio set sample rate failed", res); + return ESP_OK; +} + +esp_err_t pwm_audio_set_volume(int8_t volume) +{ + pwm_audio_data_t *handle = g_pwm_audio_handle; + PWM_AUDIO_CHECK(NULL != handle, PWM_AUDIO_NOT_INITIALIZED, ESP_ERR_INVALID_STATE); + if (volume < 0) { + PWM_AUDIO_CHECK(-volume <= VOLUME_0DB, "Volume is too small", ESP_ERR_INVALID_ARG); + } else { + PWM_AUDIO_CHECK(volume <= VOLUME_0DB, "Volume is too large", ESP_ERR_INVALID_ARG); + } + + handle->volume = volume + VOLUME_0DB; + return ESP_OK; +} + +esp_err_t pwm_audio_get_volume(int8_t *volume) +{ + pwm_audio_data_t *handle = g_pwm_audio_handle; + PWM_AUDIO_CHECK(NULL != handle, PWM_AUDIO_NOT_INITIALIZED, ESP_ERR_INVALID_STATE); + PWM_AUDIO_CHECK(NULL != volume, PWM_AUDIO_PARAM_ADDR_ERROR, ESP_ERR_INVALID_ARG); + *volume = handle->volume; + return ESP_OK; +} + +esp_err_t IRAM_ATTR pwm_audio_write(uint8_t *inbuf, size_t inbuf_len, size_t *bytes_written, TickType_t ticks_to_wait) +{ + esp_err_t res = ESP_OK; + pwm_audio_data_t *handle = g_pwm_audio_handle; + PWM_AUDIO_CHECK(NULL != handle, PWM_AUDIO_NOT_INITIALIZED, ESP_ERR_INVALID_STATE); + PWM_AUDIO_CHECK(inbuf != NULL && bytes_written != NULL, "Invalid pointer", ESP_ERR_INVALID_ARG); + PWM_AUDIO_CHECK(inbuf_len != 0, "Length should not be zero", ESP_ERR_INVALID_ARG); + + *bytes_written = 0; + ringbuf_handle_t *rb = handle->ringbuf; + TickType_t start_ticks = xTaskGetTickCount(); + + while (inbuf_len) { + uint32_t free = rb_get_free(rb); + if (ESP_OK == rb_wait_semaphore(rb, ticks_to_wait)) { + uint32_t bytes_can_write = inbuf_len; + + if (inbuf_len > free) { + bytes_can_write = free; + } + + bytes_can_write &= 0xfffffffc; // 按 4 字节对齐。/ Align to a multiple of 4. + + if (0 == bytes_can_write) { + *bytes_written += inbuf_len; // 丢掉末尾未对齐字节。/ Discard the last misaligned bytes. + return ESP_OK; + } + + // PWM 分辨率与采样位宽之差。/ Difference between PWM resolution and sample width. + int8_t shift = handle->bits_per_sample - handle->config.duty_resolution; + uint32_t len = bytes_can_write; + + switch (handle->bits_per_sample) { + case 8: { + if (shift < 0) { + // PWM 分辨率大于 8 bit 时要扩位。/ Expand the value when PWM resolution is wider than 8 bits. + uint16_t value; + uint8_t temp; + shift = -shift; + len >>= 1; + bytes_can_write >>= 1; + + for (size_t i = 0; i < len; i++) { + temp = (inbuf[i] * handle->volume / VOLUME_0DB) + 0x7f; // 偏置到无符号。/ Offset to unsigned. + value = temp << shift; + rb_write_byte(rb, value); + rb_write_byte(rb, value >> 8); + } + } else { + uint8_t value; + + for (size_t i = 0; i < len; i++) { + value = (inbuf[i] * handle->volume / VOLUME_0DB) + 0x7f; // 偏置到无符号。/ Offset to unsigned. + rb_write_byte(rb, value); + } + } + } + break; + + case 16: { + len >>= 1; + uint16_t *buf_16b = (uint16_t *)inbuf; + static uint16_t value_16b; + int16_t temp; + + if (handle->config.duty_resolution > 8) { + for (size_t i = 0; i < len; i++) { + temp = buf_16b[i]; + temp = temp * handle->volume / VOLUME_0DB; + value_16b = temp + 0x7fff; // 偏置到无符号。/ Offset to unsigned. + value_16b >>= shift; + rb_write_byte(rb, value_16b); + rb_write_byte(rb, value_16b >> 8); + } + } else { + // PWM 分辨率 8 bit 时只传一字节。/ 8-bit PWM resolution: send one byte only. + for (size_t i = 0; i < len; i++) { + temp = buf_16b[i]; + temp = temp * handle->volume / VOLUME_0DB; + value_16b = temp + 0x7fff; // 偏置到无符号。/ Offset to unsigned. + value_16b >>= shift; + rb_write_byte(rb, value_16b); + } + } + } + break; + + case 32: { + len >>= 2; + uint32_t *buf_32b = (uint32_t *)inbuf; + uint32_t value; + int32_t temp; + + if (handle->config.duty_resolution > 8) { + for (size_t i = 0; i < len; i++) { + temp = buf_32b[i]; + temp = temp * handle->volume / VOLUME_0DB; + value = temp + 0x7fffffff; // 偏置到无符号。/ Offset to unsigned. + value >>= shift; + rb_write_byte(rb, value); + rb_write_byte(rb, value >> 8); + } + } else { + // PWM 分辨率 8 bit 时只传一字节。/ 8-bit PWM resolution: send one byte only. + for (size_t i = 0; i < len; i++) { + temp = buf_32b[i]; + temp = temp * handle->volume / VOLUME_0DB; + value = temp + 0x7fffffff; // 偏置到无符号。/ Offset to unsigned. + value >>= shift; + rb_write_byte(rb, value); + } + } + } + break; + + default: + break; + } + + inbuf += bytes_can_write; + inbuf_len -= bytes_can_write; + *bytes_written += bytes_can_write; + + } else { + res = ESP_FAIL; + } + + if ((xTaskGetTickCount() - start_ticks) >= ticks_to_wait) { + return res; + } + } + + return res; +} + +esp_err_t pwm_audio_start(void) +{ + esp_err_t res; + pwm_audio_data_t *handle = g_pwm_audio_handle; + PWM_AUDIO_CHECK(NULL != handle, PWM_AUDIO_NOT_INITIALIZED, ESP_ERR_INVALID_STATE); + PWM_AUDIO_CHECK(handle->status == PWM_AUDIO_STATUS_IDLE, PWM_AUDIO_STATUS_ERROR, ESP_ERR_INVALID_STATE); + handle->status = PWM_AUDIO_STATUS_BUSY; + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + res = gptimer_enable(g_gptimer); + PWM_AUDIO_CHECK(res == ESP_OK, "gptimer enable fail", res); + res = gptimer_start(g_gptimer); + PWM_AUDIO_CHECK(res == ESP_OK, "gptimer start fail", res); +#else + res = timer_enable_intr(handle->config.tg_num, handle->config.timer_num); + PWM_AUDIO_CHECK(res == ESP_OK, "timer enable Interrupt fail", res); + res = timer_start(handle->config.tg_num, handle->config.timer_num); + PWM_AUDIO_CHECK(res == ESP_OK, "timer start fail", res); +#endif + return ESP_OK; +} + +esp_err_t pwm_audio_stop(void) +{ + esp_err_t res; + pwm_audio_data_t *handle = g_pwm_audio_handle; + PWM_AUDIO_CHECK(NULL != handle, PWM_AUDIO_NOT_INITIALIZED, ESP_ERR_INVALID_STATE); + PWM_AUDIO_CHECK(handle->status == PWM_AUDIO_STATUS_BUSY, PWM_AUDIO_STATUS_ERROR, ESP_ERR_INVALID_STATE); + + // 只停定时器,PWM 继续出,减少开关噪声。 + // / Stop the timer only; keep PWM output to cut switching noise. + // 关定时器中断。/ Disable the timer interrupt. +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + res = gptimer_stop(g_gptimer); + PWM_AUDIO_CHECK(res == ESP_OK, "gptimer stop failed", res); + res = gptimer_disable(g_gptimer); + PWM_AUDIO_CHECK(res == ESP_OK, "gptimer disable failed", res); +#else + res = timer_pause(handle->config.tg_num, handle->config.timer_num); + PWM_AUDIO_CHECK(res == ESP_OK, "timer pause failed", res); + res = timer_disable_intr(handle->config.tg_num, handle->config.timer_num); + PWM_AUDIO_CHECK(res == ESP_OK, "timer disable failed", res); +#endif + rb_flush(handle->ringbuf); // 清空环形缓冲,避免残留噪声。/ Flush the ring buffer so leftover samples do not play. + handle->status = PWM_AUDIO_STATUS_IDLE; + return ESP_OK; +} + +esp_err_t pwm_audio_deinit(void) +{ + esp_err_t res; + pwm_audio_data_t *handle = g_pwm_audio_handle; + PWM_AUDIO_CHECK(handle != NULL, PWM_AUDIO_PARAM_ADDR_ERROR, ESP_FAIL); + handle->status = PWM_AUDIO_STATUS_UN_INIT; + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + res = gptimer_del_timer(g_gptimer); + PWM_AUDIO_CHECK(res == ESP_OK, "gptimer del failed", res); +#else + res = timer_deinit(handle->config.tg_num, handle->config.timer_num); + PWM_AUDIO_CHECK(res == ESP_OK, "timer deinit failed", res); +#endif + + for (size_t i = 0; i < PWM_AUDIO_CH_MAX; i++) { + if (handle->ledc_channel[i].gpio_num >= 0) { + ledc_stop(handle->ledc_channel[i].speed_mode, handle->ledc_channel[i].channel, 0); + } + } + + // 通道 GPIO 改成输入。/ Set channel GPIOs to input. + for (size_t i = 0; i < PWM_AUDIO_CH_MAX; i++) { + if (handle->ledc_channel[i].gpio_num >= 0) { + gpio_set_direction(handle->ledc_channel[i].gpio_num, GPIO_MODE_INPUT); + } + } + + rb_destroy(handle->ringbuf); + heap_caps_free(handle); + g_pwm_audio_handle = NULL; + return ESP_OK; +} diff --git a/firmware/targets/mindreset_read_pico/components/read_pico/CMakeLists.txt b/firmware/targets/mindreset_read_pico/components/read_pico/CMakeLists.txt new file mode 100644 index 0000000..5aac8e6 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/read_pico/CMakeLists.txt @@ -0,0 +1,12 @@ +set(read_pico_requires epdiy e0470_epaper_waveform sc7a20h cst836u read_pico_pmu fatfs sy7636a fca9555 + esp_driver_gpio esp_driver_i2c esp_driver_ledc esp_driver_rmt esp_driver_sdmmc pwm_audio) + +idf_component_register( + SRCS "read_pico_init.c" "read_pico_board.c" "read_pico_epd_timing.c" "read_pico_buzzer.c" "buzzer_score.c" "buzzer_duty_map.c" "buzzer_1bit.c" "read_pico_sd.c" "read_pico_flash_hpm.c" + INCLUDE_DIRS "include" + REQUIRES ${read_pico_requires} + PRIV_REQUIRES bootloader_support spi_flash freertos + # flash_hpm_zb.c 只定义了一个覆盖 IDF weak 符号的表,没有任何 + # 代码引用它,不整包链接的话这个目标文件会被丢掉,覆盖就不生效。 + WHOLE_ARCHIVE +) diff --git a/firmware/targets/mindreset_read_pico/components/read_pico/Kconfig b/firmware/targets/mindreset_read_pico/components/read_pico/Kconfig new file mode 100644 index 0000000..a086537 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/read_pico/Kconfig @@ -0,0 +1,33 @@ +menu "Read Pico board configuration" + +config READ_PICO_I2C_PORT + int "I2C controller" + default 0 + range 0 1 + +config READ_PICO_I2C_SCL_GPIO + int "I2C SCL GPIO" + default 40 + range 0 48 + +config READ_PICO_I2C_SDA_GPIO + int "I2C SDA GPIO" + default 39 + range 0 48 + +config READ_PICO_I2C_FREQ_HZ + int "I2C frequency (Hz)" + default 400000 + range 10000 400000 + +config READ_PICO_IOE_INT_GPIO + int "FCA9555 interrupt GPIO" + default 41 + range 0 48 + +config READ_PICO_TP_INT_GPIO + int "CST836U interrupt GPIO" + default 43 + range 0 48 + +endmenu diff --git a/firmware/targets/mindreset_read_pico/components/read_pico/LICENSE b/firmware/targets/mindreset_read_pico/components/read_pico/LICENSE new file mode 100644 index 0000000..500992f --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/read_pico/LICENSE @@ -0,0 +1,17 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +Copyright 2026 mindreset + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/firmware/targets/mindreset_read_pico/components/read_pico/README.md b/firmware/targets/mindreset_read_pico/components/read_pico/README.md new file mode 100644 index 0000000..1e87e85 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/read_pico/README.md @@ -0,0 +1,41 @@ +# read_pico + +小纸 Pico 墨水屏开发板(ESP32-S3 + 4.7 寸单色面板)的板级支持。只包含板级而非芯片级的内容:共享 I2C、epdiy 板定义、经 SY7636A 与 FCA9555 的屏电源轨、TF 卡、蜂鸣器、flash 高性能模式补丁。 + +Board support for the Read Pico e-paper dev board (ESP32-S3 + 4.7" mono panel). Owns +everything that is board-specific rather than chip-specific: the shared I2C bus, the +epdiy board definition, screen rails via SY7636A and an FCA9555 expander, the TF card, +the buzzer, and the flash high-performance-mode workaround. + +组件名 / Registry name: `mindreset/read_pico` + +## 做什么 / What it does + +- `read_pico_init()`:初始化面板、探测 TF 卡、检查加速度计后使其掉电、启动 PMU 主机与触摸控制器。外设失败只清除对应的 `*_ready` 标志;仅面板失败视为致命。/ Brings up the panel, probes the card, checks the accelerometer and powers it back down, starts the PMU host and the touch controller. A failing peripheral only clears its `*_ready` flag; only the panel failing is fatal. +- `read_pico_get_status()` / `read_pico_get_ioe_status()`:扩展口引脚、电源轨状态与 SY7636A 寄存器的一份快照,供电源页和 IO 页使用。/ One snapshot of expander pins, rail state and the SY7636A registers, for the power and IO pages. +- 电源轨:`read_pico_rails_on()`、`read_pico_toggle_vcomctl()`,以及 epdiy 板级钩子。/ Rails: `read_pico_rails_on()`, `read_pico_toggle_vcomctl()`, plus the epdiy board hooks. +- 加速度计轴映射:芯片坐标 → 设备坐标(屏朝上为 +Z)。/ Accelerometer axis mapping: chip → device coordinates (screen up is +Z). +- `read_pico_sd_*`:非阻塞的探卡、挂载、格式化与容量查询。/ Non-blocking card probe, mount, format, capacity. +- `read_pico_buzzer_tone()`:LEDC 发声,按给定时长阻塞。/ LEDC tone, blocking for the given duration. +- `read_pico_flash_hpm.c`:本板的 Zbit(`0x5E`)flash 需要写状态寄存器才能运行于 120 MHz;IDF 自带的表只包含 0xC8 / 0x20 厂家。/ This board ships a Zbit (`0x5E`) flash whose status register must be written to reach 120 MHz; IDF's table only lists the 0xC8 / 0x20 vendors. + +## 引脚 / Pins + +可通过 `idf.py menuconfig` → *Read Pico board configuration* 配置:I2C 端口、SCL/SDA、总线频率、FCA9555 INT、CST836U INT。面板总线、TF 卡与蜂鸣器引脚固定在 `read_pico_board.c` / `read_pico_sd.c` / `read_pico_buzzer.c` 中。 + +Configurable through `idf.py menuconfig` → *Read Pico board configuration*: I2C port, +SCL/SDA, bus frequency, FCA9555 INT and CST836U INT. The panel bus, TF card and buzzer +pins are fixed in `read_pico_board.c` / `read_pico_sd.c` / `read_pico_buzzer.c`. + +## 依赖 / Dependencies + +`epdiy`、`sy7636a`、`fca9555`、`cst836u`、`sc7a20h`、`read_pico_pmu`、`e0470_epaper_waveform`。 + +`epdiy`, `sy7636a`, `fca9555`, `cst836u`, `sc7a20h`, `read_pico_pmu`, +`e0470_epaper_waveform`. + +## 许可 / License + +Apache-2.0,见 [LICENSE](LICENSE)。 + +Apache-2.0, see [LICENSE](LICENSE). diff --git a/firmware/targets/mindreset_read_pico/components/read_pico/buzzer_1bit.c b/firmware/targets/mindreset_read_pico/components/read_pico/buzzer_1bit.c new file mode 100644 index 0000000..6decb4d --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/read_pico/buzzer_1bit.c @@ -0,0 +1,130 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * 多路音高排成 1bit 脉冲串。 + * + * Schedule several pitches into a 1-bit pulse train. + */ + +#include "buzzer_1bit.h" + +int buzzer_1bit_build( + const uint16_t *hz, const uint16_t *weight, int nvoice, const buzzer_1bit_cfg_t *cfg, + uint32_t window_us, buzzer_1bit_sym_t *out, int max_out, buzzer_1bit_stats_t *st +) { + if (hz == NULL || out == NULL || nvoice <= 0 || max_out <= 0 || window_us == 0) { + return 0; + } + if (nvoice > BUZZER_1BIT_VOICES_MAX) { + nvoice = BUZZER_1BIT_VOICES_MAX; + } + + buzzer_1bit_cfg_t c = cfg != NULL ? *cfg : (buzzer_1bit_cfg_t)BUZZER_1BIT_CFG_DEFAULT; + if (c.pulse_us == 0) { + c.pulse_us = 1; + } + + uint32_t period[BUZZER_1BIT_VOICES_MAX]; + uint32_t next[BUZZER_1BIT_VOICES_MAX]; + uint16_t width[BUZZER_1BIT_VOICES_MAX]; + int live = 0; + for (int i = 0; i < nvoice; i++) { + if (hz[i] == 0) { + continue; + } + period[live] = (1000000u + (uint32_t)hz[i] / 2u) / (uint32_t)hz[i]; + if (period[live] == 0) { + period[live] = 1; + } + uint32_t w = c.pulse_us; + if (weight != NULL) { + w = (w * (uint32_t)weight[i]) / 256u; + } + if (w == 0) { + w = 1; + } + if (w >= period[live]) { + w = period[live] - 1; + } + width[live] = (uint16_t)w; + next[live] = (uint32_t)live * c.pulse_us; + live++; + } + if (live == 0) { + return 0; + } + + buzzer_1bit_stats_t local = { 0 }; + local.window_us = window_us; + uint32_t t = 0; + int nout = 0; + + while (t < window_us && nout < max_out) { + int pick = 0; + uint32_t soon = next[0]; + for (int i = 1; i < live; i++) { + if (next[i] < soon) { + soon = next[i]; + pick = i; + } + } + + uint32_t start = soon; + if (start < t) { + uint32_t slip = t - start; + if (slip > (uint32_t)c.max_slip_us) { + local.dropped++; + next[pick] += period[pick]; + continue; + } + start = t; + local.serialized++; + } + if (start >= window_us) { + break; + } + + uint32_t hi = width[pick]; + if (start + hi > window_us) { + hi = window_us - start; + } + if (hi == 0) { + break; + } + + next[pick] += period[pick]; + t = start + hi; + local.emitted++; + local.high_us += hi; + + uint32_t nxt = next[0]; + for (int i = 1; i < live; i++) { + if (next[i] < nxt) { + nxt = next[i]; + } + } + if (nxt < t) { + nxt = t; + } + uint32_t lo = nxt - t; + if (t + lo > window_us) { + lo = window_us - t; + } + if (lo == 0 && nout > 0) { + out[nout - 1].high_us = (uint16_t)(out[nout - 1].high_us + (uint16_t)hi); + local.merged_high++; + continue; + } + + out[nout].high_us = (uint16_t)hi; + out[nout].low_us = (uint16_t)(lo > 65535u ? 65535u : lo); + nout++; + t += lo; + } + + if (st != NULL) { + *st = local; + } + return nout; +} diff --git a/firmware/targets/mindreset_read_pico/components/read_pico/buzzer_duty_map.c b/firmware/targets/mindreset_read_pico/components/read_pico/buzzer_duty_map.c new file mode 100644 index 0000000..5b5e577 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/read_pico/buzzer_duty_map.c @@ -0,0 +1,185 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * duty = clamp(bias + gain * sample, min, max),sample = pcm / 32768。 + * pwm_audio 16bit/10bit:duty = (pcm + 32767) >> 6,这里做逆变换。 + * + * duty = clamp(bias + gain * sample, min, max), sample = pcm / 32768. + * pwm_audio 16-bit/10-bit: duty = (pcm + 32767) >> 6; this inverts that map. + */ + +#include "buzzer_duty_map.h" + +#include + +int buzzer_duty_map_valid(const buzzer_duty_map_t *m) { + if (m == NULL) { + return 0; + } + if (m->duty_min > m->duty_max) { + return 0; + } + if (m->duty_max > BUZZER_DUTY_FS) { + return 0; + } + if (m->bias > BUZZER_DUTY_FS || m->gain > BUZZER_DUTY_GAIN_MAX) { + return 0; + } + return 1; +} + +int32_t buzzer_duty_legal_pcm(const buzzer_duty_map_t *m) { + if (!buzzer_duty_map_valid(m) || m->gain == 0) { + return 0; + } + int32_t up = ((int32_t)m->duty_max - (int32_t)m->bias) * 32768 / (int32_t)m->gain; + int32_t dn = ((int32_t)m->bias - (int32_t)m->duty_min) * 32768 / (int32_t)m->gain; + if (up < 0) { + up = 0; + } + if (dn < 0) { + dn = 0; + } + int32_t t = up < dn ? up : dn; + return (t * 255) / 256; +} + +uint16_t buzzer_duty_from_sample_ex(const buzzer_duty_map_t *m, int16_t pcm, int *clipped) { + int32_t duty = (int32_t)m->bias + ((int32_t)m->gain * (int32_t)pcm) / 32768; + int clip = 0; + if (duty < (int32_t)m->duty_min) { + duty = (int32_t)m->duty_min; + clip = 1; + } + if (duty > (int32_t)m->duty_max) { + duty = (int32_t)m->duty_max; + clip = 1; + } + if (clipped != NULL) { + *clipped = clip; + } + return (uint16_t)duty; +} + +uint16_t buzzer_duty_from_sample(const buzzer_duty_map_t *m, int16_t pcm) { + return buzzer_duty_from_sample_ex(m, pcm, NULL); +} + +int16_t buzzer_duty_to_pwm_audio_pcm(uint16_t duty) { + if (duty > BUZZER_DUTY_FS) { + duty = BUZZER_DUTY_FS; + } + int32_t s = ((int32_t)duty << 6) - 32767; + if (s > 32767) { + s = 32767; + } + if (s < -32768) { + s = -32768; + } + return (int16_t)s; +} + +void buzzer_duty_map_pcm(const buzzer_duty_map_t *m, const int16_t *in, int16_t *out, int n) { + for (int i = 0; i < n; i++) { + out[i] = buzzer_duty_to_pwm_audio_pcm(buzzer_duty_from_sample(m, in[i])); + } +} + +void buzzer_duty_accum_reset(buzzer_duty_accum_t *a) { + if (a == NULL) { + return; + } + a->min = 0xffff; + a->max = 0; + a->n = 0; + a->clips = 0; + a->sum = 0; + a->sum_sq = 0; +} + +void buzzer_duty_accum_add(buzzer_duty_accum_t *a, uint16_t duty, int clipped) { + if (a == NULL) { + return; + } + if (duty < a->min) { + a->min = duty; + } + if (duty > a->max) { + a->max = duty; + } + a->n++; + a->sum += duty; + a->sum_sq += (uint64_t)duty * (uint64_t)duty; + if (clipped) { + a->clips++; + } +} + +uint32_t buzzer_u64_isqrt(uint64_t x) { + if (x == 0) { + return 0; + } + uint64_t op = x; + uint64_t res = 0; + uint64_t one = 1ull << 62; + while (one > op) { + one >>= 2; + } + while (one != 0) { + if (op >= res + one) { + op -= res + one; + res = (res >> 1) + one; + } else { + res >>= 1; + } + one >>= 2; + } + return (uint32_t)res; +} + +void buzzer_duty_accum_result( + const buzzer_duty_accum_t *a, uint16_t *min, uint16_t *max, uint32_t *mean_x100, + uint32_t *ac_rms_x100, uint32_t *n, uint32_t *clips +) { + uint32_t nn = a != NULL ? a->n : 0; + if (n != NULL) { + *n = nn; + } + if (clips != NULL) { + *clips = a != NULL ? a->clips : 0; + } + if (nn == 0) { + if (min != NULL) { + *min = 0; + } + if (max != NULL) { + *max = 0; + } + if (mean_x100 != NULL) { + *mean_x100 = 0; + } + if (ac_rms_x100 != NULL) { + *ac_rms_x100 = 0; + } + return; + } + if (min != NULL) { + *min = a->min; + } + if (max != NULL) { + *max = a->max; + } + if (mean_x100 != NULL) { + *mean_x100 = (uint32_t)((a->sum * 100ull) / nn); + } + if (ac_rms_x100 != NULL) { + uint64_t num = a->sum_sq * (uint64_t)nn; + uint64_t den = a->sum * a->sum; + if (num <= den) { + *ac_rms_x100 = 0; + } else { + *ac_rms_x100 = buzzer_u64_isqrt((num - den) * 10000ull / ((uint64_t)nn * (uint64_t)nn)); + } + } +} diff --git a/firmware/targets/mindreset_read_pico/components/read_pico/buzzer_score.c b/firmware/targets/mindreset_read_pico/components/read_pico/buzzer_score.c new file mode 100644 index 0000000..0c93eb2 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/read_pico/buzzer_score.c @@ -0,0 +1,830 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * 正弦+弱二次谐波,短起音/衰减/释放。延音线合成一条,同音无连线则重触发。 + * 声部按 note id 归属,左右手同音高互不关闭。 + * 混音增益按整曲峰值一次性定标,不按声部上限或瞬时声部数缩放。 + * ENHANCE:每声部按基频做连续整数谐波,1.2–5 kHz 平滑加权后 L1 归一。 + * 不是对方波频响截图做反向 EQ。 + * + * Sine plus a weak 2nd harmonic, short attack/decay/release. A tie is one + * voice; same pitch without a tie retriggers. Voices belong to note id, so + * left and right hands on the same pitch do not steal each other. + * Mix gain is scaled once from the whole-piece peak, not by voice cap or + * live voice count. ENHANCE: integer harmonics of each voice's fundamental, + * smooth 1.2–5 kHz weights, L1-normalized. Not an inverse EQ of a square + * frequency-response screenshot. + */ + +#include "buzzer_score.h" + +#include +#include +#include + +#include "buzzer_duty_map.h" + +#define WT BUZZER_SCORE_WT +#define NVOICE BUZZER_SCORE_VOICES +#define SR BUZZER_SCORE_SR + +#define T16 120u +#define T8 240u +#define T4 480u +#define T4D 720u +#define T2 960u +#define T2D 1440u + +#define M_E3 52 +#define M_F3 53 +#define M_G3 55 +#define M_A3 57 +#define M_B3 59 +#define M_C4 60 +#define M_D4 62 +#define M_F4 65 +#define M_G4 67 +#define M_A4 69 +#define M_B4 71 +#define M_C5 72 +#define M_D5 74 +#define M_E5 76 +#define M_F5 77 +#define M_FS5 78 +#define M_G5 79 + +#define ATK_N ((SR * 5) / 1000) +#define DEC_N ((SR * 60) / 1000) +#define REL_N ((SR * 72) / 1000) +#define SUS_ENV 15000 +#define HOLD_ATK_N ((SR * 4) / 1000) +#define HOLD_DEC_N ((SR * 40) / 1000) +#define HOLD_REL_N ((SR * 20) / 1000) +#define HOLD_SUS 14000 +#define MIX_GAIN_SHIFT BUZZER_SCORE_MIX_GAIN_SHIFT +#define SAMPLE_CAP ((uint32_t)SR * 8u) +#define HARM_MAX 24 +#define ENHANCE_WSUM 256 +#define ENHANCE_ID_MIX 80 +#define ENHANCE_EX_MIX 176 +#define ENHANCE_EX_SPAN 6 + +#define ST_OFF 0 +#define ST_ON 1 +#define ST_REL 2 + +#define EV_MAX 128 + +typedef struct { + uint32_t sample; + uint16_t id; + uint8_t midi; + uint8_t vel; + uint8_t on; + uint8_t staff; +} ev_t; + +typedef struct { + uint16_t id; + uint8_t midi; + uint8_t vel; + uint8_t stage; + uint8_t n_harm; + uint16_t hw[HARM_MAX]; + uint32_t phase; + uint32_t inc; + uint32_t age; + uint32_t rel_age; + int32_t env; + int32_t rel_from; +} voice_t; + +static const buzzer_score_note_t k_notes[] = { + { 0, T8, 1, M_E5, 184, 0 }, + { 240, T16, 2, M_E5, 168, 0 }, + { 360, T16, 3, M_E5, 168, 0 }, + { 480, T8, 4, M_C5, 160, 0 }, + { 720, T8, 5, M_E5, 176, 0 }, + { 960, T8, 6, M_C5, 158, 0 }, + { 1200, T8, 7, M_G4, 150, 0 }, + { 1440, T8, 8, M_C5, 158, 0 }, + { 1680, T8, 9, M_E5, 176, 0 }, + + { 1920, T8, 10, M_F5, 180, 0 }, + { 2160, T16, 11, M_F5, 166, 0 }, + { 2280, T16, 12, M_F5, 166, 0 }, + { 2400, T8, 13, M_C5, 156, 0 }, + { 2640, T8, 14, M_F5, 174, 0 }, + { 2880, T8, 15, M_C5, 154, 0 }, + { 3120, T8, 16, M_A4, 148, 0 }, + { 3360, T8, 17, M_D5, 164, 0 }, + { 3600, T8, 18, M_C5, 152, 0 }, + + { 3840, T8, 19, M_B4, 168, 0 }, + { 4080, T16, 20, M_B4, 156, 0 }, + { 4200, T16, 21, M_C5, 158, 0 }, + { 4320, T8, 22, M_D5, 164, 0 }, + { 4560, T8, 23, M_E5, 170, 0 }, + { 4800, T8, 24, M_FS5, 176, 0 }, + { 5040, T8, 25, M_D5, 164, 0 }, + { 5280, T8, 26, M_E5, 170, 0 }, + { 5520, T8, 27, M_FS5, 176, 0 }, + + { 5760, T8, 28, M_G5, 186, 0 }, + { 6000, T8, 29, M_G5, 180, 0 }, + { 6240, T8, 30, M_D5, 166, 0 }, + { 6480, T8, 31, M_B4, 158, 0 }, + { 6720, T4, 32, M_G4, 170, 0 }, + { 7200, T4, 33, M_D4, 148, 0 }, + { 7200, T4, 34, M_F4, 156, 0 }, + { 7200, T4, 35, M_B4, 188, 0 }, + + { 0, 1200, 36, M_E3, 78, 1 }, + { 0, 1200, 37, M_G3, 78, 1 }, + { 0, 1200, 38, M_C4, 82, 1 }, + { 1200, T4D, 39, M_E3, 78, 1 }, + { 1200, T4D, 40, M_G3, 78, 1 }, + { 1200, T4D, 41, M_C4, 82, 1 }, + + { 1920, 1200, 42, M_F3, 78, 1 }, + { 1920, 1200, 43, M_A3, 78, 1 }, + { 1920, 1200, 44, M_C4, 82, 1 }, + { 3120, T4D, 45, M_F3, 78, 1 }, + { 3120, T4D, 46, M_A3, 78, 1 }, + { 3120, T4D, 47, M_C4, 82, 1 }, + + { 3840, T2, 48, M_F3, 78, 1 }, + { 3840, T2, 49, M_A3, 78, 1 }, + { 3840, T2, 50, M_D4, 82, 1 }, + { 4800, T2, 51, M_F3, 78, 1 }, + { 4800, T2, 52, M_A3, 78, 1 }, + { 4800, T2, 53, M_D4, 82, 1 }, + + { 5760, T2D, 54, M_G3, 78, 1 }, + { 5760, T2D, 55, M_B3, 78, 1 }, + { 5760, T2D, 56, M_D4, 82, 1 }, + { 7200, T4, 57, M_G3, 78, 1 }, + { 7200, T4, 58, M_B3, 78, 1 }, + { 7200, T4, 59, M_D4, 82, 1 }, +}; + +static int16_t s_sin[WT]; +static uint32_t s_inc[128]; +static ev_t s_ev[EV_MAX]; +static int s_nev; +static bool s_ready; +static buzzer_score_timbre_t s_timbre = BUZZER_SCORE_TIMBRE_BRIGHT; +static buzzer_score_env_t s_env = BUZZER_SCORE_ENV_PIANO; +static uint8_t s_staff_mask = BUZZER_SCORE_STAFF_BOTH; +static int8_t s_shift = 24; +static int32_t s_mix_gain_q = 1 << MIX_GAIN_SHIFT; +static int32_t s_raw_peak; +static int s_gain_enable; +static const buzzer_score_harmonics_t k_soft = { 256, 42, 0, 0 }; +static buzzer_score_harmonics_t s_bright = { 256, 0, 80, 48 }; + +static voice_t s_voice[NVOICE]; +static uint32_t s_pos; +static int s_evi; +static bool s_done; + +uint32_t buzzer_score_tick_to_sample(uint32_t tick) { + return (uint32_t)(((uint64_t)tick * 800ull) / 27ull); +} + +uint32_t buzzer_score_body_samples(void) { + return buzzer_score_tick_to_sample(BUZZER_SCORE_TICKS); +} + +int buzzer_score_note_count(void) { + return (int)(sizeof(k_notes) / sizeof(k_notes[0])); +} + +const buzzer_score_note_t *buzzer_score_notes(void) { + return k_notes; +} + +static int ev_cmp(const void *a, const void *b) { + const ev_t *ea = (const ev_t *)a; + const ev_t *eb = (const ev_t *)b; + if (ea->sample != eb->sample) { + return ea->sample < eb->sample ? -1 : 1; + } + if (ea->on != eb->on) { + return ea->on ? 1 : -1; + } + return (int)ea->id - (int)eb->id; +} + +void buzzer_score_init(void) { + if (s_ready) { + return; + } + for (int i = 0; i < WT; i++) { + s_sin[i] = (int16_t)(sinf((float)i * 6.28318530718f / (float)WT) * 32767.f); + } + for (int m = 0; m < 128; m++) { + double hz = 440.0 * pow(2.0, ((double)m - 69.0) / 12.0); + s_inc[m] = (uint32_t)(hz * 4294967296.0 / (double)SR + 0.5); + } + + const int n = buzzer_score_note_count(); + int e = 0; + for (int i = 0; i < n && e + 1 < EV_MAX; i++) { + const buzzer_score_note_t *note = &k_notes[i]; + s_ev[e].sample = buzzer_score_tick_to_sample(note->tick); + s_ev[e].id = note->id; + s_ev[e].midi = note->midi; + s_ev[e].vel = note->vel; + s_ev[e].on = 1; + s_ev[e].staff = note->staff; + e++; + s_ev[e].sample = buzzer_score_tick_to_sample(note->tick + note->dur); + s_ev[e].id = note->id; + s_ev[e].midi = note->midi; + s_ev[e].vel = note->vel; + s_ev[e].on = 0; + s_ev[e].staff = note->staff; + e++; + } + s_nev = e; + qsort(s_ev, (size_t)s_nev, sizeof(s_ev[0]), ev_cmp); + s_ready = true; +} + +void buzzer_score_reset(buzzer_score_stats_t *st) { + memset(s_voice, 0, sizeof(s_voice)); + s_pos = 0; + s_evi = 0; + s_done = false; + if (st != NULL) { + memset(st, 0, sizeof(*st)); + } +} + +bool buzzer_score_idle(void) { + if (!s_done) { + return false; + } + for (int i = 0; i < NVOICE; i++) { + if (s_voice[i].stage != ST_OFF) { + return false; + } + } + return true; +} + +#define HARM_NYQ_LIM 1879048192ull + +static int harm_ok(uint32_t inc, unsigned n) { + return ((uint64_t)inc * (uint64_t)n) < HARM_NYQ_LIM; +} + +static const buzzer_score_harmonics_t *active_harmonics(void) { + return s_timbre == BUZZER_SCORE_TIMBRE_BRIGHT ? &s_bright : &k_soft; +} + +static int32_t wave_at(uint32_t phase, uint32_t inc, const buzzer_score_harmonics_t *h) { + int32_t acc = 0; + int32_t wsum = 0; + if (h->w1 != 0 && harm_ok(inc, 1)) { + acc += (int32_t)s_sin[phase >> 24] * (int32_t)h->w1; + wsum += (int32_t)h->w1; + } + if (h->w2 != 0 && harm_ok(inc, 2)) { + acc += (int32_t)s_sin[(phase * 2u) >> 24] * (int32_t)h->w2; + wsum += (int32_t)h->w2; + } + if (h->w3 != 0 && harm_ok(inc, 3)) { + acc += (int32_t)s_sin[(phase * 3u) >> 24] * (int32_t)h->w3; + wsum += (int32_t)h->w3; + } + if (h->w5 != 0 && harm_ok(inc, 5)) { + acc += (int32_t)s_sin[(phase * 5u) >> 24] * (int32_t)h->w5; + wsum += (int32_t)h->w5; + } + if (wsum <= 0) { + return 0; + } + return acc / wsum; +} + +static int32_t enhance_id_w(unsigned k) { + if (k == 1) { + return 112; + } + if (k == 2) { + return 40; + } + if (k == 3) { + return 26; + } + if (k == 4) { + return 14; + } + return 0; +} + +// 探索权重:1.2–5 kHz 平滑凸起,肩部到 0.8/6.2 kHz。不是 4 kHz 单点。 +// / Explore weight: smooth 1.2–5 kHz bump, shoulders at 0.8/6.2 kHz. Not a 4 kHz spike. +static int32_t enhance_ex_w(uint32_t f_hz) { + if (f_hz <= 800u || f_hz >= 6200u) { + return 0; + } + if (f_hz >= 1200u && f_hz <= 5000u) { + uint32_t x = (f_hz - 1200u) * 256u / 3800u; + if (x > 256u) { + x = 256u; + } + uint32_t bump = (4u * x * (256u - x)) / 256u; + return (int32_t)((140u * bump) / 256u); + } + if (f_hz < 1200u) { + return (int32_t)(((f_hz - 800u) * 22u) / 400u); + } + return (int32_t)(((6200u - f_hz) * 16u) / 1200u); +} + +static void voice_fill_enhance(voice_t *v) { + int32_t id_raw[HARM_MAX]; + int32_t ex_raw[HARM_MAX]; + int n = 0; + int32_t id_sum = 0; + int best = 0; + int32_t best_w = -1; + + for (unsigned k = 1; k <= HARM_MAX; k++) { + if (!harm_ok(v->inc, k)) { + break; + } + uint32_t f_hz = (uint32_t)(((uint64_t)v->inc * (uint64_t)k * (uint64_t)SR) >> 32); + id_raw[n] = enhance_id_w(k); + ex_raw[n] = enhance_ex_w(f_hz); + id_sum += id_raw[n]; + if (ex_raw[n] > best_w) { + best_w = ex_raw[n]; + best = n; + } + n++; + } + if (n <= 0 || id_sum <= 0) { + v->n_harm = 1; + v->hw[0] = ENHANCE_WSUM; + return; + } + + // 低音只保留一段连续探索谐波,避免 20 个分量摊薄能量。 + // / Bass keeps one contiguous explore-harmonic span so 20 partials do not dilute energy. + int ex0 = best - (ENHANCE_EX_SPAN / 2 - 1); + int ex1 = ex0 + ENHANCE_EX_SPAN - 1; + if (ex0 < 0) { + ex1 -= ex0; + ex0 = 0; + } + if (ex1 >= n) { + ex0 -= (ex1 - (n - 1)); + ex1 = n - 1; + if (ex0 < 0) { + ex0 = 0; + } + } + + int32_t ex_sum = 0; + for (int i = 0; i < n; i++) { + if (i < ex0 || i > ex1) { + ex_raw[i] = 0; + } + ex_sum += ex_raw[i]; + } + + int32_t assigned = 0; + v->n_harm = (uint8_t)n; + for (int i = 0; i < n; i++) { + int32_t w = id_raw[i] * ENHANCE_ID_MIX / id_sum; + if (ex_sum > 0) { + w += ex_raw[i] * ENHANCE_EX_MIX / ex_sum; + } else if (i == 0) { + w += ENHANCE_EX_MIX; + } + v->hw[i] = (uint16_t)w; + assigned += w; + } + while (n > 1 && v->hw[n - 1] == 0) { + n--; + } + v->n_harm = (uint8_t)n; + if (assigned < ENHANCE_WSUM) { + v->hw[0] = (uint16_t)(v->hw[0] + (uint16_t)(ENHANCE_WSUM - assigned)); + } + if (v->hw[0] == 0) { + v->hw[0] = 1; + } +} + +static void voice_fill_bright(voice_t *v) { + int32_t raw[HARM_MAX]; + int n = 0; + int32_t sum = 0; + for (unsigned k = 1; k <= 7; k += 2) { + if (!harm_ok(v->inc, k)) { + break; + } + while ((int)k > n) { + raw[n] = 0; + n++; + } + raw[n - 1] = (int32_t)(256u / k); + sum += raw[n - 1]; + } + if (n <= 0 || sum <= 0) { + v->n_harm = 1; + v->hw[0] = ENHANCE_WSUM; + return; + } + int32_t assigned = 0; + v->n_harm = (uint8_t)n; + for (int i = 0; i < n; i++) { + int32_t w = raw[i] * ENHANCE_WSUM / sum; + v->hw[i] = (uint16_t)w; + assigned += w; + } + if (assigned < ENHANCE_WSUM) { + v->hw[0] = (uint16_t)(v->hw[0] + (uint16_t)(ENHANCE_WSUM - assigned)); + } + if (v->hw[0] == 0) { + v->hw[0] = 1; + } +} + +static int32_t wave_enhance(const voice_t *v) { + int32_t acc = 0; + unsigned n = v->n_harm; + uint32_t ph = v->phase; + for (unsigned k = 1; k <= n; k++) { + acc += (int32_t)s_sin[(ph * (uint32_t)k) >> 24] * (int32_t)v->hw[k - 1]; + } + return acc / ENHANCE_WSUM; +} + +static int32_t voice_wave(const voice_t *v) { + if (s_timbre == BUZZER_SCORE_TIMBRE_ENHANCE || s_timbre == BUZZER_SCORE_TIMBRE_BRIGHT) { + return wave_enhance(v); + } + return wave_at(v->phase, v->inc, &k_soft); +} + +void buzzer_score_set_timbre(buzzer_score_timbre_t timbre) { + if (timbre > BUZZER_SCORE_TIMBRE_ENHANCE) { + timbre = BUZZER_SCORE_TIMBRE_SOFT; + } + s_timbre = timbre; +} + +buzzer_score_timbre_t buzzer_score_timbre(void) { + return s_timbre; +} + +void buzzer_score_set_staff_mask(uint8_t mask) { + mask &= BUZZER_SCORE_STAFF_BOTH; + s_staff_mask = mask == 0 ? BUZZER_SCORE_STAFF_BOTH : mask; +} + +uint8_t buzzer_score_staff_mask(void) { + return s_staff_mask; +} + +void buzzer_score_set_semitone_shift(int8_t semitones) { + if (semitones > 24) { + semitones = 24; + } else if (semitones < -24) { + semitones = -24; + } + s_shift = semitones; +} + +int8_t buzzer_score_semitone_shift(void) { + return s_shift; +} + +void buzzer_score_set_env(buzzer_score_env_t env) { + s_env = env == BUZZER_SCORE_ENV_HOLD ? BUZZER_SCORE_ENV_HOLD : BUZZER_SCORE_ENV_PIANO; +} + +buzzer_score_env_t buzzer_score_env(void) { + return s_env; +} + +int32_t buzzer_score_raw_peak(void) { + return s_raw_peak; +} + +int32_t buzzer_score_mix_gain_q12(void) { + return s_mix_gain_q; +} + +int32_t buzzer_score_prepare(const buzzer_duty_map_t *map, const volatile bool *abort) { + static int16_t discard[256]; + int32_t target = buzzer_duty_legal_pcm(map); + s_gain_enable = 0; + s_raw_peak = 0; + s_mix_gain_q = 1 << MIX_GAIN_SHIFT; + buzzer_score_reset(NULL); + while (buzzer_score_render(discard, 256, NULL)) { + if (abort != NULL && *abort) { + s_mix_gain_q = 1 << MIX_GAIN_SHIFT; + s_gain_enable = 1; + buzzer_score_reset(NULL); + return 0; + } + } + if (s_raw_peak > 0 && target > 0) { + s_mix_gain_q = (int32_t)(((int64_t)target << MIX_GAIN_SHIFT) / (int64_t)s_raw_peak); + if (s_mix_gain_q < 1) { + s_mix_gain_q = 1; + } + } else { + s_mix_gain_q = 0; + } + s_gain_enable = 1; + buzzer_score_reset(NULL); + return s_mix_gain_q; +} + +void buzzer_score_set_harmonics(const buzzer_score_harmonics_t *h) { + if (h != NULL) { + s_bright = *h; + } +} + +void buzzer_score_get_harmonics(buzzer_score_harmonics_t *h) { + if (h != NULL) { + *h = s_bright; + } +} + +uint32_t buzzer_score_midi_inc(uint8_t midi) { + return s_inc[midi]; +} + +void buzzer_score_render_hold( + int16_t *dst, int n, uint32_t *phase, uint32_t inc, const buzzer_score_harmonics_t *h, + int32_t amp +) { + uint32_t ph = phase != NULL ? *phase : 0; + const buzzer_score_harmonics_t *use = h != NULL ? h : active_harmonics(); + if (amp < 0) { + amp = 0; + } + if (amp > 32767) { + amp = 32767; + } + for (int i = 0; i < n; i++) { + int32_t s = wave_at(ph, inc, use); + dst[i] = (int16_t)((s * amp) >> 15); + ph += inc; + } + if (phase != NULL) { + *phase = ph; + } +} + +uint32_t buzzer_score_pcm_rms(const buzzer_score_stats_t *st) { + if (st == NULL || st->samples == 0) { + return 0; + } + return buzzer_u64_isqrt(st->sum_sq / (uint64_t)st->samples); +} + +static uint32_t rel_samples(void) { + return s_env == BUZZER_SCORE_ENV_HOLD ? (uint32_t)HOLD_REL_N : (uint32_t)REL_N; +} + +static int32_t env_on(uint32_t age) { + if (s_env == BUZZER_SCORE_ENV_HOLD) { + if (age < (uint32_t)HOLD_ATK_N) { + return (int32_t)(age * 32767u / (uint32_t)(HOLD_ATK_N ? HOLD_ATK_N : 1)); + } + uint32_t u = age - (uint32_t)HOLD_ATK_N; + if (u < (uint32_t)HOLD_DEC_N) { + int32_t drop = 32767 - HOLD_SUS; + return 32767 - (int32_t)((uint32_t)drop * u / (uint32_t)HOLD_DEC_N); + } + return HOLD_SUS; + } + if (age < (uint32_t)ATK_N) { + return (int32_t)(age * 32767u / (uint32_t)(ATK_N ? ATK_N : 1)); + } + uint32_t u = age - (uint32_t)ATK_N; + if (u < (uint32_t)DEC_N) { + int32_t drop = 32767 - SUS_ENV; + return 32767 - (int32_t)((uint32_t)drop * u / (uint32_t)DEC_N); + } + return SUS_ENV; +} + +static void voice_start(voice_t *v, const ev_t *ev) { + int midi = (int)ev->midi + (int)s_shift; + if (midi < 0) { + midi = 0; + } else if (midi > 127) { + midi = 127; + } + v->id = ev->id; + v->midi = (uint8_t)midi; + v->vel = ev->vel; + v->stage = ST_ON; + v->phase = 0; + v->inc = s_inc[v->midi]; + v->age = 0; + v->rel_age = 0; + v->env = 0; + v->rel_from = 0; + if (s_timbre == BUZZER_SCORE_TIMBRE_ENHANCE) { + voice_fill_enhance(v); + } else if (s_timbre == BUZZER_SCORE_TIMBRE_BRIGHT) { + voice_fill_bright(v); + } else { + v->n_harm = 0; + } +} + +static void voice_release(voice_t *v) { + if (v->stage == ST_OFF) { + return; + } + v->stage = ST_REL; + v->rel_from = v->env; + v->rel_age = 0; +} + +static voice_t *find_id(uint16_t id) { + for (int i = 0; i < NVOICE; i++) { + if (s_voice[i].stage != ST_OFF && s_voice[i].id == id) { + return &s_voice[i]; + } + } + return NULL; +} + +static voice_t *alloc_voice(void) { + for (int i = 0; i < NVOICE; i++) { + if (s_voice[i].stage == ST_OFF) { + return &s_voice[i]; + } + } + voice_t *best = NULL; + for (int i = 0; i < NVOICE; i++) { + if (s_voice[i].stage != ST_REL) { + continue; + } + if (best == NULL || s_voice[i].env < best->env) { + best = &s_voice[i]; + } + } + if (best != NULL) { + return best; + } + best = &s_voice[0]; + for (int i = 1; i < NVOICE; i++) { + if (s_voice[i].age > best->age) { + best = &s_voice[i]; + } + } + return best; +} + +static void apply_event(const ev_t *ev, buzzer_score_stats_t *st) { + if (!ev->on) { + voice_t *v = find_id(ev->id); + if (v != NULL) { + voice_release(v); + if (st != NULL) { + st->note_offs++; + } + } + return; + } + if (((1u << ev->staff) & s_staff_mask) == 0) { + return; + } + voice_t *v = find_id(ev->id); + if (v == NULL) { + v = alloc_voice(); + } + voice_start(v, ev); + if (st != NULL) { + st->note_ons++; + } +} + +static void apply_due(buzzer_score_stats_t *st) { + while (s_evi < s_nev && s_ev[s_evi].sample == s_pos) { + apply_event(&s_ev[s_evi], st); + s_evi++; + } +} + +static int32_t step_voice(voice_t *v) { + int32_t s = voice_wave(v); + v->phase += v->inc; + if (v->stage == ST_ON) { + v->env = env_on(v->age); + v->age++; + } else { + v->rel_age++; + uint32_t rn = rel_samples(); + if (v->rel_age >= rn || v->rel_from <= 0) { + v->env = 0; + v->stage = ST_OFF; + v->id = 0; + return 0; + } + v->env = (int32_t)((int64_t)v->rel_from * ((int32_t)rn - (int32_t)v->rel_age) / (int32_t)rn); + } + int32_t v16 = (int32_t)(((int64_t)s * v->env) >> 15); + return (v16 * (int32_t)v->vel) >> 8; +} + +bool buzzer_score_render(int16_t *dst, int n, buzzer_score_stats_t *st) { + if (s_done || n <= 0) { + if (n > 0) { + memset(dst, 0, (size_t)n * sizeof(int16_t)); + } + return false; + } + + const uint32_t body = buzzer_score_body_samples(); + for (int i = 0; i < n; i++) { + apply_due(st); + + int32_t mix = 0; + uint32_t live = 0; + for (int v = 0; v < NVOICE; v++) { + if (s_voice[v].stage == ST_OFF) { + continue; + } + mix += step_voice(&s_voice[v]); + if (s_voice[v].stage != ST_OFF) { + live++; + } + } + + int32_t raw_abs = mix < 0 ? -mix : mix; + if (!s_gain_enable && raw_abs > s_raw_peak) { + s_raw_peak = raw_abs; + } + if (s_gain_enable && s_mix_gain_q != (1 << MIX_GAIN_SHIFT)) { + mix = (int32_t)(((int64_t)mix * (int64_t)s_mix_gain_q) >> MIX_GAIN_SHIFT); + } + + if (st != NULL) { + int32_t abs = mix < 0 ? -mix : mix; + if (abs > st->peak) { + st->peak = abs; + } + if (live > st->max_voices) { + st->max_voices = live; + } + st->samples++; + } + if (mix > 32767) { + mix = 32767; + if (st != NULL) { + st->clips++; + } + } else if (mix < -32768) { + mix = -32768; + if (st != NULL) { + st->clips++; + } + } + if (st != NULL) { + st->sum_sq += (uint64_t)((int64_t)mix * (int64_t)mix); + } + dst[i] = (int16_t)mix; + s_pos++; + + if (s_pos >= SAMPLE_CAP) { + s_done = true; + if (i + 1 < n) { + memset(dst + i + 1, 0, (size_t)(n - i - 1) * sizeof(int16_t)); + } + return true; + } + } + + if (s_evi >= s_nev && s_pos >= body) { + bool live = false; + for (int v = 0; v < NVOICE; v++) { + if (s_voice[v].stage != ST_OFF) { + live = true; + break; + } + } + if (!live) { + s_done = true; + } + } + return true; +} diff --git a/firmware/targets/mindreset_read_pico/components/read_pico/idf_component.yml b/firmware/targets/mindreset_read_pico/components/read_pico/idf_component.yml new file mode 100644 index 0000000..a722e71 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/read_pico/idf_component.yml @@ -0,0 +1,12 @@ +version: "1.0.0" +description: "Read Pico board support — I2C, IOE, SY7636A, EPD rails, SD, buzzer, flash HPM" +url: "https://github.com/mindreset/read_pico" +repository: "https://github.com/mindreset/read_pico.git" +issues: "https://github.com/mindreset/read_pico/issues" +license: "Apache-2.0" +tags: + - epd + - eink + - board +dependencies: + idf: ">=5.3" diff --git a/firmware/targets/mindreset_read_pico/components/read_pico/include/buzzer_1bit.h b/firmware/targets/mindreset_read_pico/components/read_pico/include/buzzer_1bit.h new file mode 100644 index 0000000..5a47d41 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/read_pico/include/buzzer_1bit.h @@ -0,0 +1,51 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * 把多路音高排成 1bit 脉冲串:按周期排队,滑移超限就丢。 + * + * Schedule several pitches into a 1-bit pulse train: queue by period, + * drop a pulse if slip exceeds the limit. + */ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define BUZZER_1BIT_VOICES_MAX 6 +#define BUZZER_1BIT_SYM_MAX 96 + +typedef struct { + uint16_t pulse_us; + uint16_t max_slip_us; +} buzzer_1bit_cfg_t; + +typedef struct { + uint16_t high_us; + uint16_t low_us; +} buzzer_1bit_sym_t; + +typedef struct { + uint32_t emitted; + uint32_t dropped; + uint32_t serialized; + uint32_t merged_high; + uint32_t high_us; + uint32_t window_us; +} buzzer_1bit_stats_t; + +#define BUZZER_1BIT_CFG_DEFAULT \ + { .pulse_us = 48, .max_slip_us = 80 } + +int buzzer_1bit_build( + const uint16_t *hz, const uint16_t *weight, int nvoice, const buzzer_1bit_cfg_t *cfg, + uint32_t window_us, buzzer_1bit_sym_t *out, int max_out, buzzer_1bit_stats_t *st +); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/mindreset_read_pico/components/read_pico/include/buzzer_duty_map.h b/firmware/targets/mindreset_read_pico/components/read_pico/include/buzzer_duty_map.h new file mode 100644 index 0000000..a7f67cd --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/read_pico/include/buzzer_duty_map.h @@ -0,0 +1,60 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * 零中心 PCM → 低端开关占空比。不取绝对值、不半波整流。 + * 参数是待测电气工作点,不是安全额定,也不等于硬件限流。 + * + * Zero-centered PCM to low-side switch duty. No abs, no half-wave + * rectify. The parameters are experimental electrical setpoints, not + * safety ratings and not a hardware current limit. + */ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define BUZZER_DUTY_FS 1023 +#define BUZZER_DUTY_GAIN_MAX 32767 + +typedef struct { + uint16_t bias; + uint16_t gain; + uint16_t duty_min; + uint16_t duty_max; +} buzzer_duty_map_t; + +#define BUZZER_DUTY_MAP_EXPERIMENTAL \ + { .bias = 512, .gain = 3600, .duty_min = 0, .duty_max = 1023 } + +int buzzer_duty_map_valid(const buzzer_duty_map_t *m); +int32_t buzzer_duty_legal_pcm(const buzzer_duty_map_t *m); +uint16_t buzzer_duty_from_sample(const buzzer_duty_map_t *m, int16_t pcm); +uint16_t buzzer_duty_from_sample_ex(const buzzer_duty_map_t *m, int16_t pcm, int *clipped); +int16_t buzzer_duty_to_pwm_audio_pcm(uint16_t duty); +void buzzer_duty_map_pcm(const buzzer_duty_map_t *m, const int16_t *in, int16_t *out, int n); + +typedef struct { + uint16_t min; + uint16_t max; + uint32_t n; + uint32_t clips; + uint64_t sum; + uint64_t sum_sq; +} buzzer_duty_accum_t; + +void buzzer_duty_accum_reset(buzzer_duty_accum_t *a); +void buzzer_duty_accum_add(buzzer_duty_accum_t *a, uint16_t duty, int clipped); +uint32_t buzzer_u64_isqrt(uint64_t x); +void buzzer_duty_accum_result( + const buzzer_duty_accum_t *a, uint16_t *min, uint16_t *max, uint32_t *mean_x100, + uint32_t *ac_rms_x100, uint32_t *n, uint32_t *clips +); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/mindreset_read_pico/components/read_pico/include/buzzer_score.h b/firmware/targets/mindreset_read_pico/components/read_pico/include/buzzer_score.h new file mode 100644 index 0000000..bb4b495 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/read_pico/include/buzzer_score.h @@ -0,0 +1,106 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * 无 ESP 依赖的曲谱合成:480 tick/四分、十二平均律、固定增益混音。 + * 主机与固件共用,实时路径不分配内存。 + * + * ESP-free score synthesis: 480 ticks per quarter, 12-TET, fixed-gain + * mix. Shared by host and firmware; the realtime path allocates nothing. + */ + +#pragma once + +#include +#include + +#include "buzzer_duty_map.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define BUZZER_SCORE_SR 32000 +#define BUZZER_SCORE_BPM 135 +#define BUZZER_SCORE_PPQ 480 +#define BUZZER_SCORE_TICKS 7680 +#define BUZZER_SCORE_VOICES 12 +#define BUZZER_SCORE_WT 256 +#define BUZZER_SCORE_BARS 4 +#define BUZZER_SCORE_BAR_TICKS 1920 + +typedef struct { + uint32_t tick; + uint16_t dur; + uint16_t id; + uint8_t midi; + uint8_t vel; + uint8_t staff; +} buzzer_score_note_t; + +typedef struct { + int32_t peak; + uint32_t clips; + uint32_t samples; + uint32_t max_voices; + uint32_t note_ons; + uint32_t note_offs; + uint64_t sum_sq; +} buzzer_score_stats_t; + +typedef enum { + BUZZER_SCORE_TIMBRE_SOFT = 0, + BUZZER_SCORE_TIMBRE_BRIGHT = 1, + BUZZER_SCORE_TIMBRE_ENHANCE = 2, +} buzzer_score_timbre_t; + +typedef enum { + BUZZER_SCORE_ENV_PIANO = 0, + BUZZER_SCORE_ENV_HOLD = 1, +} buzzer_score_env_t; + +#define BUZZER_SCORE_MIX_GAIN_SHIFT 12 + +#define BUZZER_SCORE_STAFF_RH 1u +#define BUZZER_SCORE_STAFF_LH 2u +#define BUZZER_SCORE_STAFF_BOTH 3u + +typedef struct { + uint16_t w1; + uint16_t w2; + uint16_t w3; + uint16_t w5; +} buzzer_score_harmonics_t; + +void buzzer_score_init(void); +void buzzer_score_reset(buzzer_score_stats_t *st); +bool buzzer_score_render(int16_t *dst, int n, buzzer_score_stats_t *st); +bool buzzer_score_idle(void); +void buzzer_score_set_timbre(buzzer_score_timbre_t timbre); +buzzer_score_timbre_t buzzer_score_timbre(void); +void buzzer_score_set_staff_mask(uint8_t mask); +uint8_t buzzer_score_staff_mask(void); +void buzzer_score_set_semitone_shift(int8_t semitones); +int8_t buzzer_score_semitone_shift(void); +void buzzer_score_set_env(buzzer_score_env_t env); +buzzer_score_env_t buzzer_score_env(void); +int32_t buzzer_score_prepare(const buzzer_duty_map_t *map, const volatile bool *abort); +int32_t buzzer_score_raw_peak(void); +int32_t buzzer_score_mix_gain_q12(void); +void buzzer_score_set_harmonics(const buzzer_score_harmonics_t *h); +void buzzer_score_get_harmonics(buzzer_score_harmonics_t *h); +uint32_t buzzer_score_midi_inc(uint8_t midi); +void buzzer_score_render_hold( + int16_t *dst, int n, uint32_t *phase, uint32_t inc, const buzzer_score_harmonics_t *h, + int32_t amp +); +uint32_t buzzer_score_pcm_rms(const buzzer_score_stats_t *st); + +uint32_t buzzer_score_tick_to_sample(uint32_t tick); +uint32_t buzzer_score_body_samples(void); +int buzzer_score_note_count(void); +const buzzer_score_note_t *buzzer_score_notes(void); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/mindreset_read_pico/components/read_pico/include/read_pico.h b/firmware/targets/mindreset_read_pico/components/read_pico/include/read_pico.h new file mode 100644 index 0000000..87b95df --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/read_pico/include/read_pico.h @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * 小纸 Pico 板级 BSP 伞头。 + * + * Board BSP umbrella for Read Pico. + */ + +#pragma once + +#include "read_pico_board.h" +#include "read_pico_buzzer.h" +#include "read_pico_epd_timing.h" +#include "read_pico_init.h" +#include "read_pico_sd.h" diff --git a/firmware/targets/mindreset_read_pico/components/read_pico/include/read_pico_board.h b/firmware/targets/mindreset_read_pico/components/read_pico/include/read_pico_board.h new file mode 100644 index 0000000..029e9e9 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/read_pico/include/read_pico_board.h @@ -0,0 +1,110 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * 小纸 Pico 板级:I2C、IOE、SY7636A、EPD 总线与电源轨。 + * + * Read Pico board: I2C, IO expander, SY7636A, EPD bus and rails. + */ + +#pragma once + +#include +#include + +#include "driver/i2c_master.h" +#include "epd_board.h" +#include "esp_err.h" +#include "fca9555.h" +#include "sy7636a.h" +#include "sdkconfig.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define READ_PICO_I2C_PORT ((i2c_port_t)CONFIG_READ_PICO_I2C_PORT) +#define READ_PICO_I2C_FREQ_HZ CONFIG_READ_PICO_I2C_FREQ_HZ +/// FCA9555 INT#,原理图接到 ESP32-S3 IO41。GPIO41 不是 RTC 脚,只能浅睡唤醒。 +/// / FCA9555 INT# on ESP32-S3 IO41. GPIO41 is not RTC, so light-sleep wake only. +#define READ_PICO_IOE_INT_GPIO CONFIG_READ_PICO_IOE_INT_GPIO +/// CST836U INT,原理图 TXD0 / GPIO43。开漏低有效,FPC 侧有 10k 上拉。 +/// / CST836U INT on TXD0 / GPIO43. Open-drain, active-low, 10k pull-up on the FPC. +#define READ_PICO_TP_INT_GPIO CONFIG_READ_PICO_TP_INT_GPIO + +#define READ_PICO_PRODUCT_NAME "Read Pico" +#define READ_PICO_DEVICE_NAME "小纸 Pico" +/// 芯片平放实测 -5,-119,-1005;转到设备坐标后屏幕朝上约为 +Z。 +/// / Chip at rest on the bench: -5,-119,-1005. Device frame is about +Z face-up. +#define READ_PICO_ACCEL_ZERO_X_MG 119 +#define READ_PICO_ACCEL_ZERO_Y_MG 5 +#define READ_PICO_ACCEL_ZERO_Z_MG 1005 + +extern const EpdBoardDefinition epd_board_read_pico; + +i2c_master_bus_handle_t read_pico_i2c_bus(void); +sy7636a_handle_t read_pico_sy7636a(void); +fca9555_handle_t read_pico_fca9555(void); + +typedef struct { + uint16_t ioe_input; + uint16_t ioe_output; + uint16_t ioe_invert; + uint16_t ioe_config; + int ioe_int_level; + sy7636a_status_t sy; + bool sy_live; + bool rails_on; +} read_pico_status_t; + +#define READ_PICO_I2C_DEV_N 5 + +typedef struct { + const char* name; + uint8_t addr; + bool online; +} read_pico_i2c_dev_t; + +typedef struct { + read_pico_i2c_dev_t dev[READ_PICO_I2C_DEV_N]; + int offline_n; + bool all_online; +} read_pico_i2c_census_t; + +/// 轨已上电后再拍。SY7636A 在 EN 低时不应答。 +/// / Take after the rails are up. SY7636A NACKs while EN is low. +void read_pico_i2c_census_take(void); +const read_pico_i2c_census_t* read_pico_i2c_census(void); + +esp_err_t read_pico_get_status(read_pico_status_t* status); +/// 只读 IN/OUT + INT#,CFG/INV 走缓存。不唤醒 SY7636A。 +/// / Read IN/OUT + INT# only; CFG/INV come from cache. Does not wake SY7636A. +esp_err_t read_pico_get_ioe_status(read_pico_status_t* status); +/// 连读 8 寄存器,刷新 CFG/INV 缓存。底栏「读取」和进页用。 +/// / Sequential 8-register read; refreshes CFG/INV cache. Footer Read and page enter. +esp_err_t read_pico_get_ioe_status_full(read_pico_status_t* status); +int read_pico_ioe_int_level(void); +bool read_pico_sd_present(void); +bool read_pico_rails_on(void); + +/// 翻转 VCOMCTL:1=外部 VCOM_EN,0=自动时序。 +/// / Toggle VCOMCTL: 1=external VCOM_EN, 0=auto timing. +esp_err_t read_pico_toggle_vcomctl(void); + +/// 拉低再放开触摸芯片的 RST(挂在 FCA9555 上)。触摸进了深度睡眠后不再应答 I2C, +/// 只有这条路能把它叫回来。返回后调用方还要等芯片启动完成。 +/// / Pulse the touch RST on the FCA9555. After deep sleep the chip ignores I2C; +/// this is the only wake. Caller must wait for boot after return. +esp_err_t read_pico_touch_reset(void); +/// high=false 拉低复位,high=true 放开。扩展口页要分两帧画脉冲时用。 +/// / high=false asserts RST, high=true releases. IOE page draws the pulse in two frames. +esp_err_t read_pico_tp_rst(bool high); + +/// 读一遍 9555 输入。INT# 不锁存,输入稳定后会自行松开;浅睡前/醒后都要调。 +/// / Read 9555 inputs. INT# is not latched and releases once inputs are stable; +/// call before light sleep and after wake. +void read_pico_clear_ioe_int(void); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/mindreset_read_pico/components/read_pico/include/read_pico_buzzer.h b/firmware/targets/mindreset_read_pico/components/read_pico/include/read_pico_buzzer.h new file mode 100644 index 0000000..3755a57 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/read_pico/include/read_pico_buzzer.h @@ -0,0 +1,83 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * GPIO2 → AO3400A 栅极,高导通低关断。停播必须禁用 PWM 并拉低。 + * 曲谱合成是零中心 PCM;硬件占空比另经 bias/gain/min/max 映射。 + * 高频 PWM 默认不初始化、不上电自测。 + * + * GPIO2 drives the AO3400A gate: high on, low off. Stop must disable PWM + * and drive the pin low. Score synthesis is zero-centered PCM; hardware + * duty is mapped separately via bias/gain/min/max. HF PWM is off by + * default and is not a power-on self-test. + */ + +#pragma once + +#include +#include + +#include "buzzer_duty_map.h" +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + READ_PICO_BUZZER_PATH_CLASSIC = 0, + READ_PICO_BUZZER_PATH_HF = 1, + READ_PICO_BUZZER_PATH_DIRECT_1BIT = 2, +} read_pico_buzzer_path_t; + +typedef enum { + READ_PICO_BUZZER_PROBE_A_15K = 0, + READ_PICO_BUZZER_PROBE_B_15K = 1, + READ_PICO_BUZZER_PROBE_1BIT = 2, + READ_PICO_BUZZER_PROBE_4K = 3, +} read_pico_buzzer_probe_t; + +typedef struct { + uint32_t carrier_hz; + uint8_t resolution_bits; + uint8_t path; + uint16_t duty_min; + uint16_t duty_max; + uint32_t duty_mean_x100; + uint32_t duty_ac_rms_x100; + uint32_t duty_clips; + uint32_t underruns; + uint32_t samples; + int32_t pcm_peak; + uint32_t pcm_rms; + uint8_t current_ma_valid; + int16_t current_ma; + uint32_t tone_hz; + uint32_t tone_hz_actual; + int8_t shift_st; + uint16_t duty_fs; + uint16_t duty_hw_max; + uint8_t residual_low_ticks; + uint8_t voices; +} read_pico_buzzer_out_stats_t; + +esp_err_t read_pico_buzzer_tone(uint32_t frequency_hz, uint32_t duration_ms); + +esp_err_t read_pico_buzzer_score_play(void); +esp_err_t read_pico_buzzer_score_stop(void); +bool read_pico_buzzer_score_busy(void); +esp_err_t read_pico_buzzer_probe(read_pico_buzzer_probe_t probe); + +void read_pico_buzzer_out_stats_get(read_pico_buzzer_out_stats_t *st); + +uint32_t read_pico_buzzer_pwm_carrier_hz(void); +uint32_t read_pico_buzzer_pwm_carrier_hz_nominal(void); + +void read_pico_buzzer_duty_map_get(buzzer_duty_map_t *map); +esp_err_t read_pico_buzzer_duty_map_set(const buzzer_duty_map_t *map); + +esp_err_t read_pico_buzzer_effect(void); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/mindreset_read_pico/components/read_pico/include/read_pico_epd_timing.h b/firmware/targets/mindreset_read_pico/components/read_pico/include/read_pico_epd_timing.h new file mode 100644 index 0000000..eca5b64 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/read_pico/include/read_pico_epd_timing.h @@ -0,0 +1,131 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * 墨水屏扫描时序:按像素钟解出行/帧参数与帧频。 + * + * 一帧要给每条栅极开门一次,开门期间把整行源极数据移进去,所以 + * + * L_DL = H / p (每钟送 p 个像素) + * H_all = L_SL + L_BL + L_DL + L_EL (一行的总钟数) + * T_line = H_all / f (f 单位 MHz,得微秒) + * T_frame= T_line · V_all, V_all = F_SL+F_BL+V+F_EL + * ν = 10^6 / T_frame (帧频 Hz) + * T_refresh = N · T_frame (N 是波形相数) + * + * 帧频只由几何与时钟决定,和 LUT 里的电压码无关:LUT 决定扫几帧,不决定一帧多久。 + * 源极电压只在 L_DL 有效,CKV 必须盖住这段。多出来的行时间堆到 L_EL,不能堆到 + * L_BL。厂家表 LGONL≈13.4µs。消隐下界:T(L_SL)≥0.3µs、T(L_BL)≥0.8µs、L_EL≥4 钟。 + * 这些下界都是时间,换算成钟数就随 f 变,所以每次改像素钟都要重新解一遍。 + * + * EPD scan timing: solve line/frame parameters and frame rate from the pixel clock. + * + * Each frame opens every gate once and shifts a full source line while it is + * open, so + * + * L_DL = H / p (p pixels per clock) + * H_all = L_SL + L_BL + L_DL + L_EL (clocks per line) + * T_line = H_all / f (f in MHz, result in µs) + * T_frame= T_line · V_all, V_all = F_SL+F_BL+V+F_EL + * ν = 10^6 / T_frame (frame rate in Hz) + * T_refresh = N · T_frame (N is waveform phases) + * + * Frame rate is geometry and clock only; LUT voltage codes pick how many + * frames, not how long one frame lasts. Source voltages are valid only + * during L_DL, so CKV must cover that window. Extra line time goes to + * L_EL, never L_BL. Vendor LGONL≈13.4µs. Blanking floors: T(L_SL)≥0.3µs, + * T(L_BL)≥0.8µs, L_EL≥4 clocks. Those floors are times, so clock counts + * change with f and must be re-solved whenever the pixel clock changes. + */ + +#pragma once + +#include "epd_lcd.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/// 板级支持的像素钟区间。上限受 DMA 供数能力限制,不是面板规格限制。 +/// / Board pixel-clock range. The cap is DMA feed, not a panel spec. +#define READ_PICO_EPD_PCLK_MIN_MHZ 12 +#define READ_PICO_EPD_PCLK_MAX_MHZ 24 + +/// 扫描档位。灰阶刷新按原厂算法文档的设计点(ν≈90Hz)配帧周期;自制的跟随 DU 与 +/// 连续 DU 是按最短行标定的,跟着拉长只会白白降帧率。实测在 7~18ms 之间改帧周期对 +/// GC16 的成像看不出差别,所以这里只是各自守住自己的标定点,不是靠它调驱动量。 +/// 切换只是几个寄存器加一次 RMT 重建,放在两次刷新之间做。 +/// / Scan profile. Gray refresh uses the vendor design point (ν≈90 Hz). +/// Follow and continuous DU are calibrated at the shortest line; stretching +/// that only drops frame rate. Bench GC16 looks the same from 7–18 ms, so +/// each profile keeps its own setpoint rather than using it as drive. +/// Switching is a few registers plus one RMT rebuild, between refreshes. +typedef enum { + READ_PICO_EPD_SCAN_FULL = 0, ///< 行周期补到波形标定的帧周期 / Pad the line to the waveform frame period + READ_PICO_EPD_SCAN_FAST, ///< 行周期取规格允许的最短值 / Shortest legal line period +} read_pico_epd_scan_profile_t; + +typedef struct { + int pclk_mhz; ///< f + int px_per_clk; ///< p,由总线位宽与 2bit/像素得出 / p from bus width and 2 bits/pixel + int line_start; ///< L_SL,喂 le_high_time / L_SL → le_high_time + int line_back_porch; ///< L_BL,喂 line_front_porch / L_BL → line_front_porch + int line_data; ///< L_DL = H / p + int line_end; ///< L_EL,凑行长的余量,必须写进 LCD / L_EL slack; must be written to the LCD + int line_clocks; ///< H_all + int line_us; ///< T_line + int frame_lines; ///< V_all + int frame_us; ///< T_frame + int frame_hz_x100; ///< ν×100,避免为了两位小数引入浮点 / ν×100, no float for two decimals + int ckv_high_01us; ///< T(L_GON),单位 0.1µs / T(L_GON) in 0.1 µs +} read_pico_epd_scan_t; + +/// 按当前面板几何、像素钟与档位解一套扫描参数。pclk 会先夹到板级区间里。 +/// / Solve scan params from geometry, pixel clock, and profile. pclk is clamped first. +void read_pico_epd_scan( + int pclk_mhz, read_pico_epd_scan_profile_t profile, read_pico_epd_scan_t* out +); + +/// N 相波形的墙钟时间 T_refresh = N · T_frame,单位毫秒。 +/// / Wall time for an N-phase waveform: T_refresh = N · T_frame, in ms. +int read_pico_epd_refresh_ms(int pclk_mhz, read_pico_epd_scan_profile_t profile, int phases); + +/// 改像素钟:重解行消隐与 CKV 宽度再切频率,只调 epd_set_lcd_pixel_clock_MHz +/// 会让消隐停在上一档的钟数上,高频下 CKV 就顶满整行了。 +/// / Change the pixel clock: re-solve blanking and CKV, then switch frequency. +/// Calling epd_set_lcd_pixel_clock_MHz alone leaves the previous clock counts, +/// so CKV fills the whole line at high f. +void read_pico_epd_set_pclk(int pclk_mhz); + +/// 当前像素钟。/ Current pixel clock. +int read_pico_epd_pclk_mhz(void); + +/// FULL 档要凑到的帧周期(µs)。每相的驱动量正比于它,灰阶梯子的展开程度也就由它 +/// 决定:太短压不黑,太长单次推动就饱和、梯子塞到白端。默认取波形头里的标定值, +/// 连调时可以在线改。下界受最短行限制(约 700 行 × 最短行长)。 +/// / FULL-profile target frame period (µs). Drive per phase scales with this, +/// and so does the gray ladder: too short never goes black; too long saturates +/// in one push and packs the ladder toward white. Default is the waveform +/// header setpoint; live-tunable. Floor is the shortest line (~700 × min line). +int read_pico_epd_frame_target_us(void); +void read_pico_epd_set_frame_target_us(int us); + +/// CKV 高电平上限(0.1µs)。厂家表 LGONL≈13.4µs,默认 134。 +/// / CKV high cap (0.1 µs). Vendor LGONL≈13.4 µs; default 134. +int read_pico_epd_ckv_high_max_01us(void); +void read_pico_epd_set_ckv_high_max_01us(int v); + +/// 切扫描档位,同档重复调用是空操作。必须在两次刷新之间调。 +/// / Switch scan profile; same-profile calls are no-ops. Between refreshes only. +void read_pico_epd_use_scan(read_pico_epd_scan_profile_t profile); + +/// 开机:把解出的扫描参数灌进 LCD 并初始化外设。 +/// / Boot: push the solved scan into the LCD and init the peripheral. +void read_pico_epd_init_lcd( + const lcd_bus_config_t* bus, int bus_width, int width, int height, + const read_pico_epd_scan_t* scan +); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/mindreset_read_pico/components/read_pico/include/read_pico_init.h b/firmware/targets/mindreset_read_pico/components/read_pico/include/read_pico_init.h new file mode 100644 index 0000000..5e03117 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/read_pico/include/read_pico_init.h @@ -0,0 +1,58 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * 小纸 Pico 板级 bring-up:墨水屏、SD、加速度计、PMU、触摸。 + * + * Read Pico board bring-up: EPD, SD, accelerometer, PMU, touch. + */ + +#pragma once + +#include +#include + +#include "cst836u.h" +#include "epd_highlevel.h" +#include "esp_err.h" +#include "sc7a20h.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + EpdiyHighlevelState hl; + uint8_t* framebuffer; + sc7a20h_handle_t sensor; + cst836u_handle_t touch; + bool sensor_ready; + bool touch_ready; + bool pmu_ready; + uint8_t sensor_whoami; + uint8_t sensor_version; +} read_pico_handle_t; + +/// 墨水屏、SD 探测、加速度计(校验后掉电)、PMU、触摸。 +/// 外设失败只清对应 ready;墨水屏起不来才返回错误。 +/// / EPD, SD probe, accelerometer (power-down after check), PMU, touch. +/// Peripheral failures clear that ready flag; only EPD failure is fatal. +esp_err_t read_pico_init(read_pico_handle_t* handle); +void read_pico_deinit(read_pico_handle_t* handle); + +/// 加速度计只在 AXIS 页采样,离开后关掉 ODR。 +/// / Sample the accelerometer only on the AXIS page; drop ODR on leave. +void read_pico_sensor_wake(sc7a20h_handle_t h); +void read_pico_sensor_sleep(sc7a20h_handle_t h); + +/// 芯片坐标 → 设备坐标。平放屏幕朝上为 +Z;立在 X 正边为 +X。 +/// Xd=-Yc,Yd=-Xc,Zd=-Zc。 +/// / Chip frame to device frame. Face-up is +Z; standing on +X is +X. +/// Xd=-Yc, Yd=-Xc, Zd=-Zc. +void read_pico_accel_to_device(sc7a20h_sample_t* sample); +uint8_t read_pico_accel_map_aoi(uint8_t src); +esp_err_t read_pico_accel_read(sc7a20h_handle_t h, sc7a20h_sample_t* sample); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/mindreset_read_pico/components/read_pico/include/read_pico_sd.h b/firmware/targets/mindreset_read_pico/components/read_pico/include/read_pico_sd.h new file mode 100644 index 0000000..bcd9cb3 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/read_pico/include/read_pico_sd.h @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * 小纸 Pico SDMMC 探测、挂载、格式化。 + * + * Read Pico SDMMC probe, mount, and format. + */ + +#pragma once + +#include +#include + +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + bool present; + bool mounted; + bool needs_format; + char name[8]; + uint64_t capacity_bytes; + uint64_t free_bytes; + esp_err_t error; +} read_pico_sd_info_t; + +esp_err_t read_pico_sd_start_probe(void); +/// 查询实时 CD 并锁存失效,不卸载卡;拔卡后容量清零,重新插入也须显式 remount。 +/// / Sample live CD and latch invalidation without unmounting; removal clears capacity and reinsertion requires explicit remount. +esp_err_t read_pico_sd_get_info(read_pico_sd_info_t* info); +/// 调用前关闭所有字体、阅读及上传句柄;这是拔卡失效后恢复挂载的唯一入口。 +/// / Close all font, reader and upload handles first; this is the only recovery entry after removal invalidation. +esp_err_t read_pico_sd_remount(void); +/// 仅显式用户确认后调用;失效挂载必须先 remount,绝不自动建议格式化拔出的卡。 +/// / Explicit user confirmation only; an invalidated mount needs remount first, never a removal-triggered format suggestion. +esp_err_t read_pico_sd_format(void); +/// 已挂载则卸载落盘。探测未完成返回 ESP_ERR_NOT_FINISHED。 +/// / Unmount if mounted so writes land. ESP_ERR_NOT_FINISHED while probing. +esp_err_t read_pico_sd_sync(void); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/mindreset_read_pico/components/read_pico/read_pico_blockchain.ots b/firmware/targets/mindreset_read_pico/components/read_pico/read_pico_blockchain.ots new file mode 100644 index 0000000..efdd0ff Binary files /dev/null and b/firmware/targets/mindreset_read_pico/components/read_pico/read_pico_blockchain.ots differ diff --git a/firmware/targets/mindreset_read_pico/components/read_pico/read_pico_board.c b/firmware/targets/mindreset_read_pico/components/read_pico/read_pico_board.c new file mode 100644 index 0000000..0fb992f --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/read_pico/read_pico_board.c @@ -0,0 +1,696 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * 板级 I2C、FCA9555、SY7636A 与 EPD 电源轨。 + * + * Board I2C, FCA9555, SY7636A, and EPD rails. + */ + +#include +#include +#include +#include + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "epd_board.h" +#include "epd_lcd.h" +#include "epdiy.h" +#include "esp_idf_version.h" +#include "esp_log.h" +#include "esp_rom_sys.h" +#include "esp_timer.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "fca9555.h" +#include "read_pico_board.h" +#include "read_pico_epd_timing.h" +#include "sdkconfig.h" +#include "sy7636a.h" + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) +// 避开 hal/lcd_periph.h(私有 esp_hal_i2s 依赖);S3 上 DE == LCD_H_ENABLE。 +// / Skip hal/lcd_periph.h (private esp_hal_i2s dep); DE == LCD_H_ENABLE on S3. +#include +#define BOARD_LCD_DE_SIG LCD_H_ENABLE_IDX +#elif ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 3, 2) +#include +#define BOARD_LCD_DE_SIG lcd_periph_signals.panels[0].de_sig +#else +#include +#define BOARD_LCD_DE_SIG lcd_periph_rgb_signals.panels[0].de_sig +#endif + +#define TAG "read_pico" + +#define EPD_I2C_SCL ((gpio_num_t)CONFIG_READ_PICO_I2C_SCL_GPIO) +#define EPD_I2C_SDA ((gpio_num_t)CONFIG_READ_PICO_I2C_SDA_GPIO) + +#define IOE_MODE (1U << 0) +#define IOE_XOE (1U << 1) +#define IOE_CW_INT (1U << 2) +#define IOE_SY_EN (1U << 3) +#define IOE_SY_VCOM_EN (1U << 4) +#define IOE_SY_PGOOD (1U << 5) +#define IOE_SD_CD (1U << 6) +#define IOE_TP_RST (1U << 7) + +// P0.2 CW_INT / P0.5 PGOOD / P0.6 SD_CD 为输入,其余为输出。 +// / P0.2 CW_INT / P0.5 PGOOD / P0.6 SD_CD are inputs; the rest are outputs. +#define IOE_CONFIG_PORT0 0x64 +#define IOE_REG_COUNT 8 + +#define D0 GPIO_NUM_4 +#define D1 GPIO_NUM_5 +#define D2 GPIO_NUM_6 +#define D3 GPIO_NUM_7 +#define D4 GPIO_NUM_8 +#define D5 GPIO_NUM_9 +#define D6 GPIO_NUM_10 +#define D7 GPIO_NUM_11 +#define D8 GPIO_NUM_12 +#define D9 GPIO_NUM_13 +#define D10 GPIO_NUM_14 +#define D11 GPIO_NUM_15 +#define D12 GPIO_NUM_16 +#define D13 GPIO_NUM_17 +#define D14 GPIO_NUM_18 +#define D15 GPIO_NUM_45 + +#define EPD_XLE GPIO_NUM_3 +#define EPD_XSTL GPIO_NUM_46 +#define EPD_XCL GPIO_NUM_21 +#define EPD_SPV GPIO_NUM_47 +#define EPD_CKV GPIO_NUM_48 + +static uint8_t ioe_output; +static bool rails_on; +static i2c_master_bus_handle_t s_i2c_bus; +static sy7636a_handle_t s_sy; +static fca9555_handle_t s_ioe; + +static esp_err_t ioe_set(uint8_t mask, bool high); + +i2c_master_bus_handle_t read_pico_i2c_bus(void) { + return s_i2c_bus; +} + +static void read_pico_i2c_deinit(void) { + if (s_sy) { + sy7636a_deinit(s_sy); + s_sy = NULL; + } + if (s_ioe) { + fca9555_deinit(s_ioe); + s_ioe = NULL; + } + if (s_i2c_bus != NULL) { + i2c_del_master_bus(s_i2c_bus); + s_i2c_bus = NULL; + } +} + +static lcd_bus_config_t lcd_config = { + .clock = EPD_XCL, + .ckv = EPD_CKV, + .leh = EPD_XLE, + .start_pulse = EPD_XSTL, + .stv = EPD_SPV, + .data = { + D0, D1, D2, D3, D4, D5, D6, D7, + D8, D9, D10, D11, D12, D13, D14, D15, + }, +}; + +static const char* i2c_dev_name(uint8_t addr) { + switch (addr) { + case 0x15: return "CST836U"; + case 0x19: return "SC7A20H"; + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: return "FCA9555?"; + case 0x2A: return "CW32"; + case 0x62: return "SY7636A"; + default: return NULL; + } +} + +static esp_err_t i2c_probe(uint8_t addr) { + return i2c_master_probe(s_i2c_bus, addr, 50); +} + +// USB/软件复位不会给从机掉电。CST836U / CW32 若卡在应答中间,会把 SDA 拉死, +// 新 I2C 驱动的 probe 就会对每个地址都 TIMEOUT(不是 NACK)。 +// / USB/soft reset does not power-cycle slaves. A stuck CST836U / CW32 holds +// SDA; the new I2C probe then TIMEs OUT on every address (not NACK). +static void i2c_bus_recover(void) { + gpio_hold_dis(EPD_I2C_SDA); + gpio_hold_dis(EPD_I2C_SCL); + gpio_sleep_sel_dis(EPD_I2C_SDA); + gpio_sleep_sel_dis(EPD_I2C_SCL); + + gpio_config_t io = { + .pin_bit_mask = (1ULL << EPD_I2C_SDA) | (1ULL << EPD_I2C_SCL), + .mode = GPIO_MODE_INPUT_OUTPUT_OD, + .pull_up_en = GPIO_PULLUP_ENABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + gpio_config(&io); + gpio_set_level(EPD_I2C_SDA, 1); + gpio_set_level(EPD_I2C_SCL, 1); + esp_rom_delay_us(20); + + int sda = gpio_get_level(EPD_I2C_SDA); + int scl = gpio_get_level(EPD_I2C_SCL); + if (sda && scl) { + ESP_LOGI(TAG, "I2C lines idle SDA=%d SCL=%d", sda, scl); + } else { + ESP_LOGW(TAG, "I2C bus stuck SDA=%d SCL=%d, clocking recover", sda, scl); + } + + for (int i = 0; i < 9; ++i) { + if (gpio_get_level(EPD_I2C_SDA)) break; + gpio_set_level(EPD_I2C_SCL, 0); + esp_rom_delay_us(5); + gpio_set_level(EPD_I2C_SCL, 1); + esp_rom_delay_us(5); + } + + gpio_set_level(EPD_I2C_SDA, 0); + esp_rom_delay_us(5); + gpio_set_level(EPD_I2C_SCL, 1); + esp_rom_delay_us(5); + gpio_set_level(EPD_I2C_SDA, 1); + esp_rom_delay_us(5); + + sda = gpio_get_level(EPD_I2C_SDA); + scl = gpio_get_level(EPD_I2C_SCL); + if (!sda || !scl) { + ESP_LOGE(TAG, "I2C recover failed SDA=%d SCL=%d (need power cycle)", sda, scl); + } +} + +static void i2c_scan_bus(void) { + int found = 0; + ESP_LOGI(TAG, "I2C scan SCL=%d SDA=%d 400kHz", EPD_I2C_SCL, EPD_I2C_SDA); + for (uint8_t addr = 0x08; addr < 0x78; ++addr) { + esp_err_t err = i2c_probe(addr); + if (err == ESP_ERR_TIMEOUT) { + ESP_LOGW(TAG, "I2C hung at 0x%02X, reset bus", addr); + i2c_master_bus_reset(s_i2c_bus); + err = i2c_probe(addr); + if (err == ESP_ERR_TIMEOUT) { + ESP_LOGE(TAG, "I2C still hung, abort scan"); + break; + } + } + if (err != ESP_OK) continue; + const char* name = i2c_dev_name(addr); + if (name != NULL) { + ESP_LOGI(TAG, " ACK 0x%02X %s", addr, name); + } else { + ESP_LOGI(TAG, " ACK 0x%02X", addr); + } + found++; + } + if (found == 0) { + ESP_LOGE(TAG, "I2C scan: no devices (SDA/SCL wiring or pull-up?)"); + } else { + ESP_LOGI(TAG, "I2C scan: %d device(s)", found); + } +} + +static esp_err_t fca_expect_reg(uint8_t reg, uint8_t expected, const char* name) { + uint8_t actual = 0; + esp_err_t err = fca9555_read_reg(s_ioe, reg, &actual); + if (err != ESP_OK) { + ESP_LOGE(TAG, "FCA9555 %s read 0x%02X failed: %s", name, reg, esp_err_to_name(err)); + return err; + } + if (actual != expected) { + ESP_LOGE(TAG, "FCA9555 %s mismatch: wrote 0x%02X, read 0x%02X", name, expected, actual); + return ESP_ERR_INVALID_RESPONSE; + } + ESP_LOGI(TAG, "FCA9555 %s OK: 0x%02X", name, actual); + return ESP_OK; +} + +// 上电自检:ACK、8 寄存器可读、CFG/OUT 回读。不拉 SY_EN / VCOM,避免误开高压。 +// / Power-on check: ACK, 8 readable regs, CFG/OUT readback. Leave SY_EN / VCOM +// low so HV does not come up by accident. +static esp_err_t fca9555_selftest(void) { + static const char* const reg_names[IOE_REG_COUNT] = { + "IN0", "IN1", "OUT0", "OUT1", "INV0", "INV1", "CFG0", "CFG1", + }; + uint8_t regs[IOE_REG_COUNT] = { 0 }; + + ESP_LOGI(TAG, "FCA9555 self-test start, addr=0x%02X (A2=1 A1=0 A0=0)", FCA9555_ADDR_DEFAULT); + i2c_scan_bus(); + + esp_err_t err = i2c_probe(FCA9555_ADDR_DEFAULT); + if (err != ESP_OK) { + ESP_LOGE(TAG, "FCA9555 NACK at 0x%02X: %s", FCA9555_ADDR_DEFAULT, esp_err_to_name(err)); + for (uint8_t addr = 0x20; addr <= 0x27; ++addr) { + if (addr == FCA9555_ADDR_DEFAULT) continue; + if (i2c_probe(addr) == ESP_OK) { + ESP_LOGW(TAG, "FCA9555-like ACK at 0x%02X (A2A1A0=%d%d%d)", + addr, (addr >> 2) & 1, (addr >> 1) & 1, addr & 1); + } + } + return err; + } + ESP_LOGI(TAG, "FCA9555 ACK OK"); + + for (uint8_t i = 0; i < IOE_REG_COUNT; ++i) { + err = fca9555_read_reg(s_ioe, i, ®s[i]); + if (err != ESP_OK) { + ESP_LOGE(TAG, "FCA9555 %s read failed: %s", reg_names[i], esp_err_to_name(err)); + return err; + } + } + ESP_LOGI( + TAG, + "FCA9555 regs IN=%02X%02X OUT=%02X%02X INV=%02X%02X CFG=%02X%02X", + regs[0], regs[1], regs[2], regs[3], regs[4], regs[5], regs[6], regs[7] + ); + + err = fca9555_set_config(s_ioe, 0, IOE_CONFIG_PORT0); + if (err != ESP_OK) { + ESP_LOGE(TAG, "FCA9555 CFG0 write failed: %s", esp_err_to_name(err)); + return err; + } + err = fca_expect_reg(6, IOE_CONFIG_PORT0, "CFG0"); + if (err != ESP_OK) return err; + + err = fca9555_set_config(s_ioe, 1, 0xFF); + if (err != ESP_OK) { + ESP_LOGE(TAG, "FCA9555 CFG1 write failed: %s", esp_err_to_name(err)); + return err; + } + err = fca_expect_reg(7, 0xFF, "CFG1"); + if (err != ESP_OK) return err; + + // 只翻转 MODE / TP_RST,SY_EN 和 VCOM 保持低。 + // / Toggle MODE / TP_RST only; keep SY_EN and VCOM low. + const uint8_t out_a = IOE_MODE | IOE_TP_RST; + const uint8_t out_b = IOE_MODE; + err = fca9555_set_output(s_ioe, 0, out_a); + if (err != ESP_OK) return err; + err = fca_expect_reg(2, out_a, "OUT0"); + if (err != ESP_OK) return err; + err = fca9555_set_output(s_ioe, 0, out_b); + if (err != ESP_OK) return err; + err = fca_expect_reg(2, out_b, "OUT0 toggle"); + if (err != ESP_OK) return err; + err = fca9555_set_output(s_ioe, 0, out_a); + if (err != ESP_OK) return err; + + uint8_t in0 = 0; + err = fca9555_read_reg(s_ioe, 0, &in0); + if (err != ESP_OK) return err; + ESP_LOGI( + TAG, + "FCA9555 IN0=0x%02X CW_INT=%d PGOOD=%d SD_CD=%s", + in0, + (in0 & IOE_CW_INT) ? 1 : 0, + (in0 & IOE_SY_PGOOD) ? 1 : 0, + (in0 & IOE_SD_CD) ? "absent" : "present" + ); + ESP_LOGI(TAG, "FCA9555 self-test PASS"); + return ESP_OK; +} + +static esp_err_t ioe_commit(void) { + return fca9555_set_output(s_ioe, 0, ioe_output); +} + +static esp_err_t ioe_set(uint8_t mask, bool high) { + if (high) { + ioe_output |= mask; + } else { + ioe_output &= (uint8_t)~mask; + } + return ioe_commit(); +} + +static esp_err_t board_sy_en(bool on) { + return ioe_set(IOE_SY_EN, on); +} + +static esp_err_t board_sy_vcom_en(bool on) { + return ioe_set(IOE_SY_VCOM_EN, on); +} + +static int board_sy_pgood(void) { + uint8_t in0 = 0; + if (s_ioe == NULL || fca9555_read_input(s_ioe, 0, &in0) != ESP_OK) return 0; + return (in0 & IOE_SY_PGOOD) ? 1 : 0; +} + +static void board_set_ctrl(epd_ctrl_state_t* state, const epd_ctrl_state_t* const mask) { + bool changed = false; + if (mask->ep_mode) { + if (state->ep_mode) ioe_output |= IOE_MODE; + else ioe_output &= (uint8_t)~IOE_MODE; + changed = true; + } + if (mask->ep_output_enable) { + if (state->ep_output_enable) ioe_output |= IOE_XOE; + else ioe_output &= (uint8_t)~IOE_XOE; + changed = true; + } + if (changed) { + // I2C 挂了不能 abort,否则自检中断后刷屏会把整机打回主界面。 + // / Do not abort on a dead I2C; a self-test interrupt mid-refresh would + // bounce the device back to home. + esp_err_t err = ioe_commit(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "ioe_commit %s", esp_err_to_name(err)); + } + } +} + +static void board_init(uint32_t epd_row_width) { + i2c_bus_recover(); + + i2c_master_bus_config_t bus_cfg = { + .i2c_port = READ_PICO_I2C_PORT, + .sda_io_num = EPD_I2C_SDA, + .scl_io_num = EPD_I2C_SCL, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .flags.enable_internal_pullup = true, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&bus_cfg, &s_i2c_bus)); + + fca9555_config_t ioe_config = FCA9555_CONFIG_DEFAULT(); + ioe_config.i2c_addr = FCA9555_ADDR_DEFAULT; + ioe_config.int_gpio = (gpio_num_t)READ_PICO_IOE_INT_GPIO; + ESP_ERROR_CHECK(fca9555_init(s_i2c_bus, &ioe_config, &s_ioe)); + + esp_err_t fca_err = fca9555_selftest(); + if (fca_err == ESP_ERR_TIMEOUT) { + ESP_LOGW(TAG, "FCA9555 self-test timeout, reset bus and retry"); + i2c_master_bus_reset(s_i2c_bus); + fca_err = fca9555_selftest(); + } + if (fca_err != ESP_OK) { + ESP_LOGE(TAG, "FCA9555 self-test FAILED: %s, stop before EPD init", esp_err_to_name(fca_err)); + } + ESP_ERROR_CHECK(fca_err); + + // P0.7 作为 CST836U 的低有效复位输出。 + // / P0.7 is the CST836U active-low reset output. + ESP_ERROR_CHECK(fca9555_set_config(s_ioe, 0, IOE_CONFIG_PORT0)); + ioe_output = IOE_MODE | IOE_TP_RST; + ESP_ERROR_CHECK(ioe_commit()); + + ESP_ERROR_CHECK(ioe_set(IOE_TP_RST, false)); + vTaskDelay(pdMS_TO_TICKS(10)); + ESP_ERROR_CHECK(ioe_set(IOE_TP_RST, true)); + vTaskDelay(pdMS_TO_TICKS(50)); + + sy7636a_config_t sy = SY7636A_CONFIG_DEFAULT(); + sy.power.vcom_mv = 1290; + sy.en_fn = board_sy_en; + sy.vcom_en_fn = board_sy_vcom_en; + sy.pgood_fn = board_sy_pgood; + ESP_ERROR_CHECK(sy7636a_init(s_i2c_bus, &sy, &s_sy)); + + const EpdDisplay_t* display = epd_get_display(); + const int init_pclk_mhz = display->bus_speed * 8 / display->bus_width; + read_pico_epd_scan_t scan; + read_pico_epd_scan(init_pclk_mhz, READ_PICO_EPD_SCAN_FULL, &scan); + read_pico_epd_init_lcd(&lcd_config, display->bus_width, display->width, display->height, &scan); + ESP_LOGI(TAG, "EPD bus initialized, SY7636A address 0x%02X", SY7636A_ADDR_DEFAULT); +} + +static void board_poweron(epd_ctrl_state_t* state) { + if (rails_on) return; + + esp_rom_gpio_connect_out_signal(EPD_XSTL, BOARD_LCD_DE_SIG, false, false); + + state->ep_output_enable = false; + state->ep_mode = true; + epd_ctrl_state_t ctrl_mask = { + .ep_output_enable = true, + .ep_mode = true, + }; + board_set_ctrl(state, &ctrl_mask); + + if (sy7636a_power_on(s_sy) != ESP_OK) return; + + state->ep_output_enable = true; + board_set_ctrl(state, &ctrl_mask); + rails_on = true; + + sy7636a_status_t st; + if (sy7636a_read(s_sy, &st) == ESP_OK) { + ESP_LOGI( + TAG, "SY7636A rails ready, VCOM=-%dmV, temperature=%dC", + st.vcom_mv, st.temperature_c + ); + } +} + +static void board_poweroff(epd_ctrl_state_t* state) { + state->ep_output_enable = false; + state->ep_mode = true; + epd_ctrl_state_t ctrl_mask = { + .ep_output_enable = true, + .ep_mode = true, + }; + board_set_ctrl(state, &ctrl_mask); + + vTaskDelay(pdMS_TO_TICKS(1)); + (void)sy7636a_power_off(s_sy); + rails_on = false; +} + +static void board_deinit(void) { + if (rails_on) { + board_poweroff(epd_ctrl_state()); + } + epd_lcd_deinit(); + read_pico_i2c_deinit(); +} + +static void board_measure_vcom(epd_ctrl_state_t* state) { + board_poweron(state); +} + +static float board_temperature(void) { + if (!rails_on || s_sy == NULL) return 25.0f; + sy7636a_status_t st; + if (sy7636a_read(s_sy, &st) != ESP_OK) return 25.0f; + return (float)st.temperature_c; +} + +static void board_set_vcom(int value) { + if (s_sy) (void)sy7636a_set_vcom(s_sy, value); +} + +static void status_from_cfg(sy7636a_status_t* st, const sy7636a_power_config_t* cfg, bool on) { + st->on = on; + st->vcom_manual = cfg->vcom_manual; + st->discharge = (uint8_t)(cfg->discharge & SY7636A_DISCHG_MASK); + st->vcom_mv = cfg->vcom_mv; + st->vldo = cfg->vldo; + memcpy(st->dly_ms, cfg->dly_ms, sizeof(st->dly_ms)); +} + +sy7636a_handle_t read_pico_sy7636a(void) { + return s_sy; +} + +fca9555_handle_t read_pico_fca9555(void) { + return s_ioe; +} + +static read_pico_i2c_census_t s_census; + +static const struct { + uint8_t addr; + const char* name; +} s_census_map[READ_PICO_I2C_DEV_N] = { + { 0x15, "CST836U" }, + { 0x19, "SC7A20H" }, + { FCA9555_ADDR_DEFAULT, "FCA9555" }, + { 0x2A, "CW32" }, + { SY7636A_ADDR_DEFAULT, "SY7636A" }, +}; + +void read_pico_i2c_census_take(void) { + s_census = (read_pico_i2c_census_t){ 0 }; + s_census.all_online = true; + for (int i = 0; i < READ_PICO_I2C_DEV_N; i++) { + s_census.dev[i].name = s_census_map[i].name; + s_census.dev[i].addr = s_census_map[i].addr; + if (s_i2c_bus == NULL) { + s_census.dev[i].online = false; + } else { + esp_err_t err = i2c_probe(s_census_map[i].addr); + if (err == ESP_ERR_TIMEOUT) { + i2c_master_bus_reset(s_i2c_bus); + err = i2c_probe(s_census_map[i].addr); + } + s_census.dev[i].online = (err == ESP_OK); + } + if (s_census.dev[i].online) { + ESP_LOGI(TAG, "I2C online 0x%02X %s", s_census.dev[i].addr, s_census.dev[i].name); + } else { + s_census.offline_n++; + s_census.all_online = false; + ESP_LOGW(TAG, "I2C offline 0x%02X %s", s_census.dev[i].addr, s_census.dev[i].name); + } + } +} + +const read_pico_i2c_census_t* read_pico_i2c_census(void) { + return &s_census; +} + +int read_pico_ioe_int_level(void) { + return fca9555_int_level(s_ioe); +} + +static void status_from_map(read_pico_status_t* status, const fca9555_map_t* map) { + status->ioe_input = map->input; + status->ioe_output = map->output; + status->ioe_invert = map->invert; + status->ioe_config = map->config; +} + +// refresh_cfg:连读 8 寄存器;否则只读 IN/OUT,CFG/INV 用缓存。失败不再补读。 +// / refresh_cfg: sequential 8-reg read; else IN/OUT only, CFG/INV from cache. +// No fallback read on failure. +static esp_err_t fill_ioe_status(read_pico_status_t* status, bool refresh_cfg) { + fca9555_map_t map = { 0 }; + esp_err_t err = ESP_ERR_INVALID_STATE; + if (s_ioe) { + err = (refresh_cfg || !fca9555_dir_cached(s_ioe)) + ? fca9555_read_all(s_ioe, &map) + : fca9555_read_io(s_ioe, &map); + } + + if (err != ESP_OK) { + map.input = 0; + map.output = (uint16_t)ioe_output | 0xFF00; + if (fca9555_dir_cached(s_ioe)) { + fca9555_get_dir_cache(s_ioe, &map.invert, &map.config); + } + } + status_from_map(status, &map); + status->ioe_int_level = read_pico_ioe_int_level(); + status->rails_on = rails_on; + return err; +} + +esp_err_t read_pico_get_status(read_pico_status_t* status) { + if (status == NULL) return ESP_ERR_INVALID_ARG; + + // SY 页只要 PGOOD(IN0 bit5),不要连读 8 寄存器。 + // / The SY page only needs PGOOD (IN0 bit5); do not sequential-read 8 regs. + uint8_t in0 = 0; + if (s_ioe) (void)fca9555_read_reg(s_ioe, 0, &in0); + status->ioe_input = in0; + status->ioe_output = (uint16_t)ioe_output | 0xFF00; + status->ioe_invert = 0; + status->ioe_config = 0; + status->ioe_int_level = 1; + status->rails_on = rails_on; + status->sy_live = false; + memset(&status->sy, 0, sizeof(status->sy)); + if (s_sy) status->sy.vcom_mv = sy7636a_get_vcom(s_sy); + + if (s_sy == NULL) return ESP_ERR_INVALID_STATE; + + bool woke = false; + esp_err_t err = sy7636a_i2c_begin(s_sy, &woke); + if (err == ESP_OK) err = sy7636a_read(s_sy, &status->sy); + sy7636a_i2c_end(s_sy, woke); + + if (err == ESP_OK) { + status->sy_live = true; + } else { + const sy7636a_status_t* last = sy7636a_last(s_sy); + if (last) status->sy = *last; + ESP_LOGW(TAG, "SY7636A live read failed: %s", esp_err_to_name(err)); + } + + // 轨没开时芯片已被 EN 复位,页面改显示即将写入的缓存。 + // / Rails off means EN already reset the chip; the page shows the pending cache. + if (!rails_on) { + const sy7636a_power_config_t* cfg = sy7636a_get_config(s_sy); + if (cfg) status_from_cfg(&status->sy, cfg, false); + } + return err; +} + +esp_err_t read_pico_get_ioe_status(read_pico_status_t* status) { + if (status == NULL) return ESP_ERR_INVALID_ARG; + return fill_ioe_status(status, false); +} + +esp_err_t read_pico_get_ioe_status_full(read_pico_status_t* status) { + if (status == NULL) return ESP_ERR_INVALID_ARG; + return fill_ioe_status(status, true); +} + +esp_err_t read_pico_toggle_vcomctl(void) { + if (s_sy == NULL) return ESP_ERR_INVALID_STATE; + const sy7636a_power_config_t* cfg = sy7636a_get_config(s_sy); + bool next = cfg ? !cfg->vcom_manual : true; + return sy7636a_set_vcom_manual(s_sy, next); +} + +bool read_pico_rails_on(void) { + return rails_on; +} + +bool read_pico_sd_present(void) { + // 卡座 CD 信号低有效,连接到 FCA9555 P0.6。 + // 读失败时不要把低有效 CD 判成“有卡”。 + // / Slot CD is active-low on FCA9555 P0.6. A failed read must not look like "card present". + uint8_t in0 = 0xFF; + if (s_ioe == NULL || fca9555_read_reg(s_ioe, 0, &in0) != ESP_OK) return false; + return (in0 & IOE_SD_CD) == 0; +} + +void read_pico_clear_ioe_int(void) { + (void)fca9555_clear_int(s_ioe); +} + +esp_err_t read_pico_tp_rst(bool high) { + return ioe_set(IOE_TP_RST, high); +} + +esp_err_t read_pico_touch_reset(void) { + // 低有效复位,时序照 board_init 里那次上电复位:拉低 10ms 再放开。 + // / Active-low reset, same as board_init: hold 10 ms then release. + esp_err_t err = read_pico_tp_rst(false); + if (err != ESP_OK) return err; + vTaskDelay(pdMS_TO_TICKS(10)); + return read_pico_tp_rst(true); +} + +const EpdBoardDefinition epd_board_read_pico = { + .init = board_init, + .deinit = board_deinit, + .set_ctrl = board_set_ctrl, + .poweron = board_poweron, + .poweroff = board_poweroff, + .measure_vcom = board_measure_vcom, + .get_temperature = board_temperature, + .set_vcom = board_set_vcom, + .gpio_set_direction = NULL, + .gpio_read = NULL, + .gpio_write = NULL, +}; diff --git a/firmware/targets/mindreset_read_pico/components/read_pico/read_pico_buzzer.c b/firmware/targets/mindreset_read_pico/components/read_pico/read_pico_buzzer.c new file mode 100644 index 0000000..5dd6f3e --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/read_pico/read_pico_buzzer.c @@ -0,0 +1,862 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * GPIO2 → Q1(AO3400A) 栅极,R8=100k 下拉。高电平导通,低电平关断。 + * SYS_VDD→蜂鸣器→漏极,D1 续流。不是隔直/低通音频级。 + * + * tone():音频频率方波,空闲 duty=0。 + * 曲谱:零中心 PCM 在任务中合成,再映射为占空比,经 pwm_audio 改 10bit 载波。 + * 停播/失败/欠载:禁用 PWM 并输出低,不用 50% 表示停止。 + * 软件限幅不是电流保护。未确认型号、SYS_VDD、线圈电流前,工作点只是实验值。 + * + * GPIO2 → Q1 (AO3400A) gate, R8=100k pulldown. High on, low off. + * SYS_VDD → buzzer → drain, D1 freewheel. Not a DC-block / LPF audio stage. + * + * tone(): audio-rate square, idle duty=0. + * Score: zero-centered PCM in a task, mapped to duty, 10-bit carrier via pwm_audio. + * Stop / fail / underrun: disable PWM and drive low; 50% is not "off". + * Software clip is not current protection. Until part, SYS_VDD, and coil + * current are confirmed, the setpoint is experimental. + */ + +#include "read_pico_buzzer.h" + +#include +#include + +#include "buzzer_1bit.h" +#include "buzzer_score.h" +#include "driver/gpio.h" +#include "driver/ledc.h" +#include "driver/rmt_encoder.h" +#include "driver/rmt_tx.h" +#include "esp_err.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/task.h" +#include "pwm_audio.h" +#include "soc/soc.h" + +#ifndef APB_CLK_FREQ +#define APB_CLK_FREQ 80000000 +#endif + +#define TAG "buzzer" + +#define BUZZER_GPIO GPIO_NUM_2 +#define BUZZER_TIMER LEDC_TIMER_0 +#define BUZZER_CHANNEL LEDC_CHANNEL_0 +#define PWM_RES LEDC_TIMER_10_BIT +#define CHUNK 512 +#define FADE_N 1280 +#define RINGBUF_LEN (8 * 1024) +#define WRITE_WAIT_MS 80 +#define DRAIN_MS 150 +#define STOP_WAIT_MS 2500 +#define PROBE_MS 400 +#define PROBE_15K_HZ 1500 +#define PROBE_4K_HZ 4000 +#define MIDI_E5 76 +#define LEDC_DUTY_FS 1024 +#define PWM_AUDIO_DUTY_MAX 1023 +#define PEN_B_DUTY_HI PWM_AUDIO_DUTY_MAX +#define BIT1_WINDOW_US 4000 +#define BIT1_RMT_HZ 1000000 + +#define JOB_SCORE 0 +#define JOB_A 1 +#define JOB_B 2 +#define JOB_1BIT 3 +#define JOB_4K 4 + +static bool s_ledc_ready; +static bool s_pwm_up; +static volatile bool s_score_playing; +static volatile bool s_score_stop; +static uint32_t s_carrier_hz; +static uint32_t s_underrun; +static SemaphoreHandle_t s_lock; +static TaskHandle_t s_score_task; +static int16_t s_chunk[CHUNK]; +static int16_t s_hw[CHUNK]; +static buzzer_score_stats_t s_stats; +static buzzer_duty_map_t s_map = BUZZER_DUTY_MAP_EXPERIMENTAL; +static uint8_t s_job; +static uint8_t s_1bit_voices = 1; +static buzzer_duty_accum_t s_duty_acc; +static read_pico_buzzer_out_stats_t s_out; +static const uint16_t k_1bit_hz[BUZZER_1BIT_VOICES_MAX] = { + 1500, 1000, 1250, 750, 1875, 600 +}; + +static void lock_once(void) { + if (s_lock != NULL) { + return; + } + s_lock = xSemaphoreCreateMutex(); +} + +static void gpio_idle(void) { + gpio_reset_pin(BUZZER_GPIO); + gpio_set_direction(BUZZER_GPIO, GPIO_MODE_OUTPUT); + gpio_set_level(BUZZER_GPIO, 0); +} + +void read_pico_buzzer_duty_map_get(buzzer_duty_map_t *map) { + if (map != NULL) { + *map = s_map; + } +} + +esp_err_t read_pico_buzzer_duty_map_set(const buzzer_duty_map_t *map) { + if (!buzzer_duty_map_valid(map)) { + return ESP_ERR_INVALID_ARG; + } + s_map = *map; + return ESP_OK; +} + +uint32_t read_pico_buzzer_pwm_carrier_hz_nominal(void) { + uint32_t freq = (uint32_t)APB_CLK_FREQ / (1u << 10); + return freq - (freq % 1000u); +} + +uint32_t read_pico_buzzer_pwm_carrier_hz(void) { + return s_carrier_hz; +} + +bool read_pico_buzzer_score_busy(void) { + return s_score_playing; +} + +static esp_err_t write_hw(const int16_t *pcm, int n, bool allow_stop); +static esp_err_t ledc_tone_init(void); + +typedef int (*pcm_fill_fn)(int16_t *dst, int n, void *ctx); + +static esp_err_t write_duty_ramp(uint16_t from, uint16_t to, bool allow_stop) { + int done = 0; + while (done < FADE_N) { + int n = FADE_N - done; + if (n > CHUNK) { + n = CHUNK; + } + for (int i = 0; i < n; i++) { + int idx = done + i; + uint16_t d = (uint16_t)((int32_t)from + ((int32_t)to - (int32_t)from) * idx / (FADE_N - 1)); + buzzer_duty_accum_add(&s_duty_acc, d, 0); + s_hw[i] = buzzer_duty_to_pwm_audio_pcm(d); + } + esp_err_t err = write_hw(s_hw, n, allow_stop); + if (err != ESP_OK) { + return err; + } + done += n; + } + return ESP_OK; +} + +static esp_err_t write_hw(const int16_t *pcm, int n, bool allow_stop) { + const uint8_t *p = (const uint8_t *)pcm; + size_t left = (size_t)n * sizeof(int16_t); + while (left > 0) { + if (allow_stop && s_score_stop) { + return ESP_ERR_INVALID_STATE; + } + size_t wrote = 0; + esp_err_t err = pwm_audio_write((uint8_t *)p, left, &wrote, pdMS_TO_TICKS(WRITE_WAIT_MS)); + if (err != ESP_OK || wrote == 0) { + s_underrun++; + if (s_underrun > 8) { + return err != ESP_OK ? err : ESP_FAIL; + } + continue; + } + p += wrote; + left -= wrote; + } + return ESP_OK; +} + +static esp_err_t submit_pcm(const int16_t *pcm, int n, bool allow_stop) { + for (int i = 0; i < n; i++) { + int clip = 0; + uint16_t d = buzzer_duty_from_sample_ex(&s_map, pcm[i], &clip); + buzzer_duty_accum_add(&s_duty_acc, d, clip); + s_hw[i] = buzzer_duty_to_pwm_audio_pcm(d); + } + return write_hw(s_hw, n, allow_stop); +} + +static void finish_out_stats(uint8_t path, uint32_t carrier_hz) { + buzzer_duty_accum_result( + &s_duty_acc, &s_out.duty_min, &s_out.duty_max, &s_out.duty_mean_x100, + &s_out.duty_ac_rms_x100, &s_out.samples, &s_out.duty_clips + ); + s_out.carrier_hz = carrier_hz; + s_out.resolution_bits = 10; + s_out.path = path; + s_out.underruns = s_underrun; + s_out.pcm_peak = s_stats.peak; + s_out.pcm_rms = buzzer_score_pcm_rms(&s_stats); + s_out.current_ma_valid = 0; + s_out.current_ma = 0; + s_out.shift_st = buzzer_score_semitone_shift(); + s_out.duty_fs = LEDC_DUTY_FS; + s_out.duty_hw_max = PWM_AUDIO_DUTY_MAX; + s_out.residual_low_ticks = path == READ_PICO_BUZZER_PATH_HF ? 1 : 0; +} + +static const char *timbre_tag(void) { + switch (buzzer_score_timbre()) { + case BUZZER_SCORE_TIMBRE_BRIGHT: + return "bright"; + case BUZZER_SCORE_TIMBRE_ENHANCE: + return "enhance"; + default: + return "soft"; + } +} + +static const char *staff_tag(void) { + uint8_t m = buzzer_score_staff_mask(); + if (m == BUZZER_SCORE_STAFF_RH) { + return "rh"; + } + if (m == BUZZER_SCORE_STAFF_LH) { + return "lh"; + } + return "both"; +} + +static const char *env_tag(void) { + return buzzer_score_env() == BUZZER_SCORE_ENV_HOLD ? "hold" : "piano"; +} + +static const char *path_tag(void) { + if (s_out.path == READ_PICO_BUZZER_PATH_DIRECT_1BIT) { + return "1bit"; + } + return s_out.path == READ_PICO_BUZZER_PATH_HF ? "hf" : "classic"; +} + +static uint32_t sounding_hz(uint8_t midi, int8_t shift) { + int m = (int)midi + (int)shift; + if (m < 0) { + m = 0; + } else if (m > 127) { + m = 127; + } + return (uint32_t)(((uint64_t)buzzer_score_midi_inc((uint8_t)m) * (uint64_t)BUZZER_SCORE_SR) >> 32); +} + +static void log_out_stats(const char *tag) { + ESP_LOGI( + TAG, + "%s path=%s shift_st=%d E5_sounding_hz=%" PRIu32 + " tone_hz=%" PRIu32 " tone_hz_actual=%" PRIu32 " voices=%u" + " pcm_peak=%" PRId32 " pcm_rms=%" PRIu32 + " duty min=%u max=%u fs=%u hw_max=%u residual_low_ticks=%u" + " mean=%" PRIu32 ".%02" PRIu32 " ac_rms=%" PRIu32 ".%02" PRIu32 + " clips=%" PRIu32 " pwm=%" PRIu32 "Hz/%ubit n=%" PRIu32 + " I=unverified (scope GPIO2/gate; software is not a measurement)", + tag, path_tag(), (int)s_out.shift_st, sounding_hz(MIDI_E5, s_out.shift_st), + s_out.tone_hz, s_out.tone_hz_actual, (unsigned)s_out.voices, + s_out.pcm_peak, s_out.pcm_rms, s_out.duty_min, s_out.duty_max, s_out.duty_fs, + s_out.duty_hw_max, (unsigned)s_out.residual_low_ticks, + s_out.duty_mean_x100 / 100, s_out.duty_mean_x100 % 100, s_out.duty_ac_rms_x100 / 100, + s_out.duty_ac_rms_x100 % 100, s_out.duty_clips, s_out.carrier_hz, + (unsigned)s_out.resolution_bits, s_out.samples + ); +} + +static void score_teardown(void) { + if (s_pwm_up) { + pwm_audio_status_t st; + if (pwm_audio_get_status(&st) == ESP_OK && st == PWM_AUDIO_STATUS_BUSY) { + pwm_audio_stop(); + } + pwm_audio_deinit(); + s_pwm_up = false; + } + gpio_idle(); + s_ledc_ready = false; +} + +static esp_err_t score_setup(void) { + if (s_ledc_ready) { + ledc_stop(LEDC_LOW_SPEED_MODE, BUZZER_CHANNEL, 0); + s_ledc_ready = false; + } + gpio_idle(); + + pwm_audio_config_t cfg = { + .gpio_num_left = BUZZER_GPIO, + .gpio_num_right = -1, + .ledc_channel_left = LEDC_CHANNEL_0, + .ledc_channel_right = LEDC_CHANNEL_1, + .ledc_timer_sel = LEDC_TIMER_0, + .duty_resolution = PWM_RES, + .ringbuf_len = RINGBUF_LEN, + }; + esp_err_t err = pwm_audio_init(&cfg); + if (err != ESP_OK) { + gpio_idle(); + return err; + } + ledc_set_duty(LEDC_LOW_SPEED_MODE, BUZZER_CHANNEL, 0); + ledc_update_duty(LEDC_LOW_SPEED_MODE, BUZZER_CHANNEL); + + err = pwm_audio_set_param(BUZZER_SCORE_SR, 16, 1); + if (err != ESP_OK) { + pwm_audio_deinit(); + gpio_idle(); + return err; + } + pwm_audio_set_volume(0); + err = pwm_audio_start(); + if (err != ESP_OK) { + pwm_audio_deinit(); + gpio_idle(); + return err; + } + s_carrier_hz = ledc_get_freq(LEDC_LOW_SPEED_MODE, BUZZER_TIMER); + s_pwm_up = true; + return ESP_OK; +} + +static void note_pcm(int16_t s) { + int32_t a = s < 0 ? -s : s; + if (a > s_stats.peak) { + s_stats.peak = a; + } + s_stats.sum_sq += (uint64_t)((int32_t)s * (int32_t)s); + s_stats.samples++; +} + +static esp_err_t play_hf_pcm_loop(pcm_fill_fn fill, void *ctx, bool count_pcm) { + esp_err_t err = score_setup(); + if (err != ESP_OK || s_score_stop) { + return err != ESP_OK ? err : ESP_ERR_INVALID_STATE; + } + err = write_duty_ramp(0, s_map.bias, true); + while (err == ESP_OK && !s_score_stop) { + int n = fill(s_chunk, CHUNK, ctx); + if (n <= 0) { + break; + } + if (count_pcm) { + for (int i = 0; i < n; i++) { + note_pcm(s_chunk[i]); + } + } + err = submit_pcm(s_chunk, n, true); + } + return err; +} + +static int fill_score(int16_t *dst, int n, void *ctx) { + (void)ctx; + if (!buzzer_score_render(dst, n, &s_stats)) { + return 0; + } + return n; +} + +static void play_classic(uint32_t hz, const char *tag) { + gpio_idle(); + if (s_ledc_ready) { + ledc_stop(LEDC_LOW_SPEED_MODE, BUZZER_CHANNEL, 0); + s_ledc_ready = false; + } + esp_err_t err = ledc_tone_init(); + if (err != ESP_OK) { + gpio_idle(); + ESP_LOGE(TAG, "%s %s", tag, esp_err_to_name(err)); + return; + } + ledc_set_freq(LEDC_LOW_SPEED_MODE, BUZZER_TIMER, hz); + ledc_set_duty(LEDC_LOW_SPEED_MODE, BUZZER_CHANNEL, LEDC_DUTY_FS / 2); + ledc_update_duty(LEDC_LOW_SPEED_MODE, BUZZER_CHANNEL); + uint32_t actual = ledc_get_freq(LEDC_LOW_SPEED_MODE, BUZZER_TIMER); + ESP_LOGI( + TAG, + "%s classic 50%% square req_hz=%" PRIu32 " actual_hz=%" PRIu32 + " duty=512/%u (LEDC allows 0..%u; 1023 is not constant high)", + tag, hz, actual, (unsigned)LEDC_DUTY_FS, (unsigned)LEDC_DUTY_FS + ); + if (!s_score_stop) { + vTaskDelay(pdMS_TO_TICKS(PROBE_MS)); + } + ledc_set_duty(LEDC_LOW_SPEED_MODE, BUZZER_CHANNEL, 0); + ledc_update_duty(LEDC_LOW_SPEED_MODE, BUZZER_CHANNEL); + gpio_idle(); + s_ledc_ready = false; + + buzzer_duty_accum_reset(&s_duty_acc); + buzzer_duty_accum_add(&s_duty_acc, LEDC_DUTY_FS / 2, 0); + finish_out_stats(READ_PICO_BUZZER_PATH_CLASSIC, actual); + s_out.tone_hz = hz; + s_out.tone_hz_actual = actual; + s_out.voices = 1; + s_out.samples = (uint32_t)PROBE_MS * actual / 1000u; + s_out.pcm_peak = 0; + s_out.pcm_rms = 0; + s_out.residual_low_ticks = 0; + log_out_stats(tag); + if (hz == PROBE_4K_HZ) { + ESP_LOGI(TAG, "4k ref: not an efficiency comparison vs 1.5k A/B"); + } +} + +static void play_score_job(void) { + buzzer_score_init(); + buzzer_score_reset(&s_stats); + buzzer_duty_accum_reset(&s_duty_acc); + s_underrun = 0; + + int32_t gain = buzzer_score_prepare(&s_map, &s_score_stop); + int32_t target = buzzer_duty_legal_pcm(&s_map); + ESP_LOGI( + TAG, + "prepare timbre=%s env=%s staff=%s shift_st=%d E5_sounding_hz=%" PRIu32 + " raw_peak=%" PRId32 " gain_q12=%" PRId32 " pcm_target=%" PRId32 + " (pcm ±target already near full duty swing; not raising gain)" + " duty_fs=%u hw_max=%u residual_low_ticks=1", + timbre_tag(), env_tag(), staff_tag(), (int)buzzer_score_semitone_shift(), + sounding_hz(MIDI_E5, buzzer_score_semitone_shift()), + buzzer_score_raw_peak(), gain, target, (unsigned)LEDC_DUTY_FS, + (unsigned)PWM_AUDIO_DUTY_MAX + ); + if (s_score_stop || gain <= 0) { + finish_out_stats(READ_PICO_BUZZER_PATH_HF, s_carrier_hz); + log_out_stats("score"); + return; + } + buzzer_score_reset(&s_stats); + esp_err_t err = play_hf_pcm_loop(fill_score, NULL, false); + + const bool emergency = (s_score_stop || (err != ESP_OK && err != ESP_ERR_INVALID_STATE)); + if (!emergency && s_pwm_up) { + (void)write_duty_ramp(s_map.bias, 0, false); + vTaskDelay(pdMS_TO_TICKS(DRAIN_MS)); + } + uint32_t carrier = s_carrier_hz; + score_teardown(); + finish_out_stats(READ_PICO_BUZZER_PATH_HF, carrier); + s_out.pcm_peak = s_stats.peak; + s_out.pcm_rms = buzzer_score_pcm_rms(&s_stats); + s_out.tone_hz = sounding_hz(MIDI_E5, buzzer_score_semitone_shift()); + s_out.tone_hz_actual = s_out.tone_hz; + s_out.voices = 0; + log_out_stats("score"); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + ESP_LOGE(TAG, "score %s", esp_err_to_name(err)); + } +} + +static void play_pen_b(void) { + buzzer_duty_accum_reset(&s_duty_acc); + s_underrun = 0; + memset(&s_stats, 0, sizeof(s_stats)); + + esp_err_t err = score_setup(); + uint32_t carrier = s_carrier_hz; + ESP_LOGI( + TAG, + "probeB hf duty-square req_hz=%u sr=%u carrier=%" PRIu32 + "Hz duty 0 <-> %u (LEDC full-on is %u; pwm_audio max %u leaves residual_low=1 tick/period)", + (unsigned)PROBE_15K_HZ, (unsigned)BUZZER_SCORE_SR, carrier, (unsigned)PEN_B_DUTY_HI, + (unsigned)LEDC_DUTY_FS, (unsigned)PWM_AUDIO_DUTY_MAX + ); + + uint32_t left = (uint32_t)BUZZER_SCORE_SR * PROBE_MS / 1000u; + uint32_t phase = 0; + uint32_t inc = (uint32_t)(((uint64_t)PROBE_15K_HZ << 32) / (uint64_t)BUZZER_SCORE_SR); + while (err == ESP_OK && !s_score_stop && left > 0) { + int n = CHUNK; + if ((uint32_t)n > left) { + n = (int)left; + } + for (int i = 0; i < n; i++) { + uint16_t d = (phase < 0x80000000u) ? PEN_B_DUTY_HI : 0; + buzzer_duty_accum_add(&s_duty_acc, d, 0); + s_hw[i] = buzzer_duty_to_pwm_audio_pcm(d); + s_chunk[i] = d != 0 ? 32767 : -32767; + note_pcm(s_chunk[i]); + phase += inc; + } + err = write_hw(s_hw, n, true); + left -= (uint32_t)n; + } + + score_teardown(); + finish_out_stats(READ_PICO_BUZZER_PATH_HF, carrier); + s_out.tone_hz = PROBE_15K_HZ; + s_out.tone_hz_actual = PROBE_15K_HZ; + s_out.voices = 1; + s_out.pcm_peak = s_stats.peak; + s_out.pcm_rms = buzzer_score_pcm_rms(&s_stats); + s_out.residual_low_ticks = 1; + log_out_stats("probeB"); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + ESP_LOGE(TAG, "probeB %s", esp_err_to_name(err)); + } +} + +static void rmt_put(rmt_symbol_word_t *out, int *o, int max_out, int lvl0, uint32_t d0, int lvl1, uint32_t d1) { + if (*o >= max_out) { + return; + } + if (d0 == 0) { + d0 = 1; + } + if (d1 == 0) { + d1 = 1; + } + if (d0 > 32767u) { + d0 = 32767u; + } + if (d1 > 32767u) { + d1 = 32767u; + } + out[*o].level0 = (uint32_t)lvl0; + out[*o].duration0 = (uint16_t)d0; + out[*o].level1 = (uint32_t)lvl1; + out[*o].duration1 = (uint16_t)d1; + (*o)++; +} + +static int sym_to_rmt(const buzzer_1bit_sym_t *in, int n, rmt_symbol_word_t *out, int max_out) { + int o = 0; + for (int i = 0; i < n && o < max_out; i++) { + uint32_t hi = in[i].high_us; + uint32_t lo = in[i].low_us; + while (hi > 32767u && o < max_out) { + rmt_put(out, &o, max_out, 1, 32767u, 1, 1); + hi -= (hi > 32768u) ? 32768u : hi; + } + if (hi > 0 && o < max_out) { + uint32_t b = lo > 32767u ? 32767u : lo; + if (b == 0) { + b = 1; + } else { + lo -= b; + } + rmt_put(out, &o, max_out, 1, hi, 0, b); + } + while (lo > 0 && o < max_out) { + uint32_t b = lo > 32767u ? 32767u : lo; + lo -= b; + rmt_put(out, &o, max_out, 0, b, 0, 1); + } + } + if (o > 0 && (o & 1)) { + rmt_put(out, &o, max_out, 0, 1, 0, 1); + } + return o; +} + +static void play_1bit(void) { + uint8_t n = s_1bit_voices; + if (n != 2 && n != 4 && n != 6) { + n = 1; + } + uint16_t w[BUZZER_1BIT_VOICES_MAX]; + for (int i = 0; i < BUZZER_1BIT_VOICES_MAX; i++) { + w[i] = 256; + } + buzzer_1bit_cfg_t cfg = BUZZER_1BIT_CFG_DEFAULT; + buzzer_1bit_sym_t ev[BUZZER_1BIT_SYM_MAX]; + buzzer_1bit_stats_t st; + int nev = buzzer_1bit_build(k_1bit_hz, w, n, &cfg, BIT1_WINDOW_US, ev, BUZZER_1BIT_SYM_MAX, &st); + + static rmt_symbol_word_t items[BUZZER_1BIT_SYM_MAX * 2]; + int nitem = sym_to_rmt(ev, nev, items, (int)(sizeof(items) / sizeof(items[0]))); + + ESP_LOGI( + TAG, + "1bit voices=%u hz=%u%s pulse_us=%u max_slip_us=%u window_us=%u" + " emitted=%" PRIu32 " dropped=%" PRIu32 " serialized=%" PRIu32 + " merged=%" PRIu32 " high_us=%" PRIu32 "/%" PRIu32, + (unsigned)n, (unsigned)k_1bit_hz[0], n > 1 ? "+…" : "", (unsigned)cfg.pulse_us, + (unsigned)cfg.max_slip_us, (unsigned)BIT1_WINDOW_US, st.emitted, st.dropped, + st.serialized, st.merged_high, st.high_us, st.window_us + ); + for (uint8_t i = 0; i < n; i++) { + ESP_LOGI(TAG, "1bit voice%u %uHz", (unsigned)i, (unsigned)k_1bit_hz[i]); + } + + gpio_idle(); + if (nitem <= 0) { + ESP_LOGE(TAG, "1bit empty schedule"); + return; + } + + const int rmt_ram = 48; + bool use_dma = nitem > rmt_ram; + if (use_dma) { + ESP_LOGW(TAG, "1bit nitem=%d > RMT RAM %d, trying DMA", nitem, rmt_ram); + } + + rmt_channel_handle_t chan = NULL; + rmt_encoder_handle_t enc = NULL; + rmt_tx_channel_config_t tcfg = { + .gpio_num = BUZZER_GPIO, + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = BIT1_RMT_HZ, + .mem_block_symbols = use_dma ? 128 : (size_t)rmt_ram, + .trans_queue_depth = 4, + .flags = { + .with_dma = use_dma ? 1u : 0u, + .init_level = 0, + }, + }; + esp_err_t err = rmt_new_tx_channel(&tcfg, &chan); + if (err != ESP_OK && use_dma) { + ESP_LOGW(TAG, "1bit DMA %s, truncate to %d (no bit-bang)", esp_err_to_name(err), rmt_ram); + nitem = rmt_ram; + if (nitem & 1) { + nitem--; + } + use_dma = false; + tcfg.mem_block_symbols = (size_t)rmt_ram; + tcfg.flags.with_dma = 0; + err = rmt_new_tx_channel(&tcfg, &chan); + } + if (err != ESP_OK) { + gpio_idle(); + ESP_LOGE(TAG, "1bit rmt_new_tx_channel %s (no bit-bang fallback)", esp_err_to_name(err)); + return; + } + rmt_copy_encoder_config_t ecfg = {}; + err = rmt_new_copy_encoder(&ecfg, &enc); + if (err != ESP_OK) { + rmt_del_channel(chan); + gpio_idle(); + ESP_LOGE(TAG, "1bit encoder %s", esp_err_to_name(err)); + return; + } + err = rmt_enable(chan); + if (err != ESP_OK) { + rmt_del_encoder(enc); + rmt_del_channel(chan); + gpio_idle(); + ESP_LOGE(TAG, "1bit enable %s", esp_err_to_name(err)); + return; + } + + rmt_transmit_config_t xcfg = { + .loop_count = -1, + .flags = { .eot_level = 0 }, + }; + err = rmt_transmit(chan, enc, items, (size_t)nitem * sizeof(items[0]), &xcfg); + if (err == ESP_OK && !s_score_stop) { + vTaskDelay(pdMS_TO_TICKS(PROBE_MS)); + } + rmt_disable(chan); + rmt_del_encoder(enc); + rmt_del_channel(chan); + gpio_idle(); + + uint32_t duty_x1000 = st.window_us ? (st.high_us * 1000u / st.window_us) : 0; + buzzer_duty_accum_reset(&s_duty_acc); + finish_out_stats(READ_PICO_BUZZER_PATH_DIRECT_1BIT, BIT1_RMT_HZ); + s_out.tone_hz = k_1bit_hz[0]; + s_out.tone_hz_actual = k_1bit_hz[0]; + s_out.voices = n; + s_out.pcm_peak = 0; + s_out.pcm_rms = 0; + s_out.duty_min = 0; + s_out.duty_max = 1; + s_out.duty_mean_x100 = duty_x1000 / 10; + s_out.residual_low_ticks = 0; + s_out.resolution_bits = 1; + log_out_stats("1bit"); + if (err != ESP_OK) { + ESP_LOGE(TAG, "1bit %s", esp_err_to_name(err)); + } + + if (s_1bit_voices == 1) { + s_1bit_voices = 2; + } else if (s_1bit_voices == 2) { + s_1bit_voices = 4; + } else if (s_1bit_voices == 4) { + s_1bit_voices = 6; + } else { + s_1bit_voices = 1; + } +} + +static void score_task(void *arg) { + (void)arg; + if (s_lock == NULL || xSemaphoreTake(s_lock, pdMS_TO_TICKS(50)) != pdTRUE) { + s_score_playing = false; + s_score_task = NULL; + vTaskDelete(NULL); + return; + } + + memset(&s_out, 0, sizeof(s_out)); + memset(&s_stats, 0, sizeof(s_stats)); + s_underrun = 0; + buzzer_score_init(); + + if (s_job == JOB_A) { + play_classic(PROBE_15K_HZ, "probeA"); + } else if (s_job == JOB_B) { + play_pen_b(); + } else if (s_job == JOB_1BIT) { + play_1bit(); + } else if (s_job == JOB_4K) { + play_classic(PROBE_4K_HZ, "probe4k"); + } else { + play_score_job(); + } + + s_score_playing = false; + s_score_task = NULL; + xSemaphoreGive(s_lock); + vTaskDelete(NULL); +} + +static esp_err_t start_job(uint8_t job) { + lock_once(); + if (s_lock == NULL) { + return ESP_ERR_NO_MEM; + } + if (xSemaphoreTake(s_lock, 0) != pdTRUE) { + return s_score_playing ? ESP_OK : ESP_ERR_INVALID_STATE; + } + if (s_score_playing) { + xSemaphoreGive(s_lock); + return ESP_OK; + } + s_job = job; + s_score_stop = false; + s_score_playing = true; + xSemaphoreGive(s_lock); + BaseType_t ok = xTaskCreatePinnedToCore(score_task, "bz_job", 6144, NULL, 4, &s_score_task, 1); + if (ok != pdPASS) { + s_score_playing = false; + return ESP_ERR_NO_MEM; + } + return ESP_OK; +} + +esp_err_t read_pico_buzzer_score_play(void) { + return start_job(JOB_SCORE); +} + +esp_err_t read_pico_buzzer_probe(read_pico_buzzer_probe_t probe) { + switch (probe) { + case READ_PICO_BUZZER_PROBE_A_15K: + return start_job(JOB_A); + case READ_PICO_BUZZER_PROBE_B_15K: + return start_job(JOB_B); + case READ_PICO_BUZZER_PROBE_1BIT: + return start_job(JOB_1BIT); + case READ_PICO_BUZZER_PROBE_4K: + return start_job(JOB_4K); + default: + return ESP_ERR_INVALID_ARG; + } +} + +void read_pico_buzzer_out_stats_get(read_pico_buzzer_out_stats_t *st) { + if (st != NULL) { + *st = s_out; + } +} + +esp_err_t read_pico_buzzer_score_stop(void) { + if (!s_score_playing) { + return ESP_OK; + } + s_score_stop = true; + TickType_t t0 = xTaskGetTickCount(); + while (s_score_playing) { + if ((xTaskGetTickCount() - t0) > pdMS_TO_TICKS(STOP_WAIT_MS)) { + gpio_idle(); + return ESP_ERR_TIMEOUT; + } + vTaskDelay(1); + } + return ESP_OK; +} + +esp_err_t read_pico_buzzer_effect(void) { + return read_pico_buzzer_score_play(); +} + +static esp_err_t ledc_tone_init(void) { + if (s_ledc_ready) { + return ESP_OK; + } + gpio_idle(); + const ledc_timer_config_t timer = { + .speed_mode = LEDC_LOW_SPEED_MODE, + .duty_resolution = LEDC_TIMER_10_BIT, + .timer_num = BUZZER_TIMER, + .freq_hz = 2700, + .clk_cfg = LEDC_AUTO_CLK, + }; + esp_err_t err = ledc_timer_config(&timer); + if (err != ESP_OK) { + gpio_idle(); + return err; + } + const ledc_channel_config_t channel = { + .gpio_num = BUZZER_GPIO, + .speed_mode = LEDC_LOW_SPEED_MODE, + .channel = BUZZER_CHANNEL, + .intr_type = LEDC_INTR_DISABLE, + .timer_sel = BUZZER_TIMER, + .duty = 0, + .hpoint = 0, + }; + err = ledc_channel_config(&channel); + if (err != ESP_OK) { + gpio_idle(); + return err; + } + s_ledc_ready = true; + return ESP_OK; +} + +esp_err_t read_pico_buzzer_tone(uint32_t frequency_hz, uint32_t duration_ms) { + if (frequency_hz == 0 || duration_ms == 0) { + return ESP_ERR_INVALID_ARG; + } + if (s_score_playing) { + return ESP_ERR_INVALID_STATE; + } + lock_once(); + if (s_lock == NULL) { + return ESP_ERR_NO_MEM; + } + if (xSemaphoreTake(s_lock, 0) != pdTRUE) { + return ESP_ERR_INVALID_STATE; + } + esp_err_t err = ledc_tone_init(); + if (err != ESP_OK) { + xSemaphoreGive(s_lock); + return err; + } + if (frequency_hz < 70) { + frequency_hz = 70; + } + if (frequency_hz > 6000) { + frequency_hz = 6000; + } + ledc_set_freq(LEDC_LOW_SPEED_MODE, BUZZER_TIMER, frequency_hz); + ledc_set_duty(LEDC_LOW_SPEED_MODE, BUZZER_CHANNEL, 512); + ledc_update_duty(LEDC_LOW_SPEED_MODE, BUZZER_CHANNEL); + vTaskDelay(pdMS_TO_TICKS(duration_ms)); + ledc_set_duty(LEDC_LOW_SPEED_MODE, BUZZER_CHANNEL, 0); + ledc_update_duty(LEDC_LOW_SPEED_MODE, BUZZER_CHANNEL); + xSemaphoreGive(s_lock); + return ESP_OK; +} diff --git a/firmware/targets/mindreset_read_pico/components/read_pico/read_pico_epd_timing.c b/firmware/targets/mindreset_read_pico/components/read_pico/read_pico_epd_timing.c new file mode 100644 index 0000000..e610450 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/read_pico/read_pico_epd_timing.c @@ -0,0 +1,177 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * 按像素钟解出行/帧扫描参数。 + * + * Solve line/frame scan parameters from the pixel clock. + */ + +#include "read_pico_epd_timing.h" + +#include "e0470_epaper_waveform.h" +#include "epdiy.h" +#include "esp_log.h" + +#define TAG "read_pico" + +// 规格给的消隐下界:行起始 ≥0.3µs、行后肩 ≥0.8µs、行结束 ≥4 钟。 +// / Spec blanking floors: line start ≥0.3 µs, back porch ≥0.8 µs, line end ≥4 clocks. +#define LINE_START_MIN_NS 300 +#define LINE_BACK_MIN_NS 800 +#define LINE_END_MIN_CLK 4 + +// 帧消隐:起始 1 行、后肩固定 4 行、结束 12 行。 +// / Frame blanking: 1 start line, 4-line back porch, 12 end lines. +#define FRAME_START_LINES 1 +#define FRAME_BACK_LINES 4 +#define FRAME_END_LINES 12 + +// 栅极开门必须严格短于一行。厂家表 LGONL≈13.4µs,低电平至少留 1.2µs。 +// / Gate open must be strictly shorter than one line. Vendor LGONL≈13.4 µs; leave ≥1.2 µs low. +#define CKV_LOW_MIN_01US 12 +#define CKV_HIGH_MAX_01US 134 + +static int s_frame_target_us = E0470_WAVEFORM_FRAME_US; +static int s_ckv_high_max_01us = CKV_HIGH_MAX_01US; +static int s_pclk_mhz; +static read_pico_epd_scan_profile_t s_profile = READ_PICO_EPD_SCAN_FULL; + +int read_pico_epd_frame_target_us(void) { return s_frame_target_us; } + +void read_pico_epd_set_frame_target_us(int us) { s_frame_target_us = us > 0 ? us : 0; } + +int read_pico_epd_ckv_high_max_01us(void) { return s_ckv_high_max_01us; } + +void read_pico_epd_set_ckv_high_max_01us(int v) { + if (v >= CKV_LOW_MIN_01US) s_ckv_high_max_01us = v; +} + +static int clampi(int v, int lo, int hi) { + if (v > hi) v = hi; + if (v < lo) v = lo; + return v; +} + +static int ceil_div(int n, int d) { return (n + d - 1) / d; } + +// ceil(ns × f / 1000):把一段最小时间换成 f MHz 下的整数钟。 +// / ceil(ns × f / 1000): minimum time to integer clocks at f MHz. +static int clocks_for_ns(int ns, int pclk_mhz) { return ceil_div(ns * pclk_mhz, 1000); } + +static LcdLineTiming_t scan_to_line(const read_pico_epd_scan_t* s) { + return (LcdLineTiming_t){ + .le_high_time = s->line_start, + .line_front_porch = s->line_back_porch, + .line_end = s->line_end, + .ckv_high_time = s->ckv_high_01us, + }; +} + +static void apply_lcd(const read_pico_epd_scan_t* scan) { + LcdLineTiming_t line = scan_to_line(scan); + epd_lcd_set_line_timing(&line); + epd_set_lcd_pixel_clock_MHz(scan->pclk_mhz); +} + +void read_pico_epd_scan( + int pclk_mhz, read_pico_epd_scan_profile_t profile, read_pico_epd_scan_t* out +) { + if (out == NULL) return; + pclk_mhz = clampi(pclk_mhz, READ_PICO_EPD_PCLK_MIN_MHZ, READ_PICO_EPD_PCLK_MAX_MHZ); + + const EpdDisplay_t* display = epd_get_display(); + const int p = display->bus_width / 2; // 每像素 2bit,一钟推满整条总线 / 2 bits/pixel, one clock fills the bus + const int ldl = display->width / p; // p 必须整除 H,否则一行落不到整数钟上 / p must divide H or the line is not an integer clock count + const int vall = FRAME_START_LINES + FRAME_BACK_LINES + display->height + FRAME_END_LINES; + const int lsl = clocks_for_ns(LINE_START_MIN_NS, pclk_mhz); + const int lbl = clocks_for_ns(LINE_BACK_MIN_NS, pclk_mhz); + + // 余量给 L_EL,让 L_DL 落在 CKV 高电平里。后肩只取规格下界。 + // / Slack goes to L_EL so L_DL sits inside CKV high. Back porch is the spec floor only. + int line_us = ceil_div(ldl + lsl + lbl + LINE_END_MIN_CLK, pclk_mhz); + if (profile == READ_PICO_EPD_SCAN_FULL) { + const int target_line_us = (s_frame_target_us + vall / 2) / vall; + if (target_line_us > line_us) line_us = target_line_us; + } + + int line_clocks = line_us * pclk_mhz; + int lel = line_clocks - ldl - lsl - lbl; + if (lel < LINE_END_MIN_CLK) { + line_us++; + line_clocks = line_us * pclk_mhz; + lel = line_clocks - ldl - lsl - lbl; + } + + out->pclk_mhz = pclk_mhz; + out->px_per_clk = p; + out->line_start = lsl; + out->line_back_porch = lbl; + out->line_data = ldl; + out->line_clocks = line_clocks; + out->line_end = lel; + out->line_us = line_us; + out->frame_lines = vall; + out->frame_us = line_us * vall; + out->frame_hz_x100 = 100000000 / out->frame_us; + + // 开门至少盖住有效数据,再按厂家 LGONL 上限收;短行盖不住就收到行长减 1.2µs。 + // / Gate must cover active data, then cap at vendor LGONL; if the line is + // too short, cap at line length minus 1.2 µs. + const int data_end_01us = ceil_div((lsl + lbl + ldl) * 10, pclk_mhz); + int ckv = s_ckv_high_max_01us; + if (ckv < data_end_01us) ckv = data_end_01us; + out->ckv_high_01us = clampi(ckv, CKV_LOW_MIN_01US, line_us * 10 - CKV_LOW_MIN_01US); +} + +int read_pico_epd_refresh_ms(int pclk_mhz, read_pico_epd_scan_profile_t profile, int phases) { + read_pico_epd_scan_t scan; + read_pico_epd_scan(pclk_mhz, profile, &scan); + return (phases * scan.frame_us + 500) / 1000; +} + +static void apply_scan(int pclk_mhz, read_pico_epd_scan_profile_t profile) { + read_pico_epd_scan_t scan; + read_pico_epd_scan(pclk_mhz, profile, &scan); + apply_lcd(&scan); + // 每相开扫前的预填行数决定帧间隙。FULL 档一行 16us,喂线程余量大,32 行实测无欠载, + // 帧间隙从约 1ms 归零;FAST 档一行只有 10us,32 行会欠载(EPD_DRAW_EMPTY_LINE_QUEUE, + // 后续帧直接不输出),48/64 实测都没欠载,取 64 留余量。 + // / Prefill lines before each phase set the frame gap. FULL is 16 µs/line, + // so 32 lines has slack and drops the ~1 ms gap to zero. FAST is 10 µs/line; + // 32 underruns (EPD_DRAW_EMPTY_LINE_QUEUE, later frames go silent). 48/64 + // both hold; use 64. + epd_lcd_set_prefill_lines(profile == READ_PICO_EPD_SCAN_FULL ? 32 : 64); + s_pclk_mhz = scan.pclk_mhz; + s_profile = profile; + ESP_LOGI( + TAG, "scan %s %d MHz: line %dus (%d+%d+%d+%d clk), frame %d.%02d Hz, GC16 %d phases %d ms", + profile == READ_PICO_EPD_SCAN_FULL ? "full" : "fast", scan.pclk_mhz, scan.line_us, + scan.line_start, scan.line_back_porch, scan.line_data, scan.line_end, + scan.frame_hz_x100 / 100, scan.frame_hz_x100 % 100, E0470_GC16_FRAMES, + read_pico_epd_refresh_ms(scan.pclk_mhz, profile, E0470_GC16_FRAMES) + ); +} + +void read_pico_epd_init_lcd( + const lcd_bus_config_t* bus, int bus_width, int width, int height, + const read_pico_epd_scan_t* scan +) { + if (bus == NULL || scan == NULL) return; + LcdEpdConfig_t config = { + .pixel_clock = (size_t)scan->pclk_mhz * 1000 * 1000, + .line = scan_to_line(scan), + .bus_width = bus_width, + .bus = *bus, + }; + epd_lcd_init(&config, width, height); +} + +void read_pico_epd_set_pclk(int pclk_mhz) { apply_scan(pclk_mhz, s_profile); } + +int read_pico_epd_pclk_mhz(void) { return s_pclk_mhz; } + +void read_pico_epd_use_scan(read_pico_epd_scan_profile_t profile) { + if (profile == s_profile) return; + apply_scan(s_pclk_mhz, profile); +} diff --git a/firmware/targets/mindreset_read_pico/components/read_pico/read_pico_flash_hpm.c b/firmware/targets/mindreset_read_pico/components/read_pico/read_pico_flash_hpm.c new file mode 100644 index 0000000..ca88f90 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/read_pico/read_pico_flash_hpm.c @@ -0,0 +1,101 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * 覆盖 IDF 的 flash 高性能模式表,补上板上 Zbit ZB25VQ128(0x5E4018)。 + * IDF 只认同厂 8MB 的 0x5E4016,16MB 会被判不支持,120MHz 仍走默认时序。 + * + * Override the IDF HPM table for the onboard Zbit ZB25VQ128 (0x5E4018). + * IDF only lists the 8MB 0x5E4016; the 16MB part is treated as unsupported + * and would run 120 MHz with default timing. + * + * 进入方式和 8MB 一样:写状态寄存器 3 的 HPF 位。先发 0x50(volatile SR + * 写使能),掉电即失效,不改非易失配置。回调在 cache 关闭时跑,函数进 + * IRAM,表和字符串进 DRAM。 + * Same entry as the 8MB part: set HPF in status register 3 after 0x50 + * (write-enable for volatile SR). The bit is lost on power-off. Callbacks + * run with cache off, so code is IRAM and the table/strings are DRAM. + */ + +// spi_flash_override.h 把这张表声明成 weak,直接照着声明定义会连带继承 weak 属性, +// 于是变成两个弱符号竞争,链接器取先遇到的那个(IDF 自己那份),覆盖就白做了。 +// 这里把声明改个名字避开属性,下面再定义真正的强符号。改名必须发生在任何头文件 +// 引入这个声明之前,所以这一段放在所有 include 最前面。 +// The header declares the table weak. Defining it under that name inherits +// weak, so two weaks fight and the linker keeps IDF's copy. Rename the +// declaration first, then define a strong symbol. The rename must happen +// before any header pulls the weak decl in. +#define spi_flash_hpm_enable_list spi_flash_hpm_enable_list_weak_decl +#include "esp_flash_chips/spi_flash_override.h" +#undef spi_flash_hpm_enable_list + +#include + +#include "bootloader_flash_priv.h" +#include "esp_attr.h" +#include "esp_err.h" +#include "esp_rom_spiflash.h" + +#define ZB_FLASH_ID_8MB 0x5E4016 +#define ZB_FLASH_ID_16MB 0x5E4018 +// 状态寄存器 3 的 HPF 位(datasheet 记作 bit5,寄存器内偏移 4)。/ HPF in SR3 (datasheet bit5, offset 4). +#define ZB_HPF_BIT (1 << 4) +// IDF 对同厂芯片的判断:超过这个频率就必须进高性能模式。/ IDF: above this MHz the part must enter HPM. +#define ZB_HPM_THRESHOLD_MHZ 104 + +extern void spi_flash_hpm_get_dummy_generic(spi_flash_hpm_dummy_conf_t* dummy_conf); + +static IRAM_ATTR esp_err_t zb_hpm_probe(uint32_t flash_id) { + switch (flash_id) { + case ZB_FLASH_ID_8MB: + case ZB_FLASH_ID_16MB: + return ESP_OK; + default: + return ESP_ERR_NOT_FOUND; + } +} + +static IRAM_ATTR spi_flash_requirement_t zb_hpm_requirement_check( + uint32_t flash_id, uint32_t freq_mhz, int voltage_mv, int temperature +) { + (void)flash_id; + (void)voltage_mv; + (void)temperature; + return freq_mhz >= ZB_HPM_THRESHOLD_MHZ ? SPI_FLASH_HPM_WRITE_SR_NEEDED + : SPI_FLASH_HPM_UNNEEDED; +} + +static IRAM_ATTR void zb_hpm_enable(void) { + uint8_t status = bootloader_read_status_8b_rdsr3(); + bootloader_execute_flash_command(CMD_WRENVSR, 0, 0, 0); + bootloader_write_status_8b_wrsr3(status | ZB_HPF_BIT); + esp_rom_spiflash_wait_idle(&g_rom_flashchip); +} + +static IRAM_ATTR esp_err_t zb_hpm_check(void) { + return (bootloader_read_status_8b_rdsr3() & ZB_HPF_BIT) != 0 ? ESP_OK : ESP_FAIL; +} + +static DRAM_ATTR const char zb_hpm_method[] = "write sr3-bit5 (ZB25VQ128)"; +static DRAM_ATTR const char zb_hpm_fallback[] = "NULL"; + +// 覆盖 IDF 的 weak 表。最后一项 probe 为 NULL,是查找失败时的兜底。 +// Strong override of IDF's weak table. A NULL probe is the not-found fallback. +DRAM_ATTR const spi_flash_hpm_info_t spi_flash_hpm_enable_list[] = { + { + zb_hpm_method, + zb_hpm_probe, + zb_hpm_requirement_check, + zb_hpm_enable, + zb_hpm_check, + spi_flash_hpm_get_dummy_generic, + }, + { + zb_hpm_fallback, + NULL, + NULL, + NULL, + NULL, + spi_flash_hpm_get_dummy_generic, + }, +}; diff --git a/firmware/targets/mindreset_read_pico/components/read_pico/read_pico_init.c b/firmware/targets/mindreset_read_pico/components/read_pico/read_pico_init.c new file mode 100644 index 0000000..526652f --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/read_pico/read_pico_init.c @@ -0,0 +1,161 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * 板级开机装配:墨水屏、SD 探测、加速度计、PMU、触摸。 + * + * Board bring-up: EPD, SD probe, accelerometer, PMU, touch. + */ + +#include "read_pico_init.h" + +#include "epdiy.h" +#include "esp_log.h" +#include "read_pico_board.h" +#include "read_pico_epd_timing.h" +#include "e0470_epaper_waveform.h" +#include "read_pico_pmu.h" +#include "read_pico_sd.h" + +static const char* TAG = "read_pico_init"; + +#define READ_PICO_PCLK_MHZ 18 +#define READ_PICO_VCOM_MV 1290 +#define READ_PICO_ACCEL_INT1 GPIO_NUM_1 + +esp_err_t read_pico_init(read_pico_handle_t* hw) { + if (hw == NULL) return ESP_ERR_INVALID_ARG; + *hw = (read_pico_handle_t){ 0 }; + + ESP_LOGI(TAG, "%s", READ_PICO_PRODUCT_NAME); + ESP_LOGI(TAG, "Initializing 684x1216 EPD"); + // LUT 用 1K:LCD 路径下 epdiy 走 S3 向量实现,64K 里实际只用到 1K。 + // / 1K LUT: the LCD path uses the S3 vector impl; only 1K of the 64K is used. + epd_init(&epd_board_read_pico, &E0470_DISPLAY, EPD_LUT_1K); + // CD 走 FCA9555,必须等板级 I2C 起来再探测。 + // / CD is on the FCA9555; probe after board I2C is up. + read_pico_sd_start_probe(); + epd_set_rotation(EPD_ROT_INVERTED_PORTRAIT); + // 板级默认 12MHz,锁到实测稳定的 18MHz;消隐和 CKV 一起按新频率重解。 + // / Board default is 12 MHz; lock to the bench-stable 18 MHz and re-solve blanking/CKV. + read_pico_epd_set_pclk(READ_PICO_PCLK_MHZ); + esp_log_level_set("epdiy", ESP_LOG_WARN); + epd_set_vcom(READ_PICO_VCOM_MV); + + // 默认灰阶表是开机从原厂表裁出来的,必须先算好再挂给 highlevel; + // 差分刷新跳过整屏都在保持的前导相位。 + // / Default gray table is trimmed from the vendor table at boot; build it + // before highlevel. Diff refresh skips leading all-hold phases. + e0470_waveform_init(); + epd_set_leading_skip(true); + hw->hl = epd_hl_init(&E0470_WAVEFORM); + hw->framebuffer = epd_hl_get_framebuffer(&hw->hl); + if (hw->framebuffer == NULL) { + ESP_LOGE(TAG, "EPD framebuffer alloc failed"); + return ESP_ERR_NO_MEM; + } + + sc7a20h_config_t acc = { + .i2c_addr = SC7A20H_ADDR_DEFAULT, + .int1_gpio = READ_PICO_ACCEL_INT1, + .sensor = SC7A20H_CONFIG_IDLE, + }; + if (sc7a20h_init(read_pico_i2c_bus(), &acc, &hw->sensor) == ESP_OK) { + sc7a20h_sample_t sample; + if (read_pico_accel_read(hw->sensor, &sample) == ESP_OK) { + ESP_LOGI( + TAG, "SC7A20H: X=%dmg Y=%dmg Z=%dmg", + sample.x_mg, sample.y_mg, sample.z_mg + ); + } + sc7a20h_version(hw->sensor, &hw->sensor_whoami, &hw->sensor_version); + sc7a20h_power_down(hw->sensor); + hw->sensor_ready = true; + } else { + ESP_LOGW(TAG, "SC7A20H not ready"); + } + + read_pico_pmu_config_t pmu = READ_PICO_PMU_CONFIG_DEFAULT(); + hw->pmu_ready = read_pico_pmu_init(read_pico_i2c_bus(), &pmu) == ESP_OK; + if (!hw->pmu_ready) { + ESP_LOGW(TAG, "PMU / CW32 0x2A not ready"); + } + + cst836u_config_t tp = { + .i2c_addr = CST836U_ADDR_DEFAULT, + .scl_speed_hz = CST836U_I2C_HZ_DEFAULT, + .int_gpio = (gpio_num_t)READ_PICO_TP_INT_GPIO, + .rst_gpio = GPIO_NUM_NC, + .reset_fn = read_pico_touch_reset, + }; + if (cst836u_init(read_pico_i2c_bus(), &tp, &hw->touch) == ESP_OK) { + hw->touch_ready = true; + } else { + ESP_LOGW(TAG, "CST836U not ready"); + } + + return ESP_OK; +} + +void read_pico_deinit(read_pico_handle_t* hw) { + if (hw == NULL) return; + if (hw->touch != NULL) { + (void)cst836u_deinit(hw->touch); + hw->touch = NULL; + } + if (hw->pmu_ready) { + (void)read_pico_pmu_deinit(); + hw->pmu_ready = false; + } + if (hw->sensor != NULL) { + (void)sc7a20h_deinit(hw->sensor); + hw->sensor = NULL; + } + epd_deinit(); + *hw = (read_pico_handle_t){ 0 }; +} + +void read_pico_sensor_wake(sc7a20h_handle_t h) { + if (h && !sc7a20h_powered(h)) { + sc7a20h_power_up(h); + } +} + +void read_pico_sensor_sleep(sc7a20h_handle_t h) { + if (h && sc7a20h_powered(h)) { + sc7a20h_power_down(h); + } +} + +void read_pico_accel_to_device(sc7a20h_sample_t* sample) { + if (sample == NULL) return; + int16_t xc = sample->x_mg; + int16_t yc = sample->y_mg; + int16_t zc = sample->z_mg; + sample->x_mg = (int16_t)(-yc); + sample->y_mg = (int16_t)(-xc); + sample->z_mg = (int16_t)(-zc); + xc = sample->x_raw; + yc = sample->y_raw; + zc = sample->z_raw; + sample->x_raw = (int16_t)(-yc); + sample->y_raw = (int16_t)(-xc); + sample->z_raw = (int16_t)(-zc); +} + +uint8_t read_pico_accel_map_aoi(uint8_t src) { + uint8_t out = (uint8_t)(src & 0x40); + if (src & 0x01) out |= 0x08; + if (src & 0x02) out |= 0x04; + if (src & 0x04) out |= 0x02; + if (src & 0x08) out |= 0x01; + if (src & 0x10) out |= 0x20; + if (src & 0x20) out |= 0x10; + return out; +} + +esp_err_t read_pico_accel_read(sc7a20h_handle_t h, sc7a20h_sample_t* sample) { + esp_err_t err = sc7a20h_read(h, sample); + if (err == ESP_OK) read_pico_accel_to_device(sample); + return err; +} diff --git a/firmware/targets/mindreset_read_pico/components/read_pico/read_pico_sd.c b/firmware/targets/mindreset_read_pico/components/read_pico/read_pico_sd.c new file mode 100644 index 0000000..4935c80 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/read_pico/read_pico_sd.c @@ -0,0 +1,286 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * SDMMC 探测、挂载、格式化。 + * + * SDMMC probe, mount, and format. + */ + +#include "read_pico_sd.h" + +#include +#include +#include +#include + +#include "driver/sdmmc_host.h" +#include "esp_log.h" +#include "esp_vfs_fat.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "read_pico_board.h" +#include "sdmmc_cmd.h" + +#define SD_MOUNT_POINT "/sdcard" +#define SD_PIN_CLK GPIO_NUM_38 +#define SD_PIN_CMD GPIO_NUM_42 +#define SD_PIN_D0 GPIO_NUM_44 + +static const char* TAG = "sd_card"; +static sdmmc_card_t* card; +static int probe_state; +static portMUX_TYPE state_lock = portMUX_INITIALIZER_UNLOCKED; +static bool media_invalidated; +static read_pico_sd_info_t cached_info; + +// 失效只影响快照,消费者释放句柄前不得卸载卡。/ Invalidation changes snapshots only; consumers must close handles before unmount. +static void observe_media_locked(bool present) { + if (!present && (cached_info.mounted || probe_state == 1)) media_invalidated = true; + cached_info.present = present; + if (!present || media_invalidated) { + memset(&cached_info, 0, sizeof(cached_info)); + cached_info.present = present; + cached_info.error = present ? ESP_ERR_INVALID_STATE : ESP_ERR_NOT_FOUND; + } +} + +static void publish_info(const read_pico_sd_info_t* info) { + bool present = read_pico_sd_present(); + portENTER_CRITICAL(&state_lock); + cached_info = *info; + observe_media_locked(present); + probe_state = 2; + portEXIT_CRITICAL(&state_lock); +} + +static void fill_info(read_pico_sd_info_t* info, esp_err_t mount_err) { + memset(info, 0, sizeof(*info)); + info->present = read_pico_sd_present(); + info->error = mount_err; + info->needs_format = info->present && !info->mounted + && mount_err != ESP_OK + && mount_err != ESP_ERR_TIMEOUT + && mount_err != ESP_ERR_NOT_FOUND + && mount_err != ESP_ERR_NOT_FINISHED; + if (mount_err != ESP_OK || card == NULL) return; + + info->mounted = true; + info->needs_format = false; + strlcpy(info->name, card->cid.name, sizeof(info->name)); + info->capacity_bytes = (uint64_t)card->csd.capacity * card->csd.sector_size; + uint64_t total_bytes = 0; + info->error = esp_vfs_fat_info( + SD_MOUNT_POINT, &total_bytes, &info->free_bytes + ); +} + +static void ensure_font_dirs(void) { + if (mkdir("/sdcard/assets", 0777) != 0 && errno != EEXIST) { + ESP_LOGW(TAG, "mkdir assets: %d", errno); + } + if (mkdir("/sdcard/assets/fonts", 0777) != 0 && errno != EEXIST) { + ESP_LOGW(TAG, "mkdir assets/fonts: %d", errno); + } + if (mkdir("/sdcard/fonts", 0777) != 0 && errno != EEXIST) { + ESP_LOGW(TAG, "mkdir fonts: %d", errno); + } +} + +static esp_err_t mount_card(bool format_if_failed) { + if (card != NULL) return ESP_OK; + + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + // 1-bit 只能靠提时钟换带宽。20MHz 默认对随机小读太慢,40MHz 多数卡能稳住。 + // / 1-bit only buys bandwidth by raising the clock. 20 MHz is too slow for + // random small reads; 40 MHz holds on most cards. + host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; + + sdmmc_slot_config_t slot = SDMMC_SLOT_CONFIG_DEFAULT(); + slot.width = 1; + slot.clk = SD_PIN_CLK; + slot.cmd = SD_PIN_CMD; + slot.d0 = SD_PIN_D0; + slot.d1 = GPIO_NUM_NC; + slot.d2 = GPIO_NUM_NC; + slot.d3 = GPIO_NUM_NC; + slot.cd = GPIO_NUM_NC; + slot.wp = GPIO_NUM_NC; + slot.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP; + + const esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = format_if_failed, + .max_files = 8, + .allocation_unit_size = 16 * 1024, + }; + + esp_err_t err = esp_vfs_fat_sdmmc_mount( + SD_MOUNT_POINT, &host, &slot, &mount_config, &card + ); + // 上电后首次时钟协商常超时,等卡就绪再挂一次。 + // / First clock negotiate after power-up often times out; wait and remount. + if (err == ESP_ERR_TIMEOUT) { + card = NULL; + vTaskDelay(pdMS_TO_TICKS(200)); + err = esp_vfs_fat_sdmmc_mount( + SD_MOUNT_POINT, &host, &slot, &mount_config, &card + ); + } + if (err != ESP_OK) { + card = NULL; + ESP_LOGW(TAG, "Mount failed: %s", esp_err_to_name(err)); + } + return err; +} + +static void close_card(void) { + if (card != NULL) { + esp_err_t err = esp_vfs_fat_sdcard_unmount(SD_MOUNT_POINT, card); + if (err != ESP_OK) { + ESP_LOGW(TAG, "unmount %s", esp_err_to_name(err)); + } + card = NULL; + } +} + +static void probe_task(void* arg) { + (void)arg; + read_pico_sd_info_t info = { 0 }; + info.present = read_pico_sd_present(); + if (!info.present) { + info.error = ESP_ERR_NOT_FOUND; + publish_info(&info); + ESP_LOGI(TAG, "SD_CD absent, skip mount"); + vTaskDelete(NULL); + return; + } + + esp_err_t err = mount_card(false); + fill_info(&info, err); + if (err == ESP_OK) { + ESP_LOGI( + TAG, + "Mounted %s, capacity=%llu MB, free=%llu MB", + info.name, + (unsigned long long)(info.capacity_bytes / (1024 * 1024)), + (unsigned long long)(info.free_bytes / (1024 * 1024)) + ); + } + publish_info(&info); + if (info.needs_format) { + ESP_LOGW(TAG, "SD present but no FAT, ask user to format"); + } + vTaskDelete(NULL); +} + +esp_err_t read_pico_sd_start_probe(void) { + bool present = read_pico_sd_present(); + portENTER_CRITICAL(&state_lock); + observe_media_locked(present); + esp_err_t err = ESP_OK; + if (probe_state == 1) err = ESP_ERR_NOT_FINISHED; + else if (media_invalidated) err = cached_info.error; + else if (probe_state == 2) err = cached_info.mounted ? ESP_OK : + cached_info.error != ESP_OK ? cached_info.error : ESP_FAIL; + else if (!present) { + probe_state = 2; + err = ESP_ERR_NOT_FOUND; + } else { + memset(&cached_info, 0, sizeof(cached_info)); + cached_info.present = true; + cached_info.error = ESP_ERR_NOT_FINISHED; + probe_state = 1; + } + bool start = err == ESP_OK && probe_state == 1; + portEXIT_CRITICAL(&state_lock); + if (!start) return err; + BaseType_t created = xTaskCreate( + probe_task, "sd_probe", 4096, NULL, 5, NULL + ); + if (created != pdPASS) { + portENTER_CRITICAL(&state_lock); + probe_state = 0; + cached_info.error = ESP_ERR_NO_MEM; + portEXIT_CRITICAL(&state_lock); + return ESP_ERR_NO_MEM; + } + return ESP_ERR_NOT_FINISHED; +} + +esp_err_t read_pico_sd_get_info(read_pico_sd_info_t* info) { + if (info == NULL) return ESP_ERR_INVALID_ARG; + bool present = read_pico_sd_present(); + portENTER_CRITICAL(&state_lock); + observe_media_locked(present); + *info = cached_info; + esp_err_t err = !present || media_invalidated ? info->error : + probe_state == 0 ? ESP_ERR_INVALID_STATE : + probe_state == 1 ? ESP_ERR_NOT_FINISHED : info->error; + portEXIT_CRITICAL(&state_lock); + return err; +} + +// 驱动操作期间占用忙状态,不在临界区执行 I/O。/ Reserve busy state across driver I/O outside the critical section. +static bool begin_operation(void) { + portENTER_CRITICAL(&state_lock); + bool ready = probe_state != 1; + if (ready) probe_state = 1; + portEXIT_CRITICAL(&state_lock); + return ready; +} + +esp_err_t read_pico_sd_remount(void) { + if (!begin_operation()) return ESP_ERR_NOT_FINISHED; + close_card(); + portENTER_CRITICAL(&state_lock); + memset(&cached_info, 0, sizeof(cached_info)); + media_invalidated = false; + probe_state = 0; + portEXIT_CRITICAL(&state_lock); + return read_pico_sd_start_probe(); +} + +esp_err_t read_pico_sd_sync(void) { + if (!begin_operation()) return ESP_ERR_NOT_FINISHED; + close_card(); + read_pico_sd_info_t info = { .error = ESP_ERR_INVALID_STATE }; + publish_info(&info); + return ESP_OK; +} + +esp_err_t read_pico_sd_format(void) { + read_pico_sd_info_t current; + (void)read_pico_sd_get_info(¤t); + portENTER_CRITICAL(&state_lock); + bool invalid = media_invalidated; + bool busy = probe_state == 1; + if (!invalid && !busy) probe_state = 1; + portEXIT_CRITICAL(&state_lock); + if (invalid) return current.present ? ESP_ERR_INVALID_STATE : ESP_ERR_NOT_FOUND; + if (busy) return ESP_ERR_NOT_FINISHED; + if (!read_pico_sd_present()) { + read_pico_sd_info_t info = { .error = ESP_ERR_NOT_FOUND }; + publish_info(&info); + return ESP_ERR_NOT_FOUND; + } + + esp_err_t err = ESP_OK; + if (card == NULL) { + err = mount_card(true); + } else { + err = esp_vfs_fat_sdcard_format(SD_MOUNT_POINT, card); + if (err != ESP_OK) { + ESP_LOGW(TAG, "format %s", esp_err_to_name(err)); + } + } + + read_pico_sd_info_t info = { 0 }; + fill_info(&info, err); + if (err == ESP_OK) { + ensure_font_dirs(); + fill_info(&info, ESP_OK); + ESP_LOGI(TAG, "formatted %s", info.name); + } + publish_info(&info); + return err; +} diff --git a/firmware/targets/mindreset_read_pico/components/read_pico_pmu/CMakeLists.txt b/firmware/targets/mindreset_read_pico/components/read_pico_pmu/CMakeLists.txt new file mode 100644 index 0000000..78b192a --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/read_pico_pmu/CMakeLists.txt @@ -0,0 +1,6 @@ +idf_component_register( + SRCS "read_pico_pmu.c" + INCLUDE_DIRS "include" + REQUIRES esp_driver_i2c + PRIV_REQUIRES freertos esp_rom +) diff --git a/firmware/targets/mindreset_read_pico/components/read_pico_pmu/LICENSE b/firmware/targets/mindreset_read_pico/components/read_pico_pmu/LICENSE new file mode 100644 index 0000000..500992f --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/read_pico_pmu/LICENSE @@ -0,0 +1,17 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +Copyright 2026 mindreset + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/firmware/targets/mindreset_read_pico/components/read_pico_pmu/README.md b/firmware/targets/mindreset_read_pico/components/read_pico_pmu/README.md new file mode 100644 index 0000000..8e2015a --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/read_pico_pmu/README.md @@ -0,0 +1,54 @@ +# read_pico_pmu + +小纸 Pico 电源管理协议的主机端。板上 PMU 为 CW32L010,负责电池、主机使能轨、电源键、指示灯、RTC 与闹钟;ESP32-S3 通过 I2C 以带长度前缀和 CRC 校验的帧与之通信,而不是寄存器表。 + +Host side of the Read Pico power management protocol. The board's PMU is a CW32L010 that +owns the battery, the host enable rail, the power key, the indicator LED, an RTC and an +alarm; the ESP32-S3 talks to it over I2C with a length-prefixed, CRC-checked frame +format instead of a register map. + +组件名 / Registry name: `mindreset/read_pico_pmu` + +## 做什么 / What it does + +- `read_pico_pmu_refresh()`:读取身份、状态、诊断、一条排队事件、配置、时间和闹钟,即调试页显示的全部内容。/ Identity, status, diagnostics, one queued event, config, time and alarm — everything the debug page shows. +- `read_pico_pmu_poll()`:只读取状态、快照电池和待处理事件数,开销足够低,可在页面 tick 中调用。/ Status, quick counters and the pending-event count only, cheap enough to call from a page's tick. +- `read_pico_pmu_get()`:最新快照,各数据块附 `*_ok` 标志,页面可区分「未读到」与「读到零」。/ The latest snapshot, including per-block `*_ok` flags so a page can tell "not read" from "read as zero". +- `read_pico_pmu_cmd()` / `read_pico_pmu_action()`:原始命令与动作接口。/ Raw command and action entry points. +- 事件:`read_pico_pmu_drain_events()` 丢弃开机残留事件;`read_pico_pmu_take_key_short()` / `read_pico_pmu_take_key_wakeup()` 供锁屏使用。/ Events: `read_pico_pmu_drain_events()` to discard boot leftovers, `read_pico_pmu_take_key_short()` / `read_pico_pmu_take_key_wakeup()` for the lock screen. +- 电源交接:`read_pico_pmu_report_ready()`、`read_pico_pmu_report_sleep()`(等待 PMU 确认后再拉低 EN)、`read_pico_pmu_power_off()`。/ Power handoff: `read_pico_pmu_report_ready()`, `read_pico_pmu_report_sleep()` (waits for the PMU to acknowledge before it drops EN), `read_pico_pmu_power_off()`. +- 面板 VCOM:`read_pico_pmu_vcom_get()` 只读。每台设备的面板标定值在出厂时写入 PMU;主机开机读取一次用于配置驱动,不再写回。/ Panel VCOM: `read_pico_pmu_vcom_get()` is read-only. Each unit's panel calibration is bound on the PMU at manufacturing time; the host reads it once at boot to configure the display driver and never writes it back. + +## 协议 / Protocol + +`include/read_pico_pmu_protocol.h` 定义线格式:magic、帧类型、序号、命令码、载荷、CRC。命令按功能分组(状态、配置、事件、时间、LED、电源动作)。未知命令与版本不匹配以状态码返回,而不是超时。 + +`include/read_pico_pmu_protocol.h` is the wire format: magic, frame kind, sequence, +command code, payload, CRC. Commands are grouped by page (status, config, events, +time, LED, power actions). Unknown commands and version mismatches come back as +status codes rather than timeouts. + +## 文档 / Documentation + +`docs/` 内为 PMU 主机对接说明:`llms-full_*.md` 为正文,`pmu_protocol_*.h` 与 +`pmu_registers_*.json` 为配套的协议头与寄存器表,`read_pico_pmu_datasheet_v1.0.8_*.pdf` +为印刷版。中英各一份。 + +`docs/` holds the host integration guide for the PMU: `llms-full_*.md` is the text, +`pmu_protocol_*.h` and `pmu_registers_*.json` are the matching protocol header and +register map, and `read_pico_pmu_datasheet_v1.0.8_*.pdf` is the printed edition. +Chinese and English editions are provided. + +## 硬件 / Hardware + +| 项目 / Item | 规格 / Spec | +| --- | --- | +| 芯片 / Part | 跑小纸 Pico PMU 固件的 CW32L010 / CW32L010 running the Read Pico PMU firmware | +| 总线 / Bus | I2C,400 kHz,地址 `PMU_I2C_ADDR` / I2C, 400 kHz, address `PMU_I2C_ADDR` | +| 中断 / Interrupt | 经 FCA9555 扩展口(P0.2),也是浅睡唤醒源 / Routed through the FCA9555 expander (P0.2), also the light-sleep wake source | + +## 许可 / License + +Apache-2.0,见 [LICENSE](LICENSE)。 + +Apache-2.0, see [LICENSE](LICENSE). diff --git a/firmware/targets/mindreset_read_pico/components/read_pico_pmu/idf_component.yml b/firmware/targets/mindreset_read_pico/components/read_pico_pmu/idf_component.yml new file mode 100644 index 0000000..74c0597 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/read_pico_pmu/idf_component.yml @@ -0,0 +1,14 @@ +version: "1.1.0" +description: "Read Pico CW32L010 PMU host — I2C protocol, battery, sleep, alarm, power-off" +url: "https://github.com/mindreset/read_pico_pmu" +repository: "https://github.com/mindreset/read_pico_pmu.git" +issues: "https://github.com/mindreset/read_pico_pmu/issues" +license: "Apache-2.0" +tags: + - pmu + - cw32 + - i2c + - power + - alarm +dependencies: + idf: ">=5.3" diff --git a/firmware/targets/mindreset_read_pico/components/read_pico_pmu/include/read_pico_pmu.h b/firmware/targets/mindreset_read_pico/components/read_pico_pmu/include/read_pico_pmu.h new file mode 100644 index 0000000..211199d --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/read_pico_pmu/include/read_pico_pmu.h @@ -0,0 +1,196 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * 小纸 Pico CW32L010 PMU 主机侧:I2C 协议、电池、软睡、闹钟、关机。 + * 总线由调用方注入,不依赖板级 BSP。 + * + * Read Pico CW32L010 PMU host: I2C protocol, battery, soft sleep, alarm, + * power-off. The caller injects the bus; this file does not depend on the + * board BSP. + */ + +#pragma once + +#include +#include + +#include "driver/i2c_master.h" +#include "esp_err.h" +#include "read_pico_pmu_protocol.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + uint16_t low_mv; + uint16_t critical_mv; + uint16_t key_long_ms; + uint16_t key_force_event_ms; + uint16_t key_power_on_ms; + uint16_t key_force_off_ms; + uint16_t full_mv; + uint32_t generation; + uint16_t forced_off_mv; + uint16_t hysteresis_mv; + uint16_t key_debounce_ms; + uint16_t host_boot_timeout_ms; + uint16_t shutdown_timeout_ms; + uint16_t led_override_timeout_ms; + uint8_t wake_on_charge; + uint8_t key_raw_events; + uint8_t charge_led; + uint8_t low_soc_enable; + uint16_t low_soc_threshold; +} pmu_config_t; + +typedef struct { + bool present; + bool identity_ok; + bool status_ok; + bool diag_ok; + bool event_ok; + bool config_ok; + bool time_ok; + bool alarm_ok; + + uint8_t proto_major; + uint8_t proto_minor; + uint8_t fw_major; + uint8_t fw_minor; + uint8_t fw_patch; + uint8_t hw_rev; + uint16_t device_id; + uint8_t uid[PMU_CHIP_UID_LEN]; + bool uid_ok; + uint32_t boot_id; + uint32_t caps; + uint16_t build_number; + uint32_t build_hash; + + uint16_t generation; + uint8_t power_state; + uint8_t wake_reason; + uint32_t flags; + uint16_t battery_mv; + uint16_t battery_raw; + uint16_t soc_permille; + int16_t mcu_temp_centi; + uint8_t charge_state; + uint8_t key_state; + uint8_t led_state; + uint8_t host_state; + uint32_t fault_flags; + uint8_t pending_events; + uint8_t reset_reason; + uint8_t last_cmd_status; + uint32_t uptime_ms; + uint32_t config_generation; + uint16_t last_cmd_seq; + uint16_t last_event_id; + uint32_t last_action_reason; + + uint16_t diag_i2c_rx; + uint16_t diag_i2c_recov; + uint16_t diag_crc; + uint16_t diag_bad_len; + uint16_t diag_unknown; + uint16_t diag_overflow; + uint16_t diag_sleep; + uint16_t diag_host_rst; + uint16_t diag_adc; + + uint16_t qb_mv; + uint16_t qb_soc; + uint8_t qb_charge; + uint8_t qb_flags; + + pmu_event_t event; + pmu_config_t config; + uint32_t unix_sec; + uint8_t time_synced; + uint8_t alarm_mode; + uint32_t alarm_remain; + uint32_t alarm_target; + /// RTC_RAW:CR0 CR1 CR2 PSC DATE TIME SSCNT SYSCTRL_CR1 SYSCTRL_LSI + /// / RTC_RAW: CR0 CR1 CR2 PSC DATE TIME SSCNT SYSCTRL_CR1 SYSCTRL_LSI + uint32_t rtc_raw[9]; + bool rtc_raw_ok; + + uint16_t last_op_code; + uint16_t last_op_status; + esp_err_t last_err; + +} pmu_snapshot_t; + +#ifndef READ_PICO_PMU_I2C_HZ_DEFAULT +#define READ_PICO_PMU_I2C_HZ_DEFAULT 400000 +#endif + +typedef struct { + uint16_t i2c_addr; + uint32_t scl_speed_hz; +} read_pico_pmu_config_t; + +#define READ_PICO_PMU_CONFIG_DEFAULT() \ + { \ + .i2c_addr = PMU_I2C_ADDR, \ + .scl_speed_hz = READ_PICO_PMU_I2C_HZ_DEFAULT, \ + } + +esp_err_t read_pico_pmu_init(i2c_master_bus_handle_t bus_handle, const read_pico_pmu_config_t* config); +esp_err_t read_pico_pmu_deinit(void); +bool read_pico_pmu_ready(void); +const pmu_snapshot_t* read_pico_pmu_get(void); + +/// 读 IDENTITY / STATUS / DIAG / 事件 / 配置 / 时间 / 闹钟。 +/// / Read IDENTITY / STATUS / DIAG / event / config / time / alarm. +esp_err_t read_pico_pmu_refresh(void); +/// 只刷新 STATUS / QUICK / 事件计数,给页面轮询用。 +/// / Refresh STATUS / QUICK / event count only, for page polls. +esp_err_t read_pico_pmu_poll(void); + +esp_err_t read_pico_pmu_cmd(uint16_t code, const uint8_t* payload, uint8_t plen); +/// 读出厂绑定的面板 VCOM。valid 则 *out_mv 为 500..2500, +/// 未标定 / 旧固件 / 读失败一律 ESP_ERR_NOT_FOUND。 +/// / Read factory-bound panel VCOM. When valid, *out_mv is 500..2500. +/// Unset / old firmware / read failure all return ESP_ERR_NOT_FOUND. +esp_err_t read_pico_pmu_vcom_get(int* out_mv); +/// 出厂标定页写入。mv 须 500..2500 且整十。 +/// / Factory setup-page write. mv must be 500..2500 and a multiple of 10. +esp_err_t read_pico_pmu_vcom_set(int mv); +/// 读 CW32 出厂 UID(10 字节)。成功过一次会缓存在 snapshot 里。 +/// / Read the CW32 factory UID (10 bytes). Cached in the snapshot after first success. +esp_err_t read_pico_pmu_uid_get(uint8_t out[PMU_CHIP_UID_LEN]); +esp_err_t read_pico_pmu_event_ack(uint16_t event_id); +esp_err_t read_pico_pmu_action(uint8_t action, uint16_t delay_ms, uint16_t reason); +/// 抽干事件队列,不解释按键(开机丢掉上电/烧录残留)。 +/// / Drain the event queue without interpreting keys (drop boot/flash leftovers). +void read_pico_pmu_drain_events(void); +/// 抽干事件队列;若有电源键短按返回 true。 +/// / Drain the queue; true if a power-key short press was seen. +bool read_pico_pmu_take_key_short(void); +/// 抽干队列;按下或短按都算唤醒(浅睡常先收到 KEY_DOWN)。 +/// / Drain the queue; KEY_DOWN or SHORT both count as wake (light sleep often sees KEY_DOWN first). +bool read_pico_pmu_take_key_wakeup(void); +/// 保证 RUNNING 后发 HOST_SOFT_SLEEP,等到 STATUS 为 SOFT_SLEEP(随后 EN=0)。 +/// / After RUNNING, send HOST_SOFT_SLEEP and wait for STATUS SOFT_SLEEP (then EN=0). +esp_err_t read_pico_pmu_report_sleep(void); +/// 发 HOST_READY,等到 STATUS 为 RUNNING。 +/// / Send HOST_READY and wait for STATUS RUNNING. +esp_err_t read_pico_pmu_report_ready(void); +/// 协作关机:REQUEST_OFF → SHUTDOWN_READY,EN 保持低。 +/// / Cooperative power-off: REQUEST_OFF → SHUTDOWN_READY; EN stays low. +esp_err_t read_pico_pmu_power_off(void); + +const char* pmu_power_name(uint8_t state); +const char* pmu_charge_name(uint8_t state); +const char* pmu_event_name(uint8_t type); +const char* pmu_status_name(uint16_t status); +const char* pmu_led_name(uint8_t color); +const char* pmu_action_name(uint8_t action); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/mindreset_read_pico/components/read_pico_pmu/include/read_pico_pmu_protocol.h b/firmware/targets/mindreset_read_pico/components/read_pico_pmu/include/read_pico_pmu_protocol.h new file mode 100644 index 0000000..0bced52 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/read_pico_pmu/include/read_pico_pmu_protocol.h @@ -0,0 +1,280 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * 与 CW32 `shared/pmu_protocol.h` 保持一致。线格式,不是主机实现。 + * + * Must match CW32 `shared/pmu_protocol.h`. Wire format only. + */ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define PMU_I2C_ADDR 0x2A +#define PMU_PROTOCOL_MAJOR 1 +#define PMU_PROTOCOL_MINOR 1 +#define PMU_FRAME_SIZE 64 +#define PMU_FRAME_PAYLOAD_SIZE 44 +#define PMU_IDENTITY_SIZE 32 +#define PMU_STATUS_SIZE 64 +#define PMU_DIAGNOSTICS_SIZE 32 +#define PMU_EVENT_SIZE 16 +#define PMU_EVENT_FIFO_DEPTH 8 +#define PMU_RESULT_CACHE_DEPTH 4 + +#define PMU_REG_IDENTITY 0x00 +#define PMU_REG_STATUS 0x20 +#define PMU_REG_DIAGNOSTICS 0x60 +#define PMU_REG_COMMAND 0x80 +#define PMU_REG_RESPONSE 0x81 +#define PMU_REG_EVENT_PEEK 0x82 +#define PMU_REG_EVENT_ACK 0x83 +#define PMU_REG_EVENT_COUNT 0x84 +/// QUICK_BATTERY:8 字节轻量电池快照,免帧协议直接读。 +/// / 8-byte battery snapshot, no frame protocol. +/// [0..1] battery_mv u16 LE +/// [2..3] soc_permille u16 LE(0~1000,0xFFFF=未知 / unknown) +/// [4] charge_state u8(pmu_charge_state) +/// [5] flags u8(bit0=battery_valid bit1=soc_valid bit2=charging_active) +/// [6..7] crc16 u16 LE(CRC16-CCITT-FALSE,覆盖 [0..5] / covers [0..5]) +#define PMU_REG_QUICK_BATTERY 0x85 +#define PMU_QUICK_BATTERY_SIZE 8 + +#define PMU_FW_MAJOR 1 +#define PMU_FW_MINOR 0 +/// 与 CW32 固件 PMU_FW_PATCH 对齐。/ Must match CW32 firmware. +#define PMU_FW_PATCH 8 +#define PMU_HW_REVISION 1 +#define PMU_DEVICE_ID 0x3201U + +#define PMU_FRAME_MAGIC 0xA5U +#define PMU_FRAME_KIND_REQ 0U +#define PMU_FRAME_KIND_RESP 1U + +#define PMU_CAP_BATTERY_ADC (1u << 0) +#define PMU_CAP_CHARGE_STATUS (1u << 1) +#define PMU_CAP_KEY_EVENTS (1u << 2) +#define PMU_CAP_LED_CONTROL (1u << 3) +#define PMU_CAP_HOST_EN_CONTROL (1u << 4) +#define PMU_CAP_HOST_BOOT_CONTROL (1u << 5) + +#define PMU_STATUS_BATTERY_VALID (1u << 0) +#define PMU_STATUS_BATTERY_LOW (1u << 1) +#define PMU_STATUS_BATTERY_CRITICAL (1u << 2) +#define PMU_STATUS_CHARGING_ACTIVE (1u << 3) +#define PMU_STATUS_CHARGE_PIN_HIGH (1u << 4) +#define PMU_STATUS_KEY_PRESSED (1u << 5) +#define PMU_STATUS_HOST_EN_HIGH (1u << 6) +#define PMU_STATUS_HOST_BOOT_LOW (1u << 7) +#define PMU_STATUS_EVENT_PENDING (1u << 8) +#define PMU_STATUS_COMMAND_BUSY (1u << 9) +#define PMU_STATUS_CONFIG_VALID (1u << 10) +#define PMU_STATUS_CW_INT_ASSERTED (1u << 13) + +enum pmu_power_state { + PMU_PWR_OFF = 0, + PMU_PWR_POWERING_ON = 1, + PMU_PWR_BOOT_WAIT = 2, + PMU_PWR_RUNNING = 3, + PMU_PWR_SHUTDOWN_PENDING = 4, + PMU_PWR_RESETTING = 5, + PMU_PWR_DOWNLOAD_MODE = 6, + PMU_PWR_FAULT = 7, + PMU_PWR_SOFT_SLEEP = 8 +}; + +enum pmu_charge_state { + PMU_CHARGE_UNKNOWN = 0, + PMU_CHARGE_NOT_CHARGING = 1, + PMU_CHARGE_CHARGING = 2, + PMU_CHARGE_FULL_INFERRED = 3, + PMU_CHARGE_FAULT = 4 +}; + +enum pmu_command { + PMU_CMD_PING = 0x0001, + PMU_CMD_GET_EXTENDED_INFO = 0x0002, + PMU_CMD_HOST_READY = 0x0004, + /// payload: unix_seconds:u32 LE(UTC)。校准 CW 本地时间基准。 + /// / unix_seconds:u32 LE (UTC). Calibrates the CW local clock. + PMU_CMD_TIME_SYNC = 0x0005, + PMU_CMD_CLEAR_DIAGNOSTICS = 0x0006, + /// 响应 8B:unix_seconds:u32(0=未校准)+ millis:u16 + synced:u8 + rsvd:u8。 + /// / 8B: unix_seconds:u32 (0=unset) + millis:u16 + synced:u8 + rsvd:u8. + PMU_CMD_TIME_GET = 0x0007, + /// payload: mode:u8(0=关 1=单次 2=循环)+ seconds:u32 LE(10s..31天)。 + /// 到点推 ALARM_FIRED;主机关机则自动开机(wake_reason=6)。 + /// RTC 走时,CW 深睡不影响;CW 断电/复位后闹钟丢失。 + /// / mode:u8 (0=off 1=once 2=repeat) + seconds:u32 LE (10s..31d). + /// Fires ALARM_FIRED; powers the host on if it is off (wake_reason=6). + /// RTC keeps time through CW deep sleep; lost on CW power-cut/reset. + PMU_CMD_ALARM_SET = 0x0008, + /// 响应 10B:mode:u8 + rsvd:u8 + remaining_sec:u32 + target_unix:u32。 + /// / 10B: mode:u8 + rsvd:u8 + remaining_sec:u32 + target_unix:u32. + PMU_CMD_ALARM_GET = 0x0009, + /// 排障用。响应 36B:9×u32 LE,依次为 RTC_CR0、RTC_CR1、RTC_CR2、RTC_PSC、 + /// RTC_DATE、RTC_TIME、RTC_SSCNT、SYSCTRL_CR1、SYSCTRL_LSI。 + /// / Debug. 36B: 9×u32 LE of those raw registers. + PMU_CMD_RTC_RAW = 0x000A, + PMU_CMD_BATTERY_SAMPLE = 0x0100, + PMU_CMD_BATTERY_SET_PROFILE = 0x0101, + /// LED_SET: color:u8, brightness:u8, [fade_ms:u16 LE]。 + /// 2 字节默认淡入淡出;4 字节 fade_ms=0 立刻切。brightness 0=灭。 + /// / color:u8, brightness:u8, [fade_ms:u16 LE]. + /// 2-byte payload uses the default fade; 4-byte fade_ms=0 is instant. brightness 0=off. + PMU_CMD_LED_SET = 0x0200, + PMU_CMD_LED_OVERRIDE_CLEAR = 0x0203, + PMU_CMD_ACTION_PREPARE = 0x0300, + PMU_CMD_ACTION_COMMIT = 0x0301, + PMU_CMD_ACTION_CANCEL = 0x0302, + PMU_CMD_SHUTDOWN_READY = 0x0303, + PMU_CMD_HOST_SOFT_SLEEP = 0x0310, + PMU_CMD_HOST_REQUEST_OFF = 0x0311, + PMU_CMD_HOST_REQUEST_RESET = 0x0312, + PMU_CMD_EVENTS_CLEAR_ALL = 0x0313, + PMU_CMD_CONFIG_GET = 0x0500, + /// payload: enable:u8(0/1)。RAM 保存,CW 复位后恢复默认关闭。 + /// / enable:u8 (0/1). RAM only; CW reset restores the default off. + PMU_CMD_CONFIG_SET_WAKE_ON_CHARGE = 0x0501, + /// payload: enable:u8 [threshold_permille:u16 LE]。默认关。 + /// SOC 低于阈值且未充电时推送 BATTERY_SOC_LOW;HOST_READY 时若仍低会补发。 + /// / enable:u8 [threshold_permille:u16 LE]. Off by default. + /// Fires BATTERY_SOC_LOW when SOC is under the threshold and not charging; + /// HOST_READY re-sends if still low. + PMU_CMD_CONFIG_SET_LOW_SOC_NOTIFY = 0x0502, + /// payload: enable:u8。0=不上报 KEY_DOWN/KEY_UP(SHORT/LONG/FORCE_OFF 不受影响)。默认开。 + /// / enable:u8. 0 hides KEY_DOWN/KEY_UP. SHORT/LONG/FORCE_OFF stay. Default on. + PMU_CMD_CONFIG_SET_KEY_EVENTS = 0x0503, + /// payload: enable:u8。0=关闭充电呼吸灯与充满常亮。默认开。 + /// / enable:u8. 0 disables charge-breathing and full-on LED. Default on. + PMU_CMD_CONFIG_SET_CHARGE_LED = 0x0504, + PMU_CMD_CONFIG_FACTORY_DEFAULT = 0x0505, + /// 面板 VCOM(绝对值 mV)。未标定 GET 的 valid=0。 + /// GET 响应 4B:vcom_mv:u16 LE + valid:u8(1=500..2500)+ rsvd:u8。 + /// SET payload:mv:u16 LE,500..2500 且整十。只在开机标定页写一次。 + /// / Panel VCOM (absolute mV). GET valid=0 when unset. + /// GET 4B: vcom_mv:u16 LE + valid:u8 (1=500..2500) + rsvd:u8. + /// SET: mv:u16 LE, 500..2500 and a multiple of 10. Factory setup page only. + PMU_CMD_VCOM_GET = 0x0510, + PMU_CMD_VCOM_SET = 0x0511, + /// 公开只读。响应至少 10B:芯片出厂 UID[10],可当设备序列号。 + /// / Public read. At least 10B: factory UID[10], usable as a serial. + PMU_CMD_MAINT_GET_INFO = 0x05F2 +}; + +#define PMU_CHIP_UID_LEN 10 + +enum pmu_status_code { + PMU_STATUS_OK = 0x0000, + PMU_STATUS_ACCEPTED = 0x0001, + PMU_STATUS_BUSY = 0x0002, + PMU_STATUS_BAD_CRC = 0x0010, + PMU_STATUS_BAD_MAGIC = 0x0011, + PMU_STATUS_BAD_LENGTH = 0x0012, + PMU_STATUS_UNSUPPORTED_VERSION = 0x0013, + PMU_STATUS_UNKNOWN_COMMAND = 0x0014, + PMU_STATUS_INVALID_ARGUMENT = 0x0015, + PMU_STATUS_SEQUENCE_CONFLICT = 0x0016, + PMU_STATUS_STALE_SESSION = 0x0017, + PMU_STATUS_NOT_SUPPORTED = 0x0018, + PMU_STATUS_INVALID_STATE = 0x0020, + PMU_STATUS_NOT_ARMED = 0x0021, + PMU_STATUS_TOKEN_EXPIRED = 0x0022, + PMU_STATUS_PERMISSION_DENIED = 0x0023, + PMU_STATUS_CONFIG_INVALID = 0x0030, + PMU_STATUS_NVM_FAILURE = 0x0031, + PMU_STATUS_ADC_FAILURE = 0x0040, + PMU_STATUS_HOST_TIMEOUT = 0x0041, + PMU_STATUS_INTERNAL_ERROR = 0x00FF +}; + +enum pmu_action { + PMU_ACTION_HOST_LOGICAL_OFF = 1, + PMU_ACTION_HOST_HARD_RESET = 2, + PMU_ACTION_HOST_NORMAL_RESTART = 3, + PMU_ACTION_HOST_ENTER_DOWNLOAD_MODE = 4, + PMU_ACTION_HOST_EXIT_DOWNLOAD_MODE = 5, + PMU_ACTION_HOST_POWER_ON = 6, + PMU_ACTION_HOST_SOFT_SLEEP = 7 +}; + +enum pmu_event_type { + PMU_EVT_KEY_DOWN = 0x01, + PMU_EVT_KEY_UP = 0x02, + PMU_EVT_KEY_SHORT = 0x03, + PMU_EVT_KEY_LONG = 0x04, + PMU_EVT_KEY_FORCE_OFF = 0x05, + PMU_EVT_CHARGE_STATE_CHANGED = 0x10, + PMU_EVT_BATTERY_SAMPLE_READY = 0x11, + PMU_EVT_BATTERY_LOW = 0x12, + PMU_EVT_BATTERY_CRITICAL = 0x13, + ///< arg0=当前 soc_permille,arg1=阈值。/ arg0=soc_permille, arg1=threshold. + PMU_EVT_BATTERY_SOC_LOW = 0x14, + ///< RTC 闹钟到点。arg0=mode(1=单次已停 2=循环已重排)。/ Alarm. arg0=mode (1=once 2=repeat). + PMU_EVT_ALARM_FIRED = 0x15, + PMU_EVT_HOST_STARTED = 0x20, + PMU_EVT_HOST_READY = 0x21, + PMU_EVT_SHUTDOWN_REQUESTED = 0x22, + PMU_EVT_HOST_RESET_PERFORMED = 0x24, + PMU_EVT_COMMAND_COMPLETED = 0x30, + PMU_EVT_CONFIG_RECOVERED = 0x40, + PMU_EVT_CW_RESET = 0x41, + PMU_EVT_OVERFLOW = 0x7F +}; + +enum pmu_severity { + PMU_SEV_INFO = 0, + PMU_SEV_NOTICE = 1, + PMU_SEV_WARNING = 2, + PMU_SEV_CRITICAL = 3 +}; + +enum pmu_led_color { + PMU_LED_OFF = 0, + PMU_LED_RED = 1, + PMU_LED_WHITE = 2, + PMU_LED_RED_WHITE = 3 +}; + +typedef struct __attribute__((packed)) { + uint8_t magic; + uint8_t header_version; + uint8_t kind; + uint8_t flags; + uint8_t protocol_major; + uint8_t protocol_minor; + uint16_t sequence; + uint16_t code; + uint16_t status; + uint8_t payload_length; + uint8_t reserved; + uint32_t session_id; + uint8_t payload[44]; + uint16_t crc16; +} pmu_frame_t; + +typedef struct __attribute__((packed)) { + uint16_t event_id; + uint8_t type; + uint8_t severity; + uint32_t timestamp_ms; + uint32_t arg0; + uint16_t arg1; + uint16_t crc16; +} pmu_event_t; + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +_Static_assert(sizeof(pmu_frame_t) == 64, "pmu_frame_t size"); +_Static_assert(sizeof(pmu_event_t) == 16, "pmu_event_t size"); +#endif + +uint16_t pmu_crc16_ccitt_false(const uint8_t *data, uint16_t length); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/mindreset_read_pico/components/read_pico_pmu/read_pico_pmu.c b/firmware/targets/mindreset_read_pico/components/read_pico_pmu/read_pico_pmu.c new file mode 100644 index 0000000..49d7575 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/read_pico_pmu/read_pico_pmu.c @@ -0,0 +1,830 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * CW32 PMU 主机:64 字节带 CRC 的帧、序号恢复、超时后复位 I2C。 + * + * CW32 PMU host: 64-byte CRC frames, sequence recovery, I2C reset on timeout. + */ + +#include "read_pico_pmu.h" + +#include + +#include "driver/i2c_master.h" +#include "esp_log.h" +#include "esp_rom_sys.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#define TAG "pmu" + +static i2c_master_bus_handle_t s_bus; +static i2c_master_dev_handle_t s_dev; +static pmu_snapshot_t s_snap; +static uint16_t s_seq = 1; +static bool s_ready; +static uint8_t s_last_payload[PMU_FRAME_PAYLOAD_SIZE]; +static uint8_t s_last_plen; + +/* ---- 帧收发 / Frame I/O ---- */ +uint16_t pmu_crc16_ccitt_false(const uint8_t* data, uint16_t length) { + uint16_t crc = 0xFFFF; + while (length--) { + crc ^= (uint16_t)(*data++) << 8; + for (uint8_t i = 0; i < 8; ++i) { + crc = (crc & 0x8000) ? (uint16_t)((crc << 1) ^ 0x1021) : (uint16_t)(crc << 1); + } + } + return crc; +} + +static uint16_t rd16(const uint8_t* p) { + return (uint16_t)p[0] | ((uint16_t)p[1] << 8); +} + +static uint32_t rd32(const uint8_t* p) { + return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | + ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); +} + +static void wr16(uint8_t* p, uint16_t v) { + p[0] = (uint8_t)v; + p[1] = (uint8_t)(v >> 8); +} + +static void wr32(uint8_t* p, uint32_t v) { + p[0] = (uint8_t)v; + p[1] = (uint8_t)(v >> 8); + p[2] = (uint8_t)(v >> 16); + p[3] = (uint8_t)(v >> 24); +} + +static bool crc_ok(const uint8_t* data, size_t cover, uint16_t expect) { + return pmu_crc16_ccitt_false(data, (uint16_t)cover) == expect; +} + +// I2C 超时后复位总线,避免 SCL 被钳死。/ Reset the bus after I2C timeout so SCL is not held. +static void pmu_bus_recover(esp_err_t err) { + if (err != ESP_ERR_TIMEOUT) return; + if (s_bus != NULL) { + (void)i2c_master_bus_reset(s_bus); + } +} + +// CW32 写寄存器地址后 STOP/Repeated START 都会锁存 s_reg。 +// After a register write, both STOP and Repeated START latch s_reg on the CW32. +static esp_err_t pmu_read_reg(uint8_t reg, uint8_t* data, size_t len) { + esp_err_t err = i2c_master_transmit_receive(s_dev, ®, 1, data, len, 100); + if (err == ESP_OK) return ESP_OK; + pmu_bus_recover(err); + err = i2c_master_transmit(s_dev, ®, 1, 100); + if (err != ESP_OK) { + pmu_bus_recover(err); + return err; + } + esp_rom_delay_us(50); + err = i2c_master_receive(s_dev, data, len, 100); + if (err != ESP_OK) pmu_bus_recover(err); + return err; +} + +static esp_err_t pmu_write_reg(uint8_t reg, const uint8_t* data, size_t len) { + uint8_t buf[1 + PMU_FRAME_SIZE]; + if (len > PMU_FRAME_SIZE) return ESP_ERR_INVALID_SIZE; + buf[0] = reg; + if (len > 0 && data != NULL) memcpy(&buf[1], data, len); + esp_err_t err = i2c_master_transmit(s_dev, buf, 1 + len, 100); + if (err != ESP_OK) pmu_bus_recover(err); + return err; +} + +static bool parse_identity(const uint8_t* raw) { + if (raw[0] != 'P' || raw[1] != 'M' || raw[2] != 'U' || + !crc_ok(raw, 30, rd16(&raw[30]))) { + return false; + } + s_snap.proto_major = raw[4]; + s_snap.proto_minor = raw[5]; + s_snap.fw_major = raw[6]; + s_snap.fw_minor = raw[7]; + s_snap.fw_patch = raw[8]; + s_snap.hw_rev = raw[9]; + s_snap.device_id = rd16(&raw[10]); + s_snap.boot_id = rd32(&raw[12]); + s_snap.caps = rd32(&raw[16]); + s_snap.build_number = rd16(&raw[22]); + s_snap.build_hash = rd32(&raw[24]); + return true; +} + +// CRC 不对则不改快照:STATUS 影子更新时 I2C 会读到半新半旧。 +// Leave the snapshot alone on a bad CRC: STATUS updates can tear mid-read. +static bool parse_status(const uint8_t* raw) { + if (!crc_ok(raw, 62, rd16(&raw[62]))) return false; + s_snap.generation = rd16(&raw[0]); + s_snap.power_state = raw[2]; + s_snap.wake_reason = raw[3]; + s_snap.flags = rd32(&raw[4]); + s_snap.battery_mv = rd16(&raw[8]); + s_snap.battery_raw = rd16(&raw[10]); + s_snap.soc_permille = rd16(&raw[12]); + s_snap.mcu_temp_centi = (int16_t)rd16(&raw[14]); + s_snap.charge_state = raw[16]; + s_snap.key_state = raw[17]; + s_snap.led_state = raw[18]; + s_snap.host_state = raw[19]; + s_snap.fault_flags = rd32(&raw[20]); + s_snap.pending_events = raw[24]; + s_snap.reset_reason = raw[25]; + s_snap.last_cmd_status = raw[27]; + s_snap.uptime_ms = rd32(&raw[28]); + s_snap.config_generation = rd32(&raw[36]); + s_snap.last_cmd_seq = rd16(&raw[40]); + s_snap.last_event_id = rd16(&raw[42]); + s_snap.last_action_reason = rd32(&raw[44]); + return true; +} + +static void parse_diag(const uint8_t* raw) { + s_snap.diag_ok = crc_ok(raw, 30, rd16(&raw[30])); + s_snap.diag_i2c_rx = rd16(&raw[0]); + s_snap.diag_i2c_recov = rd16(&raw[2]); + s_snap.diag_crc = rd16(&raw[4]); + s_snap.diag_bad_len = rd16(&raw[6]); + s_snap.diag_unknown = rd16(&raw[8]); + s_snap.diag_overflow = rd16(&raw[12]); + s_snap.diag_sleep = rd16(&raw[14]); + s_snap.diag_host_rst = rd16(&raw[16]); + s_snap.diag_adc = rd16(&raw[20]); +} + +static void parse_quick(const uint8_t* raw) { + if (!crc_ok(raw, 6, rd16(&raw[6]))) return; + s_snap.qb_mv = rd16(&raw[0]); + s_snap.qb_soc = rd16(&raw[2]); + s_snap.qb_charge = raw[4]; + s_snap.qb_flags = raw[5]; +} + +static void parse_event(const uint8_t* raw) { + memcpy(&s_snap.event, raw, sizeof(s_snap.event)); + s_snap.event_ok = + s_snap.event.event_id != 0 && + crc_ok(raw, 14, s_snap.event.crc16); +} + +static void parse_config(const uint8_t* p) { + s_snap.config.low_mv = rd16(&p[0]); + s_snap.config.critical_mv = rd16(&p[2]); + s_snap.config.key_long_ms = rd16(&p[4]); + s_snap.config.key_force_event_ms = rd16(&p[6]); + s_snap.config.key_power_on_ms = rd16(&p[8]); + s_snap.config.key_force_off_ms = rd16(&p[10]); + s_snap.config.full_mv = rd16(&p[12]); + s_snap.config.generation = rd32(&p[14]); + s_snap.config.forced_off_mv = rd16(&p[18]); + s_snap.config.hysteresis_mv = rd16(&p[20]); + s_snap.config.key_debounce_ms = rd16(&p[22]); + s_snap.config.host_boot_timeout_ms = rd16(&p[24]); + s_snap.config.shutdown_timeout_ms = rd16(&p[26]); + s_snap.config.led_override_timeout_ms = rd16(&p[28]); + s_snap.config.wake_on_charge = p[30]; + s_snap.config.key_raw_events = p[31]; + s_snap.config.charge_led = p[32]; + s_snap.config.low_soc_enable = p[33]; + s_snap.config.low_soc_threshold = rd16(&p[34]); + s_snap.config_ok = true; +} + +// USB 只复位 ESP 时 boot_id 不变,本地 seq 从 last_command_sequence+1 起。 +// A USB reset of the ESP keeps boot_id; resume seq from last_command_sequence+1. +static void recover_seq(void) { + uint8_t ident[PMU_IDENTITY_SIZE]; + uint8_t status[PMU_STATUS_SIZE]; + if (pmu_read_reg(PMU_REG_IDENTITY, ident, sizeof(ident)) == ESP_OK) { + parse_identity(ident); + s_snap.present = true; + } + if (pmu_read_reg(PMU_REG_STATUS, status, sizeof(status)) == ESP_OK) { + parse_status(status); + } + uint32_t next = (uint32_t)s_snap.last_cmd_seq + 1U; + if (next == 0 || next > 0xFFFF) next = 1; + if (s_seq != (uint16_t)next) { + ESP_LOGI( + TAG, "seq recover %u -> %u last=%u pwr=%s", + (unsigned)s_seq, (unsigned)next, (unsigned)s_snap.last_cmd_seq, + pmu_power_name(s_snap.power_state) + ); + s_seq = (uint16_t)next; + } +} + +// 填请求帧并推进序号(跳过 0)。/ Fill a request and bump the sequence (skip 0). +static void fill_req(pmu_frame_t* req, uint16_t code, const uint8_t* payload, uint8_t plen) { + memset(req, 0, sizeof(*req)); + req->magic = PMU_FRAME_MAGIC; + req->header_version = 1; + req->kind = PMU_FRAME_KIND_REQ; + req->protocol_major = PMU_PROTOCOL_MAJOR; + req->protocol_minor = PMU_PROTOCOL_MINOR; + req->sequence = s_seq++; + if (s_seq == 0) s_seq = 1; + req->code = code; + req->payload_length = plen; + req->session_id = s_snap.boot_id; + if (payload != NULL && plen > 0) memcpy(req->payload, payload, plen); + req->crc16 = pmu_crc16_ccitt_false((const uint8_t*)req, 62); +} + +static esp_err_t read_response(pmu_frame_t* resp) { + uint8_t raw[PMU_FRAME_SIZE]; + esp_err_t err = pmu_read_reg(PMU_REG_RESPONSE, raw, sizeof(raw)); + if (err != ESP_OK) return err; + memcpy(resp, raw, sizeof(*resp)); + if (resp->magic != PMU_FRAME_MAGIC) return ESP_ERR_INVALID_RESPONSE; + if (!crc_ok(raw, 62, resp->crc16)) return ESP_ERR_INVALID_CRC; + return ESP_OK; +} + +// 等到对应序号的响应,最多 5 次。/ Wait for the matching sequence, up to 5 reads. +static esp_err_t read_response_for(uint16_t seq, pmu_frame_t* resp) { + for (int i = 0; i < 5; i++) { + esp_err_t err = read_response(resp); + if (err != ESP_OK) return err; + if (resp->sequence == seq) return ESP_OK; + vTaskDelay(pdMS_TO_TICKS(10)); + } + return ESP_ERR_INVALID_RESPONSE; +} + +// 发一帧命令。序号冲突或过期会话就恢复序号再试。 +// Send one command. Recover seq and retry on conflict or a stale session. +esp_err_t read_pico_pmu_cmd(uint16_t code, const uint8_t* payload, uint8_t plen) { + if (s_dev == NULL) return ESP_ERR_INVALID_STATE; + + pmu_frame_t req; + pmu_frame_t resp; + memset(&resp, 0, sizeof(resp)); + esp_err_t err = ESP_FAIL; + + for (int attempt = 0; attempt < 3; attempt++) { + if (attempt > 0) recover_seq(); + + fill_req(&req, code, payload, plen); + err = pmu_write_reg(PMU_REG_COMMAND, (const uint8_t*)&req, sizeof(req)); + s_snap.last_err = err; + s_snap.last_op_code = code; + if (err != ESP_OK) return err; + + vTaskDelay(pdMS_TO_TICKS(30)); + err = read_response_for(req.sequence, &resp); + if (err != ESP_OK) { + s_snap.last_err = err; + if (err == ESP_ERR_TIMEOUT) break; + continue; + } + if (resp.status == PMU_STATUS_STALE_SESSION || + resp.status == PMU_STATUS_SEQUENCE_CONFLICT) { + s_snap.last_op_status = resp.status; + ESP_LOGW( + TAG, "cmd 0x%04X %s seq=%u, retry", + (unsigned)code, pmu_status_name(resp.status), (unsigned)req.sequence + ); + continue; + } + break; + } + + s_snap.last_err = err; + if (err != ESP_OK) return err; + s_snap.last_op_status = resp.status; + s_last_plen = resp.payload_length; + if (s_last_plen > sizeof(s_last_payload)) s_last_plen = sizeof(s_last_payload); + memcpy(s_last_payload, resp.payload, s_last_plen); + + if (code == PMU_CMD_CONFIG_GET && resp.status == PMU_STATUS_OK) { + parse_config(resp.payload); + } else if (code == PMU_CMD_TIME_GET && resp.status == PMU_STATUS_OK) { + s_snap.unix_sec = rd32(&resp.payload[0]); + s_snap.time_synced = resp.payload[6]; + s_snap.time_ok = true; + } else if (code == PMU_CMD_ALARM_GET && resp.status == PMU_STATUS_OK) { + s_snap.alarm_mode = resp.payload[0]; + s_snap.alarm_remain = rd32(&resp.payload[2]); + s_snap.alarm_target = rd32(&resp.payload[6]); + s_snap.alarm_ok = true; + } else if (code == PMU_CMD_RTC_RAW && resp.status == PMU_STATUS_OK && resp.payload_length >= 36) { + for (int i = 0; i < 9; i++) s_snap.rtc_raw[i] = rd32(&resp.payload[i * 4]); + s_snap.rtc_raw_ok = true; + } else if (code == PMU_CMD_MAINT_GET_INFO && resp.status == PMU_STATUS_OK + && resp.payload_length >= PMU_CHIP_UID_LEN) { + memcpy(s_snap.uid, resp.payload, PMU_CHIP_UID_LEN); + s_snap.uid_ok = true; + } + return (resp.status == PMU_STATUS_OK || resp.status == PMU_STATUS_ACCEPTED) + ? ESP_OK : ESP_ERR_INVALID_RESPONSE; +} + +esp_err_t read_pico_pmu_vcom_get(int* out_mv) { + if (out_mv != NULL) *out_mv = 0; + if (!s_ready) return ESP_ERR_NOT_FOUND; + if (read_pico_pmu_cmd(PMU_CMD_VCOM_GET, NULL, 0) != ESP_OK) return ESP_ERR_NOT_FOUND; + if (s_last_plen < 4 || s_last_payload[2] == 0) return ESP_ERR_NOT_FOUND; + int mv = (int)rd16(s_last_payload); + if (mv < 500 || mv > 2500 || (mv % 10) != 0) return ESP_ERR_NOT_FOUND; + if (out_mv != NULL) *out_mv = mv; + return ESP_OK; +} + +esp_err_t read_pico_pmu_vcom_set(int mv) { + if (!s_ready) return ESP_ERR_NOT_FOUND; + if (mv < 500 || mv > 2500 || (mv % 10) != 0) return ESP_ERR_INVALID_ARG; + uint8_t buf[2]; + wr16(buf, (uint16_t)mv); + return read_pico_pmu_cmd(PMU_CMD_VCOM_SET, buf, sizeof(buf)); +} + +esp_err_t read_pico_pmu_uid_get(uint8_t out[PMU_CHIP_UID_LEN]) { + if (out != NULL) memset(out, 0, PMU_CHIP_UID_LEN); + if (!s_ready) return ESP_ERR_NOT_FOUND; + if (s_snap.uid_ok) { + if (out != NULL) memcpy(out, s_snap.uid, PMU_CHIP_UID_LEN); + return ESP_OK; + } + if (read_pico_pmu_cmd(PMU_CMD_MAINT_GET_INFO, NULL, 0) != ESP_OK) { + return ESP_ERR_NOT_FOUND; + } + if (!s_snap.uid_ok) return ESP_ERR_NOT_FOUND; + if (out != NULL) memcpy(out, s_snap.uid, PMU_CHIP_UID_LEN); + return ESP_OK; +} + +esp_err_t read_pico_pmu_event_ack(uint16_t event_id) { + uint8_t buf[4]; + wr16(&buf[0], event_id); + wr16(&buf[2], pmu_crc16_ccitt_false(buf, 2)); + esp_err_t err = pmu_write_reg(PMU_REG_EVENT_ACK, buf, sizeof(buf)); + s_snap.last_err = err; + s_snap.last_op_code = PMU_REG_EVENT_ACK; + if (err != ESP_OK) return err; + vTaskDelay(pdMS_TO_TICKS(10)); + return read_pico_pmu_poll(); +} + +void read_pico_pmu_drain_events(void) { + for (int i = 0; i < PMU_EVENT_FIFO_DEPTH; i++) { + if (read_pico_pmu_poll() != ESP_OK) break; + if (!s_snap.event_ok || s_snap.pending_events == 0) break; + uint16_t id = s_snap.event.event_id; + if (id == 0) break; + ESP_LOGI(TAG, "drain %s id=%u", pmu_event_name(s_snap.event.type), (unsigned)id); + if (read_pico_pmu_event_ack(id) != ESP_OK) break; + } +} + +bool read_pico_pmu_take_key_short(void) { + bool lock = false; + for (int i = 0; i < PMU_EVENT_FIFO_DEPTH; i++) { + if (read_pico_pmu_poll() != ESP_OK) break; + if (!s_snap.event_ok || s_snap.pending_events == 0) break; + uint8_t type = s_snap.event.type; + uint16_t id = s_snap.event.event_id; + if (type == PMU_EVT_KEY_SHORT) { + lock = true; + ESP_LOGI(TAG, "KEY_SHORT id=%u held=%u", (unsigned)id, (unsigned)s_snap.event.arg0); + } + if (id == 0) break; + if (read_pico_pmu_event_ack(id) != ESP_OK) break; + } + return lock; +} + +bool read_pico_pmu_take_key_wakeup(void) { + bool wake = false; + for (int i = 0; i < PMU_EVENT_FIFO_DEPTH; i++) { + if (read_pico_pmu_poll() != ESP_OK) break; + if (!s_snap.event_ok || s_snap.pending_events == 0) break; + uint8_t type = s_snap.event.type; + uint16_t id = s_snap.event.event_id; + if (type == PMU_EVT_KEY_DOWN || type == PMU_EVT_KEY_SHORT) { + wake = true; + ESP_LOGI(TAG, "key wake %s id=%u", pmu_event_name(type), (unsigned)id); + } + if (id == 0) break; + if (read_pico_pmu_event_ack(id) != ESP_OK) break; + } + return wake; +} + +// 命令 OK/ACCEPTED 只表示受理,以 STATUS power_state 为准。 +// OK/ACCEPTED means accepted; the real state is STATUS power_state. +static esp_err_t wait_power_state(uint8_t want, int timeout_ms) { + int steps = timeout_ms / 20; + if (steps < 1) steps = 1; + for (int i = 0; i < steps; i++) { + if (read_pico_pmu_poll() == ESP_OK && s_snap.power_state == want) { + return ESP_OK; + } + vTaskDelay(pdMS_TO_TICKS(20)); + } + ESP_LOGW( + TAG, "wait %s timeout, pwr=%s", + pmu_power_name(want), pmu_power_name(s_snap.power_state) + ); + return ESP_ERR_TIMEOUT; +} + +esp_err_t read_pico_pmu_report_ready(void) { + uint8_t reason = 0; + (void)read_pico_pmu_poll(); + if (s_snap.power_state == PMU_PWR_RUNNING) return ESP_OK; + + esp_err_t err = read_pico_pmu_cmd(PMU_CMD_HOST_READY, &reason, 1); + ESP_LOGI( + TAG, "HOST_READY %s st=%s pwr=%s", + esp_err_to_name(err), pmu_status_name(s_snap.last_op_status), + pmu_power_name(s_snap.power_state) + ); + if (err != ESP_OK) return err; + return wait_power_state(PMU_PWR_RUNNING, 1000); +} + +esp_err_t read_pico_pmu_report_sleep(void) { + (void)read_pico_pmu_poll(); + if (s_snap.power_state != PMU_PWR_RUNNING) { + ESP_LOGW( + TAG, "SOFT_SLEEP need RUNNING, now %s", + pmu_power_name(s_snap.power_state) + ); + esp_err_t err = read_pico_pmu_report_ready(); + if (err != ESP_OK) return err; + } + + esp_err_t err = read_pico_pmu_cmd(PMU_CMD_HOST_SOFT_SLEEP, NULL, 0); + ESP_LOGI( + TAG, "SOFT_SLEEP cmd %s st=%s pwr=%s", + esp_err_to_name(err), pmu_status_name(s_snap.last_op_status), + pmu_power_name(s_snap.power_state) + ); + if (err != ESP_OK) return err; + if (wait_power_state(PMU_PWR_SOFT_SLEEP, 1000) != ESP_OK) { + return ESP_ERR_TIMEOUT; + } + ESP_LOGI(TAG, "PMU latched SOFT_SLEEP"); + return ESP_OK; +} + +esp_err_t read_pico_pmu_power_off(void) { + (void)read_pico_pmu_poll(); + + uint8_t req[3] = { 0, 1, 0 }; + esp_err_t err = read_pico_pmu_cmd(PMU_CMD_HOST_REQUEST_OFF, req, sizeof(req)); + ESP_LOGI( + TAG, "REQUEST_OFF %s st=%s pwr=%s", + esp_err_to_name(err), pmu_status_name(s_snap.last_op_status), + pmu_power_name(s_snap.power_state) + ); + if (err != ESP_OK) { + err = read_pico_pmu_action(PMU_ACTION_HOST_LOGICAL_OFF, 0, 1); + ESP_LOGI(TAG, "LOGICAL_OFF fallback %s", esp_err_to_name(err)); + if (err != ESP_OK) return err; + } + + uint16_t event_id = 0; + bool pending = false; + for (int i = 0; i < 100; i++) { + if (read_pico_pmu_poll() != ESP_OK) { + vTaskDelay(pdMS_TO_TICKS(20)); + continue; + } + if (s_snap.power_state == PMU_PWR_SHUTDOWN_PENDING) pending = true; + if (s_snap.event_ok && s_snap.pending_events > 0) { + uint8_t type = s_snap.event.type; + uint16_t id = s_snap.event.event_id; + ESP_LOGI(TAG, "off wait %s id=%u pwr=%s", + pmu_event_name(type), (unsigned)id, + pmu_power_name(s_snap.power_state)); + if (type == PMU_EVT_SHUTDOWN_REQUESTED && id != 0) { + event_id = id; + pending = true; + (void)read_pico_pmu_event_ack(id); + break; + } + if (id != 0) (void)read_pico_pmu_event_ack(id); + continue; + } + if (pending && event_id == 0 && s_snap.last_event_id != 0) { + event_id = s_snap.last_event_id; + break; + } + vTaskDelay(pdMS_TO_TICKS(20)); + } + + if (!pending) { + ESP_LOGE( + TAG, "no SHUTDOWN_PENDING, pwr=%s ev=%u", + pmu_power_name(s_snap.power_state), (unsigned)s_snap.pending_events + ); + return ESP_ERR_TIMEOUT; + } + if (event_id == 0) event_id = s_snap.last_event_id; + if (event_id == 0) event_id = 1; + + uint8_t ready[2]; + wr16(ready, event_id); + err = read_pico_pmu_cmd(PMU_CMD_SHUTDOWN_READY, ready, sizeof(ready)); + ESP_LOGI( + TAG, "SHUTDOWN_READY id=%u %s st=%s", + (unsigned)event_id, esp_err_to_name(err), + pmu_status_name(s_snap.last_op_status) + ); + if (err != ESP_OK) return err; + + for (int i = 0; i < 25; i++) { + (void)read_pico_pmu_poll(); + if (s_snap.power_state == PMU_PWR_OFF) { + ESP_LOGI(TAG, "PMU EN off"); + return ESP_OK; + } + vTaskDelay(pdMS_TO_TICKS(20)); + } + ESP_LOGW(TAG, "EN still high, pwr=%s", pmu_power_name(s_snap.power_state)); + return ESP_ERR_TIMEOUT; +} + +esp_err_t read_pico_pmu_action(uint8_t action, uint16_t delay_ms, uint16_t reason) { + uint8_t prep[5]; + prep[0] = action; + wr16(&prep[1], delay_ms); + wr16(&prep[3], reason); + esp_err_t err = read_pico_pmu_cmd(PMU_CMD_ACTION_PREPARE, prep, sizeof(prep)); + if (err != ESP_OK || s_last_plen < 4) return err != ESP_OK ? err : ESP_ERR_INVALID_RESPONSE; + uint8_t commit[4]; + wr32(commit, rd32(s_last_payload)); + return read_pico_pmu_cmd(PMU_CMD_ACTION_COMMIT, commit, sizeof(commit)); +} + +// full:身份/状态/诊断/配置/时间/闹钟。否则只刷状态、快照电池和事件计数。 +// full: identity, status, diag, config, time, alarm. Else status, quick battery, event count. +static esp_err_t refresh_core(bool full) { + if (s_dev == NULL) return ESP_ERR_INVALID_STATE; + + uint8_t ident[PMU_IDENTITY_SIZE]; + uint8_t status[PMU_STATUS_SIZE]; + uint8_t diag[PMU_DIAGNOSTICS_SIZE]; + uint8_t quick[PMU_QUICK_BATTERY_SIZE]; + uint8_t ev[PMU_EVENT_SIZE]; + uint8_t count = 0; + + esp_err_t err = pmu_read_reg(PMU_REG_IDENTITY, ident, sizeof(ident)); + if (err != ESP_OK) { + s_snap.present = false; + s_snap.last_err = err; + return err; + } + s_snap.present = true; + bool id_ok = parse_identity(ident); + for (int i = 0; !id_ok && i < 2; i++) { + vTaskDelay(pdMS_TO_TICKS(20)); + if (pmu_read_reg(PMU_REG_IDENTITY, ident, sizeof(ident)) == ESP_OK) { + id_ok = parse_identity(ident); + } + } + s_snap.identity_ok = id_ok; + + bool st_ok = false; + for (int i = 0; i < 3 && !st_ok; i++) { + if (i) vTaskDelay(pdMS_TO_TICKS(20)); + if (pmu_read_reg(PMU_REG_STATUS, status, sizeof(status)) == ESP_OK) { + st_ok = parse_status(status); + } + } + s_snap.status_ok = st_ok; + + if (pmu_read_reg(PMU_REG_QUICK_BATTERY, quick, sizeof(quick)) == ESP_OK) { + parse_quick(quick); + } + if (pmu_read_reg(PMU_REG_EVENT_COUNT, &count, 1) == ESP_OK) { + s_snap.pending_events = count; + } + if (count != 0 && pmu_read_reg(PMU_REG_EVENT_PEEK, ev, sizeof(ev)) == ESP_OK) { + parse_event(ev); + } else { + s_snap.event_ok = false; + memset(&s_snap.event, 0, sizeof(s_snap.event)); + } + + if (full) { + if (pmu_read_reg(PMU_REG_DIAGNOSTICS, diag, sizeof(diag)) == ESP_OK) { + parse_diag(diag); + } + read_pico_pmu_cmd(PMU_CMD_CONFIG_GET, NULL, 0); + read_pico_pmu_cmd(PMU_CMD_TIME_GET, NULL, 0); + read_pico_pmu_cmd(PMU_CMD_ALARM_GET, NULL, 0); + } + s_snap.last_err = ESP_OK; + return ESP_OK; +} + +esp_err_t read_pico_pmu_refresh(void) { + return refresh_core(true); +} + +esp_err_t read_pico_pmu_poll(void) { + return refresh_core(false); +} + +esp_err_t read_pico_pmu_init(i2c_master_bus_handle_t bus_handle, const read_pico_pmu_config_t* config) { + memset(&s_snap, 0, sizeof(s_snap)); + if (config == NULL || bus_handle == NULL) return ESP_ERR_INVALID_ARG; + + uint16_t addr = config->i2c_addr ? config->i2c_addr : PMU_I2C_ADDR; + uint32_t hz = config->scl_speed_hz ? config->scl_speed_hz : READ_PICO_PMU_I2C_HZ_DEFAULT; + i2c_device_config_t cfg = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = addr, + .scl_speed_hz = hz, + }; + s_bus = bus_handle; + esp_err_t err = i2c_master_bus_add_device(s_bus, &cfg, &s_dev); + if (err != ESP_OK) { + ESP_LOGE(TAG, "add device 0x%02X failed: %s", addr, esp_err_to_name(err)); + return err; + } + + for (int i = 0; i < 5; ++i) { + uint8_t ident[PMU_IDENTITY_SIZE]; + err = pmu_read_reg(PMU_REG_IDENTITY, ident, sizeof(ident)); + if (err == ESP_OK) { + parse_identity(ident); + s_snap.present = true; + break; + } + vTaskDelay(pdMS_TO_TICKS(20)); + } + if (err != ESP_OK) { + ESP_LOGW(TAG, "CW32 not responding: %s", esp_err_to_name(err)); + s_snap.last_err = err; + (void)i2c_master_bus_rm_device(s_dev); + s_dev = NULL; + s_bus = NULL; + return err; + } + + recover_seq(); + + uint8_t reason = 0; + err = read_pico_pmu_cmd(PMU_CMD_HOST_READY, &reason, 1); + if (err != ESP_OK) { + ESP_LOGW(TAG, "HOST_READY failed: %s st=%s", + esp_err_to_name(s_snap.last_err), pmu_status_name(s_snap.last_op_status)); + } else if (wait_power_state(PMU_PWR_RUNNING, 1000) != ESP_OK) { + ESP_LOGW(TAG, "HOST_READY sent, PMU still %s", pmu_power_name(s_snap.power_state)); + } + + s_ready = s_snap.identity_ok; + ESP_LOGI( + TAG, "Ready: %s proto %u.%u fw %u.%u.%u boot=%08lX pwr=%s", + s_snap.identity_ok ? "PMU1" : "?", + s_snap.proto_major, s_snap.proto_minor, + s_snap.fw_major, s_snap.fw_minor, s_snap.fw_patch, + (unsigned long)s_snap.boot_id, + pmu_power_name(s_snap.power_state) + ); + if (s_snap.fw_major != PMU_FW_MAJOR || + s_snap.fw_minor != PMU_FW_MINOR || + s_snap.fw_patch < PMU_FW_PATCH) { + ESP_LOGW( + TAG, "CW32 fw %u.%u.%u older than host expect %u.%u.%u", + s_snap.fw_major, s_snap.fw_minor, s_snap.fw_patch, + PMU_FW_MAJOR, PMU_FW_MINOR, PMU_FW_PATCH + ); + } + if (s_snap.power_state != PMU_PWR_RUNNING) { + ESP_LOGW(TAG, "handshake not RUNNING after HOST_READY"); + } + if (s_ready) { + read_pico_pmu_drain_events(); + return ESP_OK; + } + (void)read_pico_pmu_deinit(); + return ESP_ERR_INVALID_RESPONSE; +} + +esp_err_t read_pico_pmu_deinit(void) { + if (s_dev != NULL) { + esp_err_t err = i2c_master_bus_rm_device(s_dev); + if (err != ESP_OK) return err; + } + s_bus = NULL; + s_dev = NULL; + s_ready = false; + memset(&s_snap, 0, sizeof(s_snap)); + return ESP_OK; +} + +bool read_pico_pmu_ready(void) { + return s_ready; +} + +const pmu_snapshot_t* read_pico_pmu_get(void) { + return &s_snap; +} + +const char* pmu_power_name(uint8_t state) { + switch (state) { + case PMU_PWR_OFF: return "OFF"; + case PMU_PWR_POWERING_ON: return "PWR_ON"; + case PMU_PWR_BOOT_WAIT: return "BOOT_WAIT"; + case PMU_PWR_RUNNING: return "RUNNING"; + case PMU_PWR_SHUTDOWN_PENDING: return "SHUT_PEND"; + case PMU_PWR_RESETTING: return "RESET"; + case PMU_PWR_DOWNLOAD_MODE: return "DOWNLOAD"; + case PMU_PWR_FAULT: return "FAULT"; + case PMU_PWR_SOFT_SLEEP: return "SOFT_SLEEP"; + default: return "?"; + } +} + +const char* pmu_charge_name(uint8_t state) { + switch (state) { + case PMU_CHARGE_UNKNOWN: return "UNK"; + case PMU_CHARGE_NOT_CHARGING: return "IDLE"; + case PMU_CHARGE_CHARGING: return "CHG"; + case PMU_CHARGE_FULL_INFERRED: return "FULL"; + case PMU_CHARGE_FAULT: return "FAULT"; + default: return "?"; + } +} + +const char* pmu_event_name(uint8_t type) { + switch (type) { + case PMU_EVT_KEY_DOWN: return "KEY_DOWN"; + case PMU_EVT_KEY_UP: return "KEY_UP"; + case PMU_EVT_KEY_SHORT: return "KEY_SHORT"; + case PMU_EVT_KEY_LONG: return "KEY_LONG"; + case PMU_EVT_KEY_FORCE_OFF: return "KEY_FORCE"; + case PMU_EVT_CHARGE_STATE_CHANGED: return "CHARGE"; + case PMU_EVT_BATTERY_SAMPLE_READY: return "BAT_RDY"; + case PMU_EVT_BATTERY_LOW: return "BAT_LOW"; + case PMU_EVT_BATTERY_CRITICAL: return "BAT_CRIT"; + case PMU_EVT_BATTERY_SOC_LOW: return "SOC_LOW"; + case PMU_EVT_ALARM_FIRED: return "ALARM"; + case PMU_EVT_HOST_STARTED: return "HOST_START"; + case PMU_EVT_HOST_READY: return "HOST_RDY"; + case PMU_EVT_SHUTDOWN_REQUESTED: return "SHUT_REQ"; + case PMU_EVT_HOST_RESET_PERFORMED: return "HOST_RST"; + case PMU_EVT_COMMAND_COMPLETED: return "CMD_DONE"; + case PMU_EVT_CONFIG_RECOVERED: return "CFG_RCV"; + case PMU_EVT_CW_RESET: return "CW_RST"; + case PMU_EVT_OVERFLOW: return "OVERFLOW"; + default: return "EVT"; + } +} + +const char* pmu_status_name(uint16_t status) { + switch (status) { + case PMU_STATUS_OK: return "OK"; + case PMU_STATUS_ACCEPTED: return "ACCEPTED"; + case PMU_STATUS_BUSY: return "BUSY"; + case PMU_STATUS_BAD_CRC: return "BAD_CRC"; + case PMU_STATUS_BAD_MAGIC: return "BAD_MAGIC"; + case PMU_STATUS_BAD_LENGTH: return "BAD_LEN"; + case PMU_STATUS_UNSUPPORTED_VERSION: return "BAD_VER"; + case PMU_STATUS_UNKNOWN_COMMAND: return "UNK_CMD"; + case PMU_STATUS_INVALID_ARGUMENT: return "BAD_ARG"; + case PMU_STATUS_SEQUENCE_CONFLICT: return "SEQ"; + case PMU_STATUS_STALE_SESSION: return "STALE"; + case PMU_STATUS_NOT_SUPPORTED: return "NOSUP"; + case PMU_STATUS_INVALID_STATE: return "BAD_ST"; + case PMU_STATUS_NOT_ARMED: return "NOARM"; + case PMU_STATUS_TOKEN_EXPIRED: return "EXPIRED"; + case PMU_STATUS_PERMISSION_DENIED: return "DENIED"; + default: return "ERR"; + } +} + +const char* pmu_led_name(uint8_t color) { + switch (color) { + case PMU_LED_OFF: return "OFF"; + case PMU_LED_RED: return "RED"; + case PMU_LED_WHITE: return "WHT"; + case PMU_LED_RED_WHITE: return "BOTH"; + default: return "?"; + } +} + +const char* pmu_action_name(uint8_t action) { + switch (action) { + case PMU_ACTION_HOST_LOGICAL_OFF: return "LOG_OFF"; + case PMU_ACTION_HOST_HARD_RESET: return "HRST"; + case PMU_ACTION_HOST_NORMAL_RESTART: return "RESTART"; + case PMU_ACTION_HOST_ENTER_DOWNLOAD_MODE: return "DL"; + case PMU_ACTION_HOST_EXIT_DOWNLOAD_MODE: return "EXIT_DL"; + case PMU_ACTION_HOST_POWER_ON: return "PWR_ON"; + case PMU_ACTION_HOST_SOFT_SLEEP: return "SLEEP"; + default: return "ACT"; + } +} diff --git a/firmware/targets/mindreset_read_pico/components/sc7a20h/CMakeLists.txt b/firmware/targets/mindreset_read_pico/components/sc7a20h/CMakeLists.txt new file mode 100644 index 0000000..71759e2 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/sc7a20h/CMakeLists.txt @@ -0,0 +1,6 @@ +idf_component_register( + SRCS "sc7a20h.c" + INCLUDE_DIRS "include" + REQUIRES esp_driver_gpio esp_driver_i2c + PRIV_REQUIRES esp_timer freertos esp_hw_support +) diff --git a/firmware/targets/mindreset_read_pico/components/sc7a20h/LICENSE b/firmware/targets/mindreset_read_pico/components/sc7a20h/LICENSE new file mode 100644 index 0000000..500992f --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/sc7a20h/LICENSE @@ -0,0 +1,17 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +Copyright 2026 mindreset + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/firmware/targets/mindreset_read_pico/components/sc7a20h/README.md b/firmware/targets/mindreset_read_pico/components/sc7a20h/README.md new file mode 100644 index 0000000..814eacc --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/sc7a20h/README.md @@ -0,0 +1,112 @@ +# sc7a20h + +ESP-IDF 下的 **SC7A20H** 三轴加速度计 C 驱动(士兰)。 + +ESP-IDF C driver for the **SC7A20H** 3-axis accelerometer (Silan Microelectronics). + +面向手持与墨水屏设备:读取加速度、配置拿起唤醒、经 INT1 浅睡或深睡唤醒。敲击、6D、FIFO、自检等实验功能在 `sc7a20h_lab.h`。 + +Intended for handheld / e-paper devices: read acceleration, arm pickup-to-wake, and enter light or deep sleep on INT1. Lab helpers (click, 6D, FIFO, self-test) are in `sc7a20h_lab.h`. + +组件名 / Registry name: `mindreset/sc7a20h` + +## 能力 / Features + +- Handle:调用方传入已有 `i2c_master` 总线、7 位地址、可选 INT1 脚。/ Handle-based API: pass an existing `i2c_master` bus, 7-bit address, and optional INT1 GPIO. +- 士兰上电流程:软复位 `0x68=0xA5`,WHO_AM_I `0x11` / VERSION,CTRL5.BOOT。/ Silan bring-up: soft reset `0x68=0xA5`, WHO_AM_I `0x11` / VERSION, CTRL5.BOOT. +- 默认低功耗 12.5 Hz。`power_down` 将 ODR 写为 0000(约 0.5 µA)。/ Low-power 12.5 Hz idle by default. `power_down` sets ODR=0000 (~0.5 µA). +- 拿起唤醒:AOI1 高事件 + HPIS1(滤除重力)+ INT1 锁存。/ Pickup-to-wake: AOI1 high-event + HPIS1 (gravity filtered) + INT1 latch. +- 浅睡 GPIO 唤醒(高有效)和 EXT1 深睡唤醒。/ Light-sleep GPIO wakeup (active high) and EXT1 deep-sleep wakeup. +- 实验室头文件:敲击、6D、自由落体、活动、FIFO、自检、寄存器转储。/ Lab header: click, 6D, freefall, activity, FIFO, self-test, register dump. + +## 硬件 / Hardware + +| 项目 / Item | 规格 / Spec | +|---|---| +| 芯片 / Part | SC7A20H(士兰)/ SC7A20H (Silan) | +| 总线 / Bus | I2C,400 kHz | +| 地址 / Address | `0x18`(SA0=GND)或 `0x19`(SA0=VCC,默认)/ `0x18` (SA0=GND) or `0x19` (SA0=VCC, default) | +| INT1 | 任意 GPIO;EXT1 深睡必须是 RTC 脚 / Any GPIO; RTC-capable pin required for EXT1 wakeup | +| 极性 / Polarity | 高有效(下拉 + 上升沿 / 高电平唤醒)/ Active high (pulldown + rising edge / high-level wakeup) | + +## 安装 / Install + +```yaml +dependencies: + mindreset/sc7a20h: "^1.0.0" +``` + +或:/ Or: + +```bash +idf.py add-dependency "mindreset/sc7a20h^1.0.0" +``` + +## 用法 / Quick start + +```c +#include "sc7a20h.h" + +sc7a20h_handle_t acc = NULL; +sc7a20h_hw_t hw = SC7A20H_HW_DEFAULT(); +hw.bus = i2c_bus; +hw.int1_gpio = GPIO_NUM_1; + +if (sc7a20h_create(&hw, &acc) == ESP_OK) { + sc7a20h_sample_t s; + sc7a20h_read(acc, &s); + sc7a20h_power_down(acc); // 界面不需要采样时掉电。/ Until the UI needs samples. +} + +// 配置拿起唤醒,然后浅睡。/ Pickup-to-wake, then light sleep. +sc7a20h_arm_pickup_wake(acc, NULL); +sc7a20h_config_light_sleep_wakeup(acc); +esp_light_sleep_start(); +sc7a20h_power_down(acc); +``` + +自定义动作阈值: + +Custom motion threshold: + +```c +sc7a20h_motion_cfg_t motion = SC7A20H_MOTION_DEFAULT(); +motion.ths_mg = 400; +motion.duration = 3; +motion.axis_mask = SC7A20H_AXIS_X | SC7A20H_AXIS_Y; +sc7a20h_arm_pickup_wake(acc, &motion); +``` + +深睡(INT1 变高后 MCU 复位): + +Deep sleep (MCU resets on INT1 high): + +```c +sc7a20h_arm_pickup_wake(acc, NULL); +sc7a20h_config_ext1_wakeup(acc); +esp_deep_sleep_start(); +``` + +## 接口分组 / API groups + +| 分组 / Group | 函数 / Functions | +|---|---| +| 生命周期 / Lifecycle | `sc7a20h_create`, `sc7a20h_del` | +| 电源 / Power | `sc7a20h_power_down`, `sc7a20h_power_up`, `sc7a20h_powered` | +| 读数 / Read | `sc7a20h_read` | +| 拿起 / Pickup | `sc7a20h_arm_pickup_wake`, `sc7a20h_config_light_sleep_wakeup`, `sc7a20h_config_ext1_wakeup` | +| 实验室 / Lab | [`sc7a20h_lab.h`](include/sc7a20h_lab.h) | + +详见 [`include/sc7a20h.h`](include/sc7a20h.h)。 + +See [`include/sc7a20h.h`](include/sc7a20h.h). + +## 示例 / Example + +[`examples/pickup_wake`](examples/pickup_wake) 初始化 I2C、读取若干样本、配置拿起唤醒并进入浅睡。引脚在 `idf.py menuconfig` 中配置。 + +[`examples/pickup_wake`](examples/pickup_wake) brings up I2C, reads a few samples, arms pickup-to-wake, and light-sleeps. Pins are in `idf.py menuconfig`. + +## 许可 / License + +Apache-2.0 diff --git a/firmware/targets/mindreset_read_pico/components/sc7a20h/examples/pickup_wake/CMakeLists.txt b/firmware/targets/mindreset_read_pico/components/sc7a20h/examples/pickup_wake/CMakeLists.txt new file mode 100644 index 0000000..c85e96a --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/sc7a20h/examples/pickup_wake/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 3.16) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(pickup_wake) diff --git a/firmware/targets/mindreset_read_pico/components/sc7a20h/examples/pickup_wake/main/CMakeLists.txt b/firmware/targets/mindreset_read_pico/components/sc7a20h/examples/pickup_wake/main/CMakeLists.txt new file mode 100644 index 0000000..a7e0929 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/sc7a20h/examples/pickup_wake/main/CMakeLists.txt @@ -0,0 +1 @@ +idf_component_register(SRCS "main.c" REQUIRES sc7a20h driver) diff --git a/firmware/targets/mindreset_read_pico/components/sc7a20h/examples/pickup_wake/main/Kconfig.projbuild b/firmware/targets/mindreset_read_pico/components/sc7a20h/examples/pickup_wake/main/Kconfig.projbuild new file mode 100644 index 0000000..75594cb --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/sc7a20h/examples/pickup_wake/main/Kconfig.projbuild @@ -0,0 +1,20 @@ +menu "SC7A20H pickup_wake" + + config SC7A20H_EXAMPLE_I2C_PORT + int "I2C port" + default 0 + range 0 1 + + config SC7A20H_EXAMPLE_SDA + int "SDA GPIO" + default 6 + + config SC7A20H_EXAMPLE_SCL + int "SCL GPIO" + default 7 + + config SC7A20H_EXAMPLE_INT1 + int "INT1 GPIO" + default 1 + +endmenu diff --git a/firmware/targets/mindreset_read_pico/components/sc7a20h/examples/pickup_wake/main/idf_component.yml b/firmware/targets/mindreset_read_pico/components/sc7a20h/examples/pickup_wake/main/idf_component.yml new file mode 100644 index 0000000..415d8c6 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/sc7a20h/examples/pickup_wake/main/idf_component.yml @@ -0,0 +1,4 @@ +dependencies: + mindreset/sc7a20h: + version: "^1.0.0" + override_path: "../../.." diff --git a/firmware/targets/mindreset_read_pico/components/sc7a20h/examples/pickup_wake/main/main.c b/firmware/targets/mindreset_read_pico/components/sc7a20h/examples/pickup_wake/main/main.c new file mode 100644 index 0000000..aee039b --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/sc7a20h/examples/pickup_wake/main/main.c @@ -0,0 +1,62 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * 拿起唤醒:初始化 I2C,读几组样,武装运动,浅睡。 + * 引脚:idf.py menuconfig → SC7A20H pickup_wake + * + * Pickup-to-wake: init I2C, read a few samples, arm motion, light sleep. + * Pins: idf.py menuconfig → SC7A20H pickup_wake + */ + +#include "sc7a20h.h" + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "esp_log.h" +#include "esp_sleep.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "sdkconfig.h" + +static const char* TAG = "pickup_wake"; + +void app_main(void) { + i2c_master_bus_handle_t bus; + i2c_master_bus_config_t bus_cfg = { + .i2c_port = (i2c_port_t)CONFIG_SC7A20H_EXAMPLE_I2C_PORT, + .sda_io_num = (gpio_num_t)CONFIG_SC7A20H_EXAMPLE_SDA, + .scl_io_num = (gpio_num_t)CONFIG_SC7A20H_EXAMPLE_SCL, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .flags.enable_internal_pullup = true, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&bus_cfg, &bus)); + + sc7a20h_handle_t acc = NULL; + sc7a20h_config_t config = SC7A20H_DEVICE_CONFIG_DEFAULT(); + config.int1_gpio = (gpio_num_t)CONFIG_SC7A20H_EXAMPLE_INT1; + if (sc7a20h_init(bus, &config, &acc) != ESP_OK) { + ESP_LOGE(TAG, "sensor init failed"); + return; + } + + for (int i = 0; i < 5; i++) { + sc7a20h_sample_t s; + if (sc7a20h_read(acc, &s) == ESP_OK) { + ESP_LOGI(TAG, "X=%dmg Y=%dmg Z=%dmg", s.x_mg, s.y_mg, s.z_mg); + } + vTaskDelay(pdMS_TO_TICKS(100)); + } + + ESP_ERROR_CHECK(sc7a20h_arm_pickup_wake(acc, NULL)); + ESP_ERROR_CHECK(sc7a20h_config_light_sleep_wakeup(acc)); + ESP_LOGI(TAG, "light sleep — pick up the device to wake"); + esp_light_sleep_start(); + + sc7a20h_ack_int(acc); + ESP_LOGI(TAG, "woke, INT1=%d", sc7a20h_int1_level(acc)); + sc7a20h_power_down(acc); + sc7a20h_deinit(acc); + ESP_ERROR_CHECK(i2c_del_master_bus(bus)); +} diff --git a/firmware/targets/mindreset_read_pico/components/sc7a20h/examples/pickup_wake/sdkconfig.defaults b/firmware/targets/mindreset_read_pico/components/sc7a20h/examples/pickup_wake/sdkconfig.defaults new file mode 100644 index 0000000..b01fdbe --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/sc7a20h/examples/pickup_wake/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_IDF_TARGET="esp32s3" diff --git a/firmware/targets/mindreset_read_pico/components/sc7a20h/idf_component.yml b/firmware/targets/mindreset_read_pico/components/sc7a20h/idf_component.yml new file mode 100644 index 0000000..596aa9a --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/sc7a20h/idf_component.yml @@ -0,0 +1,14 @@ +version: "1.0.0" +description: "SC7A20H 3-axis accelerometer (Silan) — motion, click, FIFO, and pickup-to-wake" +url: "https://github.com/mindreset/sc7a20h" +repository: "https://github.com/mindreset/sc7a20h.git" +issues: "https://github.com/mindreset/sc7a20h/issues" +license: "Apache-2.0" +tags: + - accelerometer + - sc7a20h + - i2c + - motion + - wakeup +dependencies: + idf: ">=5.3" diff --git a/firmware/targets/mindreset_read_pico/components/sc7a20h/include/sc7a20h.h b/firmware/targets/mindreset_read_pico/components/sc7a20h/include/sc7a20h.h new file mode 100644 index 0000000..cfac131 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/sc7a20h/include/sc7a20h.h @@ -0,0 +1,197 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * SC7A20H 三轴加速度计(士兰)。 + * 上电:软复位 0x68=0xA5,WHO_AM_I/VERSION,CTRL5.BOOT。 + * + * 产品 API:创建、读数、电源、拿起唤醒。 + * 实验室助手(点击、FIFO、6D、自检)在 sc7a20h_lab.h。 + * + * SC7A20H 3-axis accelerometer (Silan). + * Bring-up: soft reset 0x68=0xA5, WHO_AM_I/VERSION, CTRL5.BOOT. + * + * Product API: create, read, power, pickup-to-wake. + * Lab helpers (click, FIFO, 6D, self-test) live in sc7a20h_lab.h. + */ + +#pragma once + +#include +#include + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct sc7a20h_dev_t* sc7a20h_handle_t; + +#define SC7A20H_ADDR_DEFAULT 0x19 +#define SC7A20H_WHO_AM_I_VAL 0x11 +#define SC7A20H_VERSION_VAL 0x28 + +#define SC7A20H_AXIS_X 0x01 +#define SC7A20H_AXIS_Y 0x02 +#define SC7A20H_AXIS_Z 0x04 +#define SC7A20H_AXIS_XYZ 0x07 + +/// 手册 0001 是 1.56 Hz(台上约 3 Hz)。1.48 kHz 以上未测。 +/// / Datasheet 0001 is 1.56 Hz (~3 Hz on the bench). 1.48 kHz+ is untested. +typedef enum { + SC7A20H_ODR_12_5 = 0x02, + SC7A20H_ODR_25 = 0x03, + SC7A20H_ODR_50 = 0x04, + SC7A20H_ODR_100 = 0x05, + SC7A20H_ODR_200 = 0x06, + SC7A20H_ODR_400 = 0x07, + SC7A20H_ODR_800 = 0x08, +} sc7a20h_odr_t; + +typedef enum { + SC7A20H_FS_2G = 0, + SC7A20H_FS_4G = 1, + SC7A20H_FS_8G = 2, + SC7A20H_FS_16G = 3, +} sc7a20h_fs_t; + +/// CTRL1 LPen + CTRL0/CTRL4 HR:00 正常、01 LP、10 HR、11 增强。 +/// / CTRL1 LPen + CTRL0/CTRL4 HR: 00 normal, 01 LP, 10 HR, 11 enhanced. +typedef enum { + SC7A20H_MODE_NORMAL = 0, + SC7A20H_MODE_LP = 1, + SC7A20H_MODE_HR = 2, + SC7A20H_MODE_ENHANCED = 3, +} sc7a20h_mode_t; + +typedef enum { + SC7A20H_OSR_OFF = 0, + SC7A20H_OSR_2 = 1, + SC7A20H_OSR_4 = 2, + SC7A20H_OSR_8 = 3, + SC7A20H_OSR_16 = 4, + SC7A20H_OSR_32 = 5, +} sc7a20h_osr_t; + +typedef enum { + SC7A20H_DLPF_OFF = 0, + SC7A20H_DLPF_WEAK = 1, + SC7A20H_DLPF_MED = 2, + SC7A20H_DLPF_STRONG = 3, +} sc7a20h_dlpf_t; + +/// OFF 保留重力。越大高通截止越快。 +/// / OFF keeps gravity. Higher = faster high-pass cutoff. +typedef enum { + SC7A20H_HPF_OFF = 0, + SC7A20H_HPF_VERY_SLOW = 1, + SC7A20H_HPF_SLOW = 2, + SC7A20H_HPF_MED = 3, + SC7A20H_HPF_FAST = 4, +} sc7a20h_hpf_t; + +typedef struct { + sc7a20h_odr_t odr; + sc7a20h_fs_t fs; + sc7a20h_mode_t mode; + sc7a20h_osr_t osr; + sc7a20h_dlpf_t dlpf; + sc7a20h_hpf_t hpf; + uint8_t axis_mask; +} sc7a20h_sensor_config_t; + +#define SC7A20H_CONFIG_IDLE \ + { \ + .odr = SC7A20H_ODR_12_5, \ + .fs = SC7A20H_FS_2G, \ + .mode = SC7A20H_MODE_LP, \ + .osr = SC7A20H_OSR_OFF, \ + .dlpf = SC7A20H_DLPF_OFF, \ + .hpf = SC7A20H_HPF_OFF, \ + .axis_mask = SC7A20H_AXIS_XYZ, \ + } + +#define SC7A20H_SENSOR_CONFIG_DEFAULT SC7A20H_CONFIG_IDLE + +typedef struct { + uint8_t i2c_addr; + gpio_num_t int1_gpio; + sc7a20h_sensor_config_t sensor; +} sc7a20h_config_t; + +#define SC7A20H_DEVICE_CONFIG_DEFAULT() \ + { \ + .i2c_addr = SC7A20H_ADDR_DEFAULT, \ + .int1_gpio = GPIO_NUM_NC, \ + .sensor = SC7A20H_CONFIG_IDLE, \ + } + +typedef struct { + uint16_t ths_mg; + uint8_t duration; + uint8_t axis_mask; +} sc7a20h_motion_cfg_t; + +#define SC7A20H_MOTION_DEFAULT() \ + { \ + .ths_mg = 250, \ + .duration = 2, \ + .axis_mask = SC7A20H_AXIS_XYZ, \ + } + +typedef struct { + int16_t x_mg; + int16_t y_mg; + int16_t z_mg; + int16_t x_raw; + int16_t y_raw; + int16_t z_raw; + uint8_t status; +} sc7a20h_sample_t; + +esp_err_t sc7a20h_init(i2c_master_bus_handle_t bus_handle, const sc7a20h_config_t* config, + sc7a20h_handle_t* handle); +esp_err_t sc7a20h_deinit(sc7a20h_handle_t handle); + +esp_err_t sc7a20h_apply_config(sc7a20h_handle_t h, const sc7a20h_sensor_config_t* config); +const sc7a20h_sensor_config_t* sc7a20h_get_config(sc7a20h_handle_t h); + +/// CTRL1 ODR=0000,约 0.5 uA。上次配置留给 power_up。 +/// / CTRL1 ODR=0000, ~0.5 uA. Last config is kept for power_up. +esp_err_t sc7a20h_power_down(sc7a20h_handle_t h); +esp_err_t sc7a20h_power_up(sc7a20h_handle_t h); +bool sc7a20h_powered(sc7a20h_handle_t h); + +esp_err_t sc7a20h_read(sc7a20h_handle_t h, sc7a20h_sample_t* sample); + +/// AOI1 高事件 + HPIS1,桌上静止不会被重力误触发。 +/// / AOI1 high-event + HPIS1 so gravity does not trip a table-resting part. +esp_err_t sc7a20h_enable_motion(sc7a20h_handle_t h, const sc7a20h_motion_cfg_t* cfg); +/// LP 采样 + 运动 + INT1 路由 + 锁存。cfg 为 NULL 用 SC7A20H_MOTION_DEFAULT。 +/// / LP sample + motion + INT1 route + latch. NULL cfg uses SC7A20H_MOTION_DEFAULT. +esp_err_t sc7a20h_arm_pickup_wake(sc7a20h_handle_t h, const sc7a20h_motion_cfg_t* cfg); +/// 输入 + 下拉 + 高电平 GPIO 唤醒。先拆掉 INT1 ISR。 +/// / Input + pulldown + high-level GPIO wakeup. Detaches any INT1 ISR first. +esp_err_t sc7a20h_config_light_sleep_wakeup(sc7a20h_handle_t h); +esp_err_t sc7a20h_config_ext1_wakeup(sc7a20h_handle_t h); +/// 读锁存的 INT 源,好让 INT1 落下。 +/// / Read latched INT sources so INT1 can fall. +esp_err_t sc7a20h_ack_int(sc7a20h_handle_t h); + +esp_err_t sc7a20h_soft_reset(sc7a20h_handle_t h); +esp_err_t sc7a20h_version(sc7a20h_handle_t h, uint8_t* whoami, uint8_t* version); + +gpio_num_t sc7a20h_int1_gpio(sc7a20h_handle_t h); +int sc7a20h_int1_level(sc7a20h_handle_t h); + +uint16_t sc7a20h_odr_hz(sc7a20h_odr_t odr); +const char* sc7a20h_odr_name(sc7a20h_odr_t odr); +const char* sc7a20h_fs_name(sc7a20h_fs_t fs); +const char* sc7a20h_mode_name(sc7a20h_mode_t mode); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/mindreset_read_pico/components/sc7a20h/include/sc7a20h_lab.h b/firmware/targets/mindreset_read_pico/components/sc7a20h/include/sc7a20h_lab.h new file mode 100644 index 0000000..ec285c8 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/sc7a20h/include/sc7a20h_lab.h @@ -0,0 +1,194 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * 实验室 / 上电助手。产品代码只应包含 sc7a20h.h。 + * + * Lab / bring-up helpers. Product code should include sc7a20h.h only. + */ + +#pragma once + +#include "sc7a20h.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define SC7A20H_INT1_CLICK 0x80 +#define SC7A20H_INT1_AOI1 0x40 +#define SC7A20H_INT1_AOI2 0x20 +#define SC7A20H_INT1_DRDY 0x10 +#define SC7A20H_INT1_WTM 0x04 +#define SC7A20H_INT1_OVERRUN 0x02 + +#define SC7A20H_AOI_XL 0x01 +#define SC7A20H_AOI_XH 0x02 +#define SC7A20H_AOI_YL 0x04 +#define SC7A20H_AOI_YH 0x08 +#define SC7A20H_AOI_ZL 0x10 +#define SC7A20H_AOI_ZH 0x20 +#define SC7A20H_AOI_6D 0x40 +#define SC7A20H_AOI_AND 0x80 +#define SC7A20H_AOI_XYZ \ + (SC7A20H_AOI_XL | SC7A20H_AOI_XH | SC7A20H_AOI_YL | SC7A20H_AOI_YH \ + | SC7A20H_AOI_ZL | SC7A20H_AOI_ZH) + +#define SC7A20H_FIFO_MAX 32 +#define SC7A20H_DUMP_LEN 16 + +typedef enum { + SC7A20H_AOI1 = 0, + SC7A20H_AOI2 = 1, +} sc7a20h_aoi_t; + +/// 芯片坐标系的 6D 面。产品轴映射放在应用层。 +/// / Chip-frame 6D face. Map to product axes in the application. +typedef enum { + SC7A20H_ORIENT_UNKNOWN = 0, + SC7A20H_ORIENT_PX, + SC7A20H_ORIENT_NX, + SC7A20H_ORIENT_PY, + SC7A20H_ORIENT_NY, + SC7A20H_ORIENT_PZ, + SC7A20H_ORIENT_NZ, +} sc7a20h_orient_t; + +typedef enum { + SC7A20H_FIFO_BYPASS = 0, + SC7A20H_FIFO_MODE = 1, + SC7A20H_FIFO_STREAM = 2, + SC7A20H_FIFO_TRIGGER = 3, +} sc7a20h_fifo_mode_t; + +/// 点击阈值步进 0..7。命名值是常用预设。 +/// / Click threshold step 0..7. Named values are the useful presets. +typedef enum { + SC7A20H_CLICK_SOFT = 1, + SC7A20H_CLICK_NORMAL = 3, + SC7A20H_CLICK_FIRM = 5, + SC7A20H_CLICK_HARD = 7, +} sc7a20h_click_ths_t; + +#define SC7A20H_CLICK_THS_MAX 7 + +typedef struct { + uint8_t cfg; + uint16_t ths_mg; + uint8_t duration; +} sc7a20h_aoi_cfg_t; + +typedef struct { + uint8_t src; + bool ia; + bool zh; + bool zl; + bool yh; + bool yl; + bool xh; + bool xl; +} sc7a20h_aoi_src_t; + +typedef struct { + uint8_t src; + uint8_t count; +} sc7a20h_click_src_t; + +typedef struct { + sc7a20h_fifo_mode_t mode; + uint8_t watermark; + bool trigger_aoi2; + bool bit8; +} sc7a20h_fifo_cfg_t; + +typedef struct { + uint8_t raw; + bool wtm; + bool overrun; + bool empty; + uint8_t fss; +} sc7a20h_fifo_status_t; + +typedef struct { + int16_t dx_mg; + int16_t dy_mg; + int16_t dz_mg; + bool pass_x; + bool pass_y; + bool pass_z; +} sc7a20h_selftest_t; + +typedef struct { + int16_t mean_x; + int16_t mean_y; + int16_t mean_z; + int16_t pp_x; + int16_t pp_y; + int16_t pp_z; + uint16_t std_x; + uint16_t std_y; + uint16_t std_z; + uint16_t odr_x10; + uint16_t overrun; + uint16_t n; +} sc7a20h_stats_t; + +typedef struct { + uint8_t click_src; + uint8_t aoi1_src; + uint8_t aoi2_src; + uint8_t fifo_src; + uint8_t status; + uint32_t int1_count; + int int1_level; +} sc7a20h_events_t; + +esp_err_t sc7a20h_read_new(sc7a20h_handle_t h, sc7a20h_sample_t* sample); +esp_err_t sc7a20h_dump_regs(sc7a20h_handle_t h, uint8_t* out, size_t length); + +esp_err_t sc7a20h_click_config( + sc7a20h_handle_t h, uint8_t axis_mask, sc7a20h_click_ths_t ths +); +esp_err_t sc7a20h_click_read(sc7a20h_handle_t h, sc7a20h_click_src_t* src); + +esp_err_t sc7a20h_aoi_config(sc7a20h_handle_t h, sc7a20h_aoi_t unit, const sc7a20h_aoi_cfg_t* cfg); +esp_err_t sc7a20h_aoi_read(sc7a20h_handle_t h, sc7a20h_aoi_t unit, sc7a20h_aoi_src_t* src); +void sc7a20h_aoi_decode(uint8_t raw, sc7a20h_aoi_src_t* src); + +esp_err_t sc7a20h_orientation_arm( + sc7a20h_handle_t h, bool enable_4d, uint16_t ths_mg, uint8_t duration +); +sc7a20h_orient_t sc7a20h_orientation(const sc7a20h_aoi_src_t* src); +esp_err_t sc7a20h_freefall_config(sc7a20h_handle_t h, uint16_t ths_mg, uint8_t duration); +esp_err_t sc7a20h_activity_config(sc7a20h_handle_t h, uint16_t ths_mg, uint8_t duration); + +esp_err_t sc7a20h_fifo_config(sc7a20h_handle_t h, const sc7a20h_fifo_cfg_t* cfg); +esp_err_t sc7a20h_fifo_status(sc7a20h_handle_t h, sc7a20h_fifo_status_t* status); +esp_err_t sc7a20h_fifo_read( + sc7a20h_handle_t h, sc7a20h_sample_t* samples, size_t max, size_t* out_n +); +esp_err_t sc7a20h_fifo_clear(sc7a20h_handle_t h); + +esp_err_t sc7a20h_self_test(sc7a20h_handle_t h, sc7a20h_selftest_t* result); +esp_err_t sc7a20h_stats_collect(sc7a20h_handle_t h, uint16_t n, sc7a20h_stats_t* stats); + +esp_err_t sc7a20h_int_route(sc7a20h_handle_t h, uint8_t mask); +esp_err_t sc7a20h_int1_begin(sc7a20h_handle_t h); +uint32_t sc7a20h_int1_count(sc7a20h_handle_t h); +esp_err_t sc7a20h_read_events(sc7a20h_handle_t h, sc7a20h_events_t* events); + +uint8_t sc7a20h_ths_lsb_fs(sc7a20h_fs_t fs, uint16_t ths_mg); +uint16_t sc7a20h_ths_mg_fs(sc7a20h_fs_t fs, uint8_t lsb); +uint8_t sc7a20h_ths_lsb(sc7a20h_handle_t h, uint16_t ths_mg); +uint16_t sc7a20h_ths_mg(sc7a20h_handle_t h, uint8_t lsb); + +const char* sc7a20h_orient_name(sc7a20h_orient_t orient); +const char* sc7a20h_click_name(uint8_t count); +const char* sc7a20h_fifo_mode_name(sc7a20h_fifo_mode_t mode); +const char* sc7a20h_int_route_name(uint8_t mask); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/mindreset_read_pico/components/sc7a20h/sc7a20h.c b/firmware/targets/mindreset_read_pico/components/sc7a20h/sc7a20h.c new file mode 100644 index 0000000..0bb2071 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/sc7a20h/sc7a20h.c @@ -0,0 +1,1012 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * SC7A20H 主机:读数、电源、拿起唤醒,以及实验室助手。 + * + * SC7A20H host: read, power, pickup-to-wake, plus lab helpers. + */ + +#include "sc7a20h_lab.h" + +#include +#include + +#include "driver/gpio.h" +#include "driver/rtc_io.h" +#include "esp_attr.h" +#include "esp_log.h" +#include "esp_sleep.h" +#include "esp_timer.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#define SC7A20H_REG_WHO_AM_I 0x0F +#define SC7A20H_REG_CTRL0 0x1F +#define SC7A20H_REG_CTRL1 0x20 +#define SC7A20H_REG_CTRL2 0x21 +#define SC7A20H_REG_CTRL3 0x22 +#define SC7A20H_REG_CTRL4 0x23 +#define SC7A20H_REG_CTRL5 0x24 +#define SC7A20H_REG_CTRL6 0x25 +#define SC7A20H_REG_STATUS 0x27 +#define SC7A20H_REG_OUT_X_L 0x28 +#define SC7A20H_REG_FIFO_CTRL 0x2E +#define SC7A20H_REG_FIFO_SRC 0x2F +#define SC7A20H_REG_AOI1_CFG 0x30 +#define SC7A20H_REG_AOI1_SRC 0x31 +#define SC7A20H_REG_AOI2_CFG 0x34 +#define SC7A20H_REG_AOI2_SRC 0x35 +#define SC7A20H_REG_CLICK_CTRL 0x38 +#define SC7A20H_REG_CLICK_SRC 0x39 +#define SC7A20H_REG_CLICK_C1 0x3A +#define SC7A20H_REG_CLICK_C2 0x3B +#define SC7A20H_REG_CLICK_C3 0x3C +#define SC7A20H_REG_CLICK_C4 0x3D +#define SC7A20H_REG_OUT_X_NEW 0x61 +#define SC7A20H_REG_SOFT_RESET 0x68 +#define SC7A20H_REG_FIFO_DATA 0x69 +#define SC7A20H_REG_VERSION 0x70 + +#define CTRL5_FIFO_EN 0x40 +#define CTRL5_LIR_INT1 0x08 +#define CTRL5_D4D_INT1 0x04 +#define CTRL5_BOOT 0x80 +#define CTRL3_FIFO_8BIT 0x01 +#define CTRL2_FDS 0x08 +#define CTRL2_HP_RESET 0x04 +#define CTRL2_HPIS1 0x01 +#define CTRL2_HPIS2 0x02 +#define CTRL4_BDU 0x80 +#define CTRL4_ST0 0x02 + +#define I2C_TIMEOUT_MS 100 +#define I2C_HZ 400000 + +static const char* TAG = "sc7a20h"; + +struct sc7a20h_dev_t { + i2c_master_bus_handle_t bus; + i2c_master_dev_handle_t dev; + gpio_num_t int1_gpio; + sc7a20h_sensor_config_t cfg; + uint8_t mg_lsb; + uint8_t ctrl3; + uint8_t ctrl5; + bool fifo_8bit; + bool powered; + bool int1_isr; + volatile uint32_t int1_count; +}; + +static bool gpio_ok(gpio_num_t gpio) { + return gpio != GPIO_NUM_NC && GPIO_IS_VALID_GPIO(gpio); +} + +static void int1_detach_isr(sc7a20h_handle_t h) { + if (h->int1_isr && gpio_ok(h->int1_gpio)) { + gpio_isr_handler_remove(h->int1_gpio); + gpio_set_intr_type(h->int1_gpio, GPIO_INTR_DISABLE); + h->int1_isr = false; + } +} + +static esp_err_t int1_as_input(sc7a20h_handle_t h) { + if (!gpio_ok(h->int1_gpio)) return ESP_ERR_INVALID_ARG; + int1_detach_isr(h); + gpio_config_t io = { + .pin_bit_mask = 1ULL << h->int1_gpio, + .mode = GPIO_MODE_INPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_ENABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + return gpio_config(&io); +} + +static esp_err_t write_reg(sc7a20h_handle_t h, uint8_t reg, uint8_t value) { + const uint8_t data[] = { reg, value }; + return i2c_master_transmit(h->dev, data, sizeof(data), I2C_TIMEOUT_MS); +} + +static esp_err_t read_regs(sc7a20h_handle_t h, uint8_t reg, uint8_t* data, size_t length) { + if (length > 1) reg |= 0x80; + return i2c_master_transmit_receive(h->dev, ®, 1, data, length, I2C_TIMEOUT_MS); +} + +static bool odr_ok(sc7a20h_odr_t odr) { + return odr >= SC7A20H_ODR_12_5 && odr <= SC7A20H_ODR_800; +} + +static bool fs_ok(sc7a20h_fs_t fs) { + return fs <= SC7A20H_FS_16G; +} + +static bool cfg_ok(const sc7a20h_sensor_config_t* cfg) { + return cfg != NULL && odr_ok(cfg->odr) && fs_ok(cfg->fs) + && cfg->mode <= SC7A20H_MODE_ENHANCED && cfg->osr <= SC7A20H_OSR_32 + && cfg->dlpf <= SC7A20H_DLPF_STRONG && cfg->hpf <= SC7A20H_HPF_FAST + && (cfg->axis_mask & SC7A20H_AXIS_XYZ) != 0; +} + +static void fill_sample( + sc7a20h_handle_t h, sc7a20h_sample_t* sample, + int16_t x, int16_t y, int16_t z, uint8_t status +) { + sample->x_raw = (int16_t)(x >> 4); + sample->y_raw = (int16_t)(y >> 4); + sample->z_raw = (int16_t)(z >> 4); + sample->x_mg = (int16_t)(sample->x_raw * h->mg_lsb); + sample->y_mg = (int16_t)(sample->y_raw * h->mg_lsb); + sample->z_mg = (int16_t)(sample->z_raw * h->mg_lsb); + sample->status = status; +} + +void sc7a20h_aoi_decode(uint8_t raw, sc7a20h_aoi_src_t* src) { + if (src == NULL) return; + src->src = raw; + src->ia = (raw & 0x40) != 0; + src->zh = (raw & 0x20) != 0; + src->zl = (raw & 0x10) != 0; + src->yh = (raw & 0x08) != 0; + src->yl = (raw & 0x04) != 0; + src->xh = (raw & 0x02) != 0; + src->xl = (raw & 0x01) != 0; +} + +static esp_err_t write_ctrl5(sc7a20h_handle_t h, uint8_t value) { + esp_err_t err = write_reg(h, SC7A20H_REG_CTRL5, value); + if (err == ESP_OK) h->ctrl5 = value; + return err; +} + +uint16_t sc7a20h_odr_hz(sc7a20h_odr_t odr) { + static const uint16_t hz[] = { 0, 2, 13, 25, 50, 100, 200, 400, 800 }; + return odr <= SC7A20H_ODR_800 ? hz[odr] : 0; +} + +uint8_t sc7a20h_ths_lsb_fs(sc7a20h_fs_t fs, uint16_t ths_mg) { + uint16_t lsb = (uint16_t)(16u << (unsigned)fs); + uint16_t v = ths_mg / lsb; + return v > 127 ? 127 : (uint8_t)v; +} + +uint16_t sc7a20h_ths_mg_fs(sc7a20h_fs_t fs, uint8_t lsb) { + return (uint16_t)lsb * (uint16_t)(16u << (unsigned)fs); +} + +uint8_t sc7a20h_ths_lsb(sc7a20h_handle_t h, uint16_t ths_mg) { + if (h == NULL) return 0; + return sc7a20h_ths_lsb_fs(h->cfg.fs, ths_mg); +} + +uint16_t sc7a20h_ths_mg(sc7a20h_handle_t h, uint8_t lsb) { + if (h == NULL) return 0; + return sc7a20h_ths_mg_fs(h->cfg.fs, lsb); +} + +const sc7a20h_sensor_config_t* sc7a20h_get_config(sc7a20h_handle_t h) { + return h == NULL ? NULL : &h->cfg; +} + +esp_err_t sc7a20h_apply_config(sc7a20h_handle_t h, const sc7a20h_sensor_config_t* config) { + if (h == NULL || h->dev == NULL) return ESP_ERR_INVALID_STATE; + if (!cfg_ok(config)) return ESP_ERR_INVALID_ARG; + + const bool hr = + config->mode == SC7A20H_MODE_HR || config->mode == SC7A20H_MODE_ENHANCED; + const bool lpen = + config->mode == SC7A20H_MODE_LP || config->mode == SC7A20H_MODE_ENHANCED; + const uint8_t ctrl0 = + (uint8_t)(((uint8_t)config->osr << 4) | ((config->dlpf & 2) ? 0x02 : 0x00) | (hr ? 0x01 : 0x00)); + const uint8_t ctrl4 = (uint8_t)( + CTRL4_BDU | ((uint8_t)config->fs << 4) | ((config->dlpf & 1) << 3) + ); + const uint8_t ctrl1 = (uint8_t)( + ((uint8_t)config->odr << 4) | (lpen ? 0x08 : 0x00) | (config->axis_mask & 0x07) + ); + uint8_t ctrl2 = 0; + if (config->hpf > 0) { + ctrl2 = (uint8_t)(CTRL2_FDS | (((config->hpf - 1) & 3) << 4)); + } + + esp_err_t err; + if ((err = write_reg(h, SC7A20H_REG_CTRL0, ctrl0)) != ESP_OK) return err; + if ((err = write_reg(h, SC7A20H_REG_CTRL2, ctrl2)) != ESP_OK) return err; + if ((err = write_reg(h, SC7A20H_REG_CTRL4, ctrl4)) != ESP_OK) return err; + if ((err = write_reg(h, SC7A20H_REG_CTRL1, ctrl1)) != ESP_OK) return err; + + h->cfg = *config; + h->mg_lsb = (uint8_t)(1u << (uint8_t)config->fs); + h->powered = true; + vTaskDelay(pdMS_TO_TICKS(20)); + return ESP_OK; +} + +esp_err_t sc7a20h_power_down(sc7a20h_handle_t h) { + if (h == NULL || h->dev == NULL) return ESP_ERR_INVALID_STATE; + esp_err_t err = write_reg(h, SC7A20H_REG_CTRL1, 0x00); + if (err != ESP_OK) return err; + h->powered = false; + return ESP_OK; +} + +esp_err_t sc7a20h_power_up(sc7a20h_handle_t h) { + if (h == NULL) return ESP_ERR_INVALID_STATE; + return sc7a20h_apply_config(h, &h->cfg); +} + +bool sc7a20h_powered(sc7a20h_handle_t h) { + return h != NULL && h->powered; +} + +static esp_err_t bring_up(sc7a20h_handle_t h, const sc7a20h_sensor_config_t* cfg) { + // 士兰 12.34:0x68=0xA5 复位整个模拟/数字块。 + // / Silan 12.34: 0x68 = 0xA5 resets the whole analog/digital block. + esp_err_t err = write_reg(h, SC7A20H_REG_SOFT_RESET, 0xA5); + if (err != ESP_OK) return err; + vTaskDelay(pdMS_TO_TICKS(20)); + + uint8_t chip_id = 0; + uint8_t version = 0; + err = read_regs(h, SC7A20H_REG_WHO_AM_I, &chip_id, 1); + if (err != ESP_OK) { + ESP_LOGE(TAG, "WHO_AM_I read failed: %s", esp_err_to_name(err)); + return err; + } + read_regs(h, SC7A20H_REG_VERSION, &version, 1); + if (chip_id != SC7A20H_WHO_AM_I_VAL) { + ESP_LOGE(TAG, "Unexpected WHO_AM_I: 0x%02X, expected 0x11", chip_id); + return ESP_ERR_INVALID_RESPONSE; + } + + err = write_reg(h, SC7A20H_REG_CTRL5, CTRL5_BOOT); + if (err != ESP_OK) return err; + vTaskDelay(pdMS_TO_TICKS(10)); + + h->ctrl3 = 0; + h->ctrl5 = 0; + h->fifo_8bit = false; + err = sc7a20h_apply_config(h, cfg); + if (err != ESP_OK) return err; + + ESP_LOGI( + TAG, "Ready: WHO_AM_I=0x%02X VER=0x%02X mode=%s odr=%s lsb=%umg", + chip_id, version, sc7a20h_mode_name(cfg->mode), + sc7a20h_odr_name(cfg->odr), (unsigned)h->mg_lsb + ); + return ESP_OK; +} + +esp_err_t sc7a20h_init(i2c_master_bus_handle_t bus_handle, const sc7a20h_config_t* config, + sc7a20h_handle_t* handle) { + if (bus_handle == NULL || config == NULL || handle == NULL) return ESP_ERR_INVALID_ARG; + + sc7a20h_sensor_config_t cfg = config->sensor; + if (cfg.axis_mask == 0 && cfg.odr == 0) cfg = (sc7a20h_sensor_config_t)SC7A20H_CONFIG_IDLE; + if (!cfg_ok(&cfg)) return ESP_ERR_INVALID_ARG; + + sc7a20h_handle_t h = calloc(1, sizeof(*h)); + if (h == NULL) return ESP_ERR_NO_MEM; + + h->bus = bus_handle; + h->int1_gpio = config->int1_gpio; + h->cfg = cfg; + h->mg_lsb = 1; + + uint8_t addr = config->i2c_addr ? config->i2c_addr : SC7A20H_ADDR_DEFAULT; + i2c_device_config_t dev_cfg = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = addr, + .scl_speed_hz = I2C_HZ, + }; + esp_err_t err = i2c_master_bus_add_device(bus_handle, &dev_cfg, &h->dev); + if (err != ESP_OK) { + ESP_LOGE(TAG, "I2C device add failed: %s", esp_err_to_name(err)); + free(h); + return err; + } + + err = bring_up(h, &cfg); + if (err != ESP_OK) { + i2c_master_bus_rm_device(h->dev); + free(h); + return err; + } + + if (gpio_ok(h->int1_gpio)) { + err = int1_as_input(h); + if (err != ESP_OK) { + ESP_LOGW(TAG, "INT1 GPIO%d input failed: %s", (int)h->int1_gpio, esp_err_to_name(err)); + } + } + + *handle = h; + return ESP_OK; +} + +esp_err_t sc7a20h_deinit(sc7a20h_handle_t h) { + if (h == NULL) return ESP_OK; + int1_detach_isr(h); + if (h->dev) { + write_reg(h, SC7A20H_REG_CTRL1, 0x00); + i2c_master_bus_rm_device(h->dev); + } + free(h); + return ESP_OK; +} + +esp_err_t sc7a20h_read(sc7a20h_handle_t h, sc7a20h_sample_t* sample) { + if (h == NULL || sample == NULL) return ESP_ERR_INVALID_ARG; + if (h->dev == NULL) return ESP_ERR_INVALID_STATE; + + uint8_t status = 0; + esp_err_t err = read_regs(h, SC7A20H_REG_STATUS, &status, 1); + if (err != ESP_OK) return err; + + uint8_t data[6]; + err = read_regs(h, SC7A20H_REG_OUT_X_L, data, sizeof(data)); + if (err != ESP_OK) return err; + + int16_t x = (int16_t)((uint16_t)data[1] << 8 | data[0]); + int16_t y = (int16_t)((uint16_t)data[3] << 8 | data[2]); + int16_t z = (int16_t)((uint16_t)data[5] << 8 | data[4]); + fill_sample(h, sample, x, y, z, status); + return ESP_OK; +} + +esp_err_t sc7a20h_read_new(sc7a20h_handle_t h, sc7a20h_sample_t* sample) { + if (h == NULL || sample == NULL) return ESP_ERR_INVALID_ARG; + if (h->dev == NULL) return ESP_ERR_INVALID_STATE; + + uint8_t status = 0; + esp_err_t err = read_regs(h, SC7A20H_REG_STATUS, &status, 1); + if (err != ESP_OK) return err; + + uint8_t data[6]; + err = read_regs(h, SC7A20H_REG_OUT_X_NEW, data, sizeof(data)); + if (err != ESP_OK) return err; + + int16_t x = (int16_t)((uint16_t)data[0] << 8 | data[1]); + int16_t y = (int16_t)((uint16_t)data[2] << 8 | data[3]); + int16_t z = (int16_t)((uint16_t)data[4] << 8 | data[5]); + fill_sample(h, sample, x, y, z, status); + return ESP_OK; +} + +static uint8_t motion_ctrl2(sc7a20h_handle_t h, uint8_t hpis) { + uint8_t hpcf = h->cfg.hpf > 0 ? (uint8_t)((h->cfg.hpf - 1) & 3) : 1; + return (uint8_t)(CTRL2_FDS | (hpcf << 4) | hpis); +} + +// 手册 CTRL2.HP_RESET:先吸住当前重力当参考再松开。软复位后第一次开 HPIS 不复位,桌上也会 AOI。 +// / Datasheet CTRL2.HP_RESET: latch current gravity as the reference, then +// release. The first HPIS after soft reset without this trips AOI on the table. +static esp_err_t write_ctrl2_hpf(sc7a20h_handle_t h, uint8_t ctrl2) { + esp_err_t err = write_reg(h, SC7A20H_REG_CTRL2, (uint8_t)(ctrl2 | CTRL2_HP_RESET)); + if (err != ESP_OK) return err; + vTaskDelay(pdMS_TO_TICKS(160)); + return write_reg(h, SC7A20H_REG_CTRL2, ctrl2); +} + +esp_err_t sc7a20h_enable_motion(sc7a20h_handle_t h, const sc7a20h_motion_cfg_t* cfg) { + if (h == NULL || h->dev == NULL) return ESP_ERR_INVALID_STATE; + sc7a20h_motion_cfg_t def = SC7A20H_MOTION_DEFAULT(); + if (cfg == NULL) cfg = &def; + uint8_t axes = cfg->axis_mask & SC7A20H_AXIS_XYZ; + if (axes == 0) axes = SC7A20H_AXIS_XYZ; + + uint8_t int_cfg = 0; + if (axes & SC7A20H_AXIS_X) int_cfg |= SC7A20H_AOI_XH; + if (axes & SC7A20H_AXIS_Y) int_cfg |= SC7A20H_AOI_YH; + if (axes & SC7A20H_AXIS_Z) int_cfg |= SC7A20H_AOI_ZH; + sc7a20h_aoi_cfg_t aoi = { + .cfg = int_cfg, + .ths_mg = cfg->ths_mg, + .duration = cfg->duration, + }; + uint8_t ctrl2 = motion_ctrl2(h, CTRL2_HPIS1); + esp_err_t err = write_reg(h, SC7A20H_REG_CTRL2, (uint8_t)(ctrl2 | CTRL2_HP_RESET)); + if (err != ESP_OK) return err; + err = sc7a20h_aoi_config(h, SC7A20H_AOI1, &aoi); + if (err != ESP_OK) return err; + err = write_ctrl5(h, (uint8_t)((h->ctrl5 & CTRL5_FIFO_EN) | CTRL5_LIR_INT1)); + if (err != ESP_OK) return err; + vTaskDelay(pdMS_TO_TICKS(160)); + err = write_reg(h, SC7A20H_REG_CTRL2, ctrl2); + if (err != ESP_OK) return err; + return sc7a20h_ack_int(h); +} + +esp_err_t sc7a20h_arm_pickup_wake(sc7a20h_handle_t h, const sc7a20h_motion_cfg_t* cfg) { + if (h == NULL) return ESP_ERR_INVALID_STATE; + sc7a20h_sensor_config_t saved = h->cfg; + sc7a20h_sensor_config_t idle = SC7A20H_CONFIG_IDLE; + idle.fs = saved.fs; + idle.axis_mask = saved.axis_mask ? saved.axis_mask : SC7A20H_AXIS_XYZ; + // 先断开 INT1,等高通复位完成再接到脚上,避免上电第一次把重力锁到引脚。 + // / Detach INT1 until HPF reset finishes, so the first gravity latch does not + // stick on the pin. + esp_err_t err = sc7a20h_int_route(h, 0); + if (err != ESP_OK) return err; + err = sc7a20h_apply_config(h, &idle); + if (err != ESP_OK) return err; + h->cfg = saved; + err = sc7a20h_enable_motion(h, cfg); + if (err != ESP_OK) return err; + err = sc7a20h_ack_int(h); + if (err != ESP_OK) return err; + err = sc7a20h_int_route(h, SC7A20H_INT1_AOI1); + if (err != ESP_OK) return err; + return sc7a20h_ack_int(h); +} + +esp_err_t sc7a20h_config_light_sleep_wakeup(sc7a20h_handle_t h) { + if (h == NULL || !gpio_ok(h->int1_gpio)) return ESP_ERR_INVALID_ARG; + esp_err_t err = int1_as_input(h); + if (err != ESP_OK) return err; + err = gpio_wakeup_enable(h->int1_gpio, GPIO_INTR_HIGH_LEVEL); + if (err != ESP_OK) return err; + return esp_sleep_enable_gpio_wakeup(); +} + +esp_err_t sc7a20h_ack_int(sc7a20h_handle_t h) { + if (h == NULL || h->dev == NULL) return ESP_ERR_INVALID_STATE; + uint8_t discard = 0; + esp_err_t err; + if ((err = read_regs(h, SC7A20H_REG_CLICK_SRC, &discard, 1)) != ESP_OK) return err; + if ((err = read_regs(h, SC7A20H_REG_AOI1_SRC, &discard, 1)) != ESP_OK) return err; + if ((err = read_regs(h, SC7A20H_REG_AOI2_SRC, &discard, 1)) != ESP_OK) return err; + return ESP_OK; +} + +esp_err_t sc7a20h_config_ext1_wakeup(sc7a20h_handle_t h) { + if (h == NULL || !gpio_ok(h->int1_gpio)) return ESP_ERR_INVALID_ARG; + if (!rtc_gpio_is_valid_gpio(h->int1_gpio)) return ESP_ERR_NOT_SUPPORTED; + esp_err_t err = rtc_gpio_pulldown_en(h->int1_gpio); + if (err != ESP_OK) return err; + err = rtc_gpio_pullup_dis(h->int1_gpio); + if (err != ESP_OK) return err; + return esp_sleep_enable_ext1_wakeup_io( + 1ULL << h->int1_gpio, ESP_EXT1_WAKEUP_ANY_HIGH + ); +} + +esp_err_t sc7a20h_soft_reset(sc7a20h_handle_t h) { + if (h == NULL || h->dev == NULL) return ESP_ERR_INVALID_STATE; + return bring_up(h, &h->cfg); +} + +esp_err_t sc7a20h_version(sc7a20h_handle_t h, uint8_t* whoami, uint8_t* version) { + if (h == NULL || h->dev == NULL) return ESP_ERR_INVALID_STATE; + uint8_t id = 0; + uint8_t ver = 0; + esp_err_t err = read_regs(h, SC7A20H_REG_WHO_AM_I, &id, 1); + if (err != ESP_OK) return err; + err = read_regs(h, SC7A20H_REG_VERSION, &ver, 1); + if (err != ESP_OK) return err; + if (whoami) *whoami = id; + if (version) *version = ver; + return ESP_OK; +} + +esp_err_t sc7a20h_dump_regs(sc7a20h_handle_t h, uint8_t* out, size_t length) { + if (h == NULL || out == NULL || length == 0) return ESP_ERR_INVALID_ARG; + if (h->dev == NULL) return ESP_ERR_INVALID_STATE; + static const uint8_t addrs[SC7A20H_DUMP_LEN] = { + 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x27, + 0x2E, 0x2F, 0x30, 0x31, 0x38, 0x39, 0x0F, 0x70, + }; + size_t n = length < SC7A20H_DUMP_LEN ? length : SC7A20H_DUMP_LEN; + for (size_t i = 0; i < n; i++) { + esp_err_t err = read_regs(h, addrs[i], &out[i], 1); + if (err != ESP_OK) return err; + } + return ESP_OK; +} + +esp_err_t sc7a20h_click_config( + sc7a20h_handle_t h, uint8_t axis_mask, sc7a20h_click_ths_t ths +) { + if (h == NULL || h->dev == NULL) return ESP_ERR_INVALID_STATE; + uint8_t ths_step = (uint8_t)ths; + if (ths_step > SC7A20H_CLICK_THS_MAX) ths_step = SC7A20H_CLICK_THS_MAX; + // 38h:B2=X、B1=Y、B0=Z,和 AXIS_X=bit0 的顺序相反。B3=LIR,读 SRC 才清。 + // / 38h: B2=X, B1=Y, B0=Z (opposite of AXIS_X=bit0). B3=LIR; clear by reading SRC. + uint8_t en = 0; + if (axis_mask & SC7A20H_AXIS_X) en |= 0x04; + if (axis_mask & SC7A20H_AXIS_Y) en |= 0x02; + if (axis_mask & SC7A20H_AXIS_Z) en |= 0x01; + const uint8_t ctrl = (uint8_t)(0x08 | en); + // 3Ah:PRE_QT=00(按 200Hz)、PRE_NTH=14LSB 手持也算平静、SCTH1=ths。 + // / 3Ah: PRE_QT=00 (at 200 Hz), PRE_NTH=14 LSB so handheld still counts quiet, SCTH1=ths. + const uint8_t c1 = (uint8_t)((0 << 6) | (5 << 3) | (ths_step & 7)); + // 3Bh:QT_MT=4/200Hz,SCTH2 略高于 SCTH1,脉宽上限 8/ODR。 + // / 3Bh: QT_MT=4/200 Hz, SCTH2 a step above SCTH1, pulse-width cap 8/ODR. + const uint8_t scth2 = ths_step > 6 ? 7 : (uint8_t)(ths_step + 1); + const uint8_t c2 = (uint8_t)((1 << 6) | (scth2 << 3) | 2); + const uint8_t c3 = (uint8_t)((1 << 3) | 3); + const uint8_t c4 = 0x53; + esp_err_t err; + if ((err = write_reg(h, SC7A20H_REG_CLICK_CTRL, ctrl)) != ESP_OK) return err; + if ((err = write_reg(h, SC7A20H_REG_CLICK_C1, c1)) != ESP_OK) return err; + if ((err = write_reg(h, SC7A20H_REG_CLICK_C2, c2)) != ESP_OK) return err; + if ((err = write_reg(h, SC7A20H_REG_CLICK_C3, c3)) != ESP_OK) return err; + return write_reg(h, SC7A20H_REG_CLICK_C4, c4); +} + +esp_err_t sc7a20h_click_read(sc7a20h_handle_t h, sc7a20h_click_src_t* src) { + if (h == NULL || src == NULL) return ESP_ERR_INVALID_ARG; + if (h->dev == NULL) return ESP_ERR_INVALID_STATE; + uint8_t raw = 0; + esp_err_t err = read_regs(h, SC7A20H_REG_CLICK_SRC, &raw, 1); + if (err != ESP_OK) return err; + src->src = raw; + src->count = raw & 0x0F; + return ESP_OK; +} + +esp_err_t sc7a20h_aoi_config(sc7a20h_handle_t h, sc7a20h_aoi_t unit, const sc7a20h_aoi_cfg_t* cfg) { + if (h == NULL || cfg == NULL) return ESP_ERR_INVALID_ARG; + if (h->dev == NULL) return ESP_ERR_INVALID_STATE; + uint8_t base = unit == SC7A20H_AOI2 ? SC7A20H_REG_AOI2_CFG : SC7A20H_REG_AOI1_CFG; + esp_err_t err; + if ((err = write_reg(h, base, cfg->cfg)) != ESP_OK) return err; + if ((err = write_reg(h, (uint8_t)(base + 2), sc7a20h_ths_lsb(h, cfg->ths_mg))) != ESP_OK) { + return err; + } + return write_reg(h, (uint8_t)(base + 3), (uint8_t)(cfg->duration & 0x7F)); +} + +esp_err_t sc7a20h_aoi_read(sc7a20h_handle_t h, sc7a20h_aoi_t unit, sc7a20h_aoi_src_t* src) { + if (h == NULL || src == NULL) return ESP_ERR_INVALID_ARG; + if (h->dev == NULL) return ESP_ERR_INVALID_STATE; + uint8_t raw = 0; + uint8_t reg = unit == SC7A20H_AOI2 ? SC7A20H_REG_AOI2_SRC : SC7A20H_REG_AOI1_SRC; + esp_err_t err = read_regs(h, reg, &raw, 1); + if (err != ESP_OK) return err; + sc7a20h_aoi_decode(raw, src); + return ESP_OK; +} + +esp_err_t sc7a20h_orientation_arm( + sc7a20h_handle_t h, bool enable_4d, uint16_t ths_mg, uint8_t duration +) { + if (h == NULL || h->dev == NULL) return ESP_ERR_INVALID_STATE; + sc7a20h_aoi_cfg_t cfg = { + .cfg = (uint8_t)(SC7A20H_AOI_AND | SC7A20H_AOI_6D | SC7A20H_AOI_XYZ), + .ths_mg = ths_mg, + .duration = duration, + }; + esp_err_t err = write_reg(h, SC7A20H_REG_CTRL2, 0x00); + if (err != ESP_OK) return err; + err = sc7a20h_aoi_config(h, SC7A20H_AOI1, &cfg); + if (err != ESP_OK) return err; + uint8_t ctrl5 = (uint8_t)((h->ctrl5 & CTRL5_FIFO_EN) | CTRL5_LIR_INT1); + if (enable_4d) ctrl5 |= CTRL5_D4D_INT1; + return write_ctrl5(h, ctrl5); +} + +sc7a20h_orient_t sc7a20h_orientation(const sc7a20h_aoi_src_t* src) { + if (src == NULL || !src->ia) return SC7A20H_ORIENT_UNKNOWN; + if (src->zh) return SC7A20H_ORIENT_PZ; + if (src->zl) return SC7A20H_ORIENT_NZ; + if (src->xh) return SC7A20H_ORIENT_PX; + if (src->xl) return SC7A20H_ORIENT_NX; + if (src->yh) return SC7A20H_ORIENT_PY; + if (src->yl) return SC7A20H_ORIENT_NY; + return SC7A20H_ORIENT_UNKNOWN; +} + +esp_err_t sc7a20h_freefall_config(sc7a20h_handle_t h, uint16_t ths_mg, uint8_t duration) { + if (h == NULL || h->dev == NULL) return ESP_ERR_INVALID_STATE; + sc7a20h_aoi_cfg_t cfg = { + .cfg = (uint8_t)(SC7A20H_AOI_AND | SC7A20H_AOI_6D | SC7A20H_AOI_XL + | SC7A20H_AOI_YL | SC7A20H_AOI_ZL), + .ths_mg = ths_mg, + .duration = duration, + }; + esp_err_t err = write_reg(h, SC7A20H_REG_CTRL2, 0x00); + if (err != ESP_OK) return err; + err = sc7a20h_aoi_config(h, SC7A20H_AOI2, &cfg); + if (err != ESP_OK) return err; + return write_ctrl5(h, (uint8_t)((h->ctrl5 & ~0x20) | CTRL5_LIR_INT1 | (h->ctrl5 & CTRL5_FIFO_EN))); +} + +esp_err_t sc7a20h_activity_config(sc7a20h_handle_t h, uint16_t ths_mg, uint8_t duration) { + if (h == NULL || h->dev == NULL) return ESP_ERR_INVALID_STATE; + sc7a20h_aoi_cfg_t cfg = { + .cfg = (uint8_t)(SC7A20H_AOI_XH | SC7A20H_AOI_YH | SC7A20H_AOI_ZH), + .ths_mg = ths_mg, + .duration = duration, + }; + uint8_t ctrl2 = motion_ctrl2(h, CTRL2_HPIS2); + esp_err_t err = write_ctrl2_hpf(h, ctrl2); + if (err != ESP_OK) return err; + err = sc7a20h_aoi_config(h, SC7A20H_AOI2, &cfg); + if (err != ESP_OK) return err; + return write_ctrl5(h, (uint8_t)((h->ctrl5 & ~0x20) | CTRL5_LIR_INT1 | (h->ctrl5 & CTRL5_FIFO_EN))); +} + +esp_err_t sc7a20h_fifo_config(sc7a20h_handle_t h, const sc7a20h_fifo_cfg_t* cfg) { + if (h == NULL || cfg == NULL) return ESP_ERR_INVALID_ARG; + if (h->dev == NULL) return ESP_ERR_INVALID_STATE; + uint8_t wtm = cfg->watermark > 31 ? 31 : cfg->watermark; + uint8_t fifo_ctrl = (uint8_t)(((uint8_t)cfg->mode << 6) | (cfg->trigger_aoi2 ? 0x20 : 0) | wtm); + h->fifo_8bit = cfg->bit8; + h->ctrl3 = (uint8_t)((h->ctrl3 & ~CTRL3_FIFO_8BIT) | (cfg->bit8 ? CTRL3_FIFO_8BIT : 0)); + esp_err_t err = write_reg(h, SC7A20H_REG_CTRL3, h->ctrl3); + if (err != ESP_OK) return err; + if (cfg->mode == SC7A20H_FIFO_BYPASS) { + err = write_reg(h, SC7A20H_REG_FIFO_CTRL, fifo_ctrl); + if (err != ESP_OK) return err; + return write_ctrl5(h, (uint8_t)(h->ctrl5 & ~CTRL5_FIFO_EN)); + } + err = write_ctrl5(h, (uint8_t)(h->ctrl5 | CTRL5_FIFO_EN)); + if (err != ESP_OK) return err; + return write_reg(h, SC7A20H_REG_FIFO_CTRL, fifo_ctrl); +} + +esp_err_t sc7a20h_fifo_status(sc7a20h_handle_t h, sc7a20h_fifo_status_t* status) { + if (h == NULL || status == NULL) return ESP_ERR_INVALID_ARG; + if (h->dev == NULL) return ESP_ERR_INVALID_STATE; + uint8_t raw = 0; + esp_err_t err = read_regs(h, SC7A20H_REG_FIFO_SRC, &raw, 1); + if (err != ESP_OK) return err; + status->raw = raw; + status->wtm = (raw & 0x80) != 0; + status->overrun = (raw & 0x40) != 0; + status->empty = (raw & 0x20) != 0; + status->fss = raw & 0x1F; + return ESP_OK; +} + +esp_err_t sc7a20h_fifo_read( + sc7a20h_handle_t h, sc7a20h_sample_t* samples, size_t max, size_t* out_n +) { + if (h == NULL || samples == NULL || out_n == NULL) return ESP_ERR_INVALID_ARG; + if (h->dev == NULL) return ESP_ERR_INVALID_STATE; + sc7a20h_fifo_status_t st; + esp_err_t err = sc7a20h_fifo_status(h, &st); + if (err != ESP_OK) return err; + // FSS 只有 5 位:满 32 组时为 0,EMPTY=0。不能把 0 当成没数据。 + // / FSS is 5 bits: full 32 samples reads 0 with EMPTY=0. Do not treat 0 as empty. + size_t n = st.empty ? 0 : (st.fss == 0 ? (size_t)SC7A20H_FIFO_MAX : st.fss); + if (n > max) n = max; + if (n > SC7A20H_FIFO_MAX) n = SC7A20H_FIFO_MAX; + *out_n = 0; + const int bps = h->fifo_8bit ? 3 : 6; + for (size_t i = 0; i < n; i++) { + uint8_t data[6] = { 0 }; + for (int b = 0; b < bps; b++) { + err = read_regs(h, SC7A20H_REG_FIFO_DATA, &data[b], 1); + if (err != ESP_OK) return err; + } + int16_t x, y, z; + if (h->fifo_8bit) { + x = (int16_t)((int8_t)data[0] << 8); + y = (int16_t)((int8_t)data[1] << 8); + z = (int16_t)((int8_t)data[2] << 8); + } else { + x = (int16_t)((uint16_t)data[1] << 8 | data[0]); + y = (int16_t)((uint16_t)data[3] << 8 | data[2]); + z = (int16_t)((uint16_t)data[5] << 8 | data[4]); + } + fill_sample(h, &samples[i], x, y, z, 0); + (*out_n)++; + } + return ESP_OK; +} + +esp_err_t sc7a20h_fifo_clear(sc7a20h_handle_t h) { + if (h == NULL) return ESP_ERR_INVALID_STATE; + sc7a20h_fifo_status_t st; + esp_err_t st_err = sc7a20h_fifo_status(h, &st); + if (st_err != ESP_OK) return st_err; + uint8_t saved = 0; + esp_err_t err = read_regs(h, SC7A20H_REG_FIFO_CTRL, &saved, 1); + if (err != ESP_OK) return err; + err = write_reg(h, SC7A20H_REG_FIFO_CTRL, 0x00); + if (err != ESP_OK) return err; + vTaskDelay(pdMS_TO_TICKS(2)); + return write_reg(h, SC7A20H_REG_FIFO_CTRL, saved); +} + +static esp_err_t wait_sample(sc7a20h_handle_t h, sc7a20h_sample_t* sample, int timeout_ms) { + int64_t deadline = esp_timer_get_time() + (int64_t)timeout_ms * 1000; + while (esp_timer_get_time() < deadline) { + uint8_t status = 0; + esp_err_t err = read_regs(h, SC7A20H_REG_STATUS, &status, 1); + if (err != ESP_OK) return err; + if (status & 0x08) return sc7a20h_read(h, sample); + vTaskDelay(1); + } + return ESP_ERR_TIMEOUT; +} + +esp_err_t sc7a20h_self_test(sc7a20h_handle_t h, sc7a20h_selftest_t* result) { + if (h == NULL || result == NULL) return ESP_ERR_INVALID_ARG; + if (h->dev == NULL) return ESP_ERR_INVALID_STATE; + + const int n = 16; + int32_t off[3] = { 0 }; + int32_t on[3] = { 0 }; + sc7a20h_sample_t s; + uint8_t ctrl4 = 0; + int timeout_ms = 4000 / (int)sc7a20h_odr_hz(h->cfg.odr) + 50; + if (timeout_ms < 120) timeout_ms = 120; + esp_err_t err = read_regs(h, SC7A20H_REG_CTRL4, &ctrl4, 1); + if (err != ESP_OK) return err; + + for (int i = 0; i < n; i++) { + err = wait_sample(h, &s, timeout_ms); + if (err != ESP_OK) return err; + off[0] += s.x_mg; + off[1] += s.y_mg; + off[2] += s.z_mg; + } + + err = write_reg(h, SC7A20H_REG_CTRL4, (uint8_t)((ctrl4 & ~0x06) | CTRL4_ST0)); + if (err != ESP_OK) return err; + vTaskDelay(pdMS_TO_TICKS(100)); + for (int i = 0; i < n; i++) { + err = wait_sample(h, &s, timeout_ms); + if (err != ESP_OK) { + write_reg(h, SC7A20H_REG_CTRL4, ctrl4); + return err; + } + on[0] += s.x_mg; + on[1] += s.y_mg; + on[2] += s.z_mg; + } + write_reg(h, SC7A20H_REG_CTRL4, ctrl4); + + int16_t dx = (int16_t)(on[0] / n - off[0] / n); + int16_t dy = (int16_t)(on[1] / n - off[1] / n); + int16_t dz = (int16_t)(on[2] / n - off[2] / n); + result->dx_mg = dx; + result->dy_mg = dy; + result->dz_mg = dz; + int ax = abs(dx); + int ay = abs(dy); + int az = abs(dz); + result->pass_x = ax >= 160 && ax <= 640; + result->pass_y = ay >= 160 && ay <= 640; + result->pass_z = az >= 215 && az <= 860; + return ESP_OK; +} + +static uint32_t isqrt32(uint32_t x) { + uint32_t r = 0; + uint32_t b = 1u << 30; + while (b > x) b >>= 2; + while (b != 0) { + if (x >= r + b) { + x -= r + b; + r = (r >> 1) + b; + } else { + r >>= 1; + } + b >>= 2; + } + return r; +} + +esp_err_t sc7a20h_stats_collect(sc7a20h_handle_t h, uint16_t n, sc7a20h_stats_t* stats) { + if (h == NULL || stats == NULL || n < 2 || n > 256) return ESP_ERR_INVALID_ARG; + if (h->dev == NULL) return ESP_ERR_INVALID_STATE; + + int16_t* xs = calloc((size_t)n * 3, sizeof(int16_t)); + if (xs == NULL) return ESP_ERR_NO_MEM; + int16_t* ys = xs + n; + int16_t* zs = ys + n; + uint16_t overrun = 0; + int64_t t0 = 0; + int timeout_ms = 4000 / (int)sc7a20h_odr_hz(h->cfg.odr) + 20; + if (timeout_ms < 20) timeout_ms = 20; + + for (uint16_t i = 0; i < n; i++) { + sc7a20h_sample_t s; + esp_err_t err = wait_sample(h, &s, timeout_ms); + if (err != ESP_OK) { + free(xs); + return err; + } + if (i == 0) t0 = esp_timer_get_time(); + if (s.status & 0x80) overrun++; + xs[i] = s.x_mg; + ys[i] = s.y_mg; + zs[i] = s.z_mg; + } + int64_t dt = esp_timer_get_time() - t0; + if (dt < 1) dt = 1; + + int32_t sx = 0, sy = 0, sz = 0; + int16_t minx = xs[0], maxx = xs[0]; + int16_t miny = ys[0], maxy = ys[0]; + int16_t minz = zs[0], maxz = zs[0]; + for (uint16_t i = 0; i < n; i++) { + sx += xs[i]; + sy += ys[i]; + sz += zs[i]; + if (xs[i] < minx) minx = xs[i]; + if (xs[i] > maxx) maxx = xs[i]; + if (ys[i] < miny) miny = ys[i]; + if (ys[i] > maxy) maxy = ys[i]; + if (zs[i] < minz) minz = zs[i]; + if (zs[i] > maxz) maxz = zs[i]; + } + int16_t mx = (int16_t)(sx / n); + int16_t my = (int16_t)(sy / n); + int16_t mz = (int16_t)(sz / n); + uint32_t vx = 0, vy = 0, vz = 0; + for (uint16_t i = 0; i < n; i++) { + int32_t dx = xs[i] - mx; + int32_t dy = ys[i] - my; + int32_t dz = zs[i] - mz; + vx += (uint32_t)(dx * dx); + vy += (uint32_t)(dy * dy); + vz += (uint32_t)(dz * dz); + } + stats->mean_x = mx; + stats->mean_y = my; + stats->mean_z = mz; + stats->pp_x = (int16_t)(maxx - minx); + stats->pp_y = (int16_t)(maxy - miny); + stats->pp_z = (int16_t)(maxz - minz); + stats->std_x = (uint16_t)isqrt32(vx / n); + stats->std_y = (uint16_t)isqrt32(vy / n); + stats->std_z = (uint16_t)isqrt32(vz / n); + stats->odr_x10 = (uint16_t)((int64_t)(n - 1) * 10000000 / dt); + stats->overrun = overrun; + stats->n = n; + free(xs); + return ESP_OK; +} + +esp_err_t sc7a20h_int_route(sc7a20h_handle_t h, uint8_t mask) { + if (h == NULL || h->dev == NULL) return ESP_ERR_INVALID_STATE; + h->ctrl3 = (uint8_t)((mask & 0xF6) | (h->fifo_8bit ? CTRL3_FIFO_8BIT : 0)); + esp_err_t err = write_reg(h, SC7A20H_REG_CTRL3, h->ctrl3); + if (err != ESP_OK) return err; + return write_reg(h, SC7A20H_REG_CTRL6, 0x00); +} + +static void IRAM_ATTR int1_isr(void* arg) { + sc7a20h_handle_t h = (sc7a20h_handle_t)arg; + if (h) h->int1_count++; +} + +esp_err_t sc7a20h_int1_begin(sc7a20h_handle_t h) { + if (h == NULL || !gpio_ok(h->int1_gpio)) return ESP_ERR_INVALID_ARG; + int1_detach_isr(h); + gpio_config_t io = { + .pin_bit_mask = 1ULL << h->int1_gpio, + .mode = GPIO_MODE_INPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_ENABLE, + .intr_type = GPIO_INTR_POSEDGE, + }; + esp_err_t err = gpio_config(&io); + if (err != ESP_OK) return err; + err = gpio_install_isr_service(0); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) return err; + err = gpio_isr_handler_add(h->int1_gpio, int1_isr, h); + if (err != ESP_OK) return err; + h->int1_isr = true; + h->int1_count = 0; + return ESP_OK; +} + +gpio_num_t sc7a20h_int1_gpio(sc7a20h_handle_t h) { + return h == NULL ? GPIO_NUM_NC : h->int1_gpio; +} + +int sc7a20h_int1_level(sc7a20h_handle_t h) { + if (h == NULL || !gpio_ok(h->int1_gpio)) return -1; + return gpio_get_level(h->int1_gpio); +} + +uint32_t sc7a20h_int1_count(sc7a20h_handle_t h) { + return h == NULL ? 0 : h->int1_count; +} + +esp_err_t sc7a20h_read_events(sc7a20h_handle_t h, sc7a20h_events_t* events) { + if (h == NULL || events == NULL) return ESP_ERR_INVALID_ARG; + if (h->dev == NULL) return ESP_ERR_INVALID_STATE; + memset(events, 0, sizeof(*events)); + esp_err_t err; + if ((err = read_regs(h, SC7A20H_REG_CLICK_SRC, &events->click_src, 1)) != ESP_OK) { + return err; + } + if ((err = read_regs(h, SC7A20H_REG_AOI1_SRC, &events->aoi1_src, 1)) != ESP_OK) { + return err; + } + if ((err = read_regs(h, SC7A20H_REG_AOI2_SRC, &events->aoi2_src, 1)) != ESP_OK) { + return err; + } + if ((err = read_regs(h, SC7A20H_REG_FIFO_SRC, &events->fifo_src, 1)) != ESP_OK) { + return err; + } + if ((err = read_regs(h, SC7A20H_REG_STATUS, &events->status, 1)) != ESP_OK) { + return err; + } + events->int1_count = h->int1_count; + events->int1_level = sc7a20h_int1_level(h); + return ESP_OK; +} + +const char* sc7a20h_odr_name(sc7a20h_odr_t odr) { + switch (odr) { + case SC7A20H_ODR_12_5: return "12.5 Hz"; + case SC7A20H_ODR_25: return "25 Hz"; + case SC7A20H_ODR_50: return "50 Hz"; + case SC7A20H_ODR_100: return "100 Hz"; + case SC7A20H_ODR_200: return "200 Hz"; + case SC7A20H_ODR_400: return "400 Hz"; + case SC7A20H_ODR_800: return "800 Hz"; + default: return "--"; + } +} + +const char* sc7a20h_fs_name(sc7a20h_fs_t fs) { + switch (fs) { + case SC7A20H_FS_2G: return "±2 g"; + case SC7A20H_FS_4G: return "±4 g"; + case SC7A20H_FS_8G: return "±8 g"; + case SC7A20H_FS_16G: return "±16 g"; + default: return "--"; + } +} + +const char* sc7a20h_mode_name(sc7a20h_mode_t mode) { + switch (mode) { + case SC7A20H_MODE_NORMAL: return "normal"; + case SC7A20H_MODE_LP: return "low-power"; + case SC7A20H_MODE_HR: return "high-res"; + case SC7A20H_MODE_ENHANCED: return "enhanced"; + default: return "--"; + } +} + +const char* sc7a20h_orient_name(sc7a20h_orient_t orient) { + switch (orient) { + case SC7A20H_ORIENT_PX: return "+X"; + case SC7A20H_ORIENT_NX: return "-X"; + case SC7A20H_ORIENT_PY: return "+Y"; + case SC7A20H_ORIENT_NY: return "-Y"; + case SC7A20H_ORIENT_PZ: return "+Z"; + case SC7A20H_ORIENT_NZ: return "-Z"; + default: return "unknown"; + } +} + +const char* sc7a20h_click_name(uint8_t count) { + switch (count) { + case 0: return "none"; + case 1: return "single"; + case 2: return "double"; + case 3: return "triple"; + default: return "multi"; + } +} + +const char* sc7a20h_fifo_mode_name(sc7a20h_fifo_mode_t mode) { + switch (mode) { + case SC7A20H_FIFO_BYPASS: return "bypass"; + case SC7A20H_FIFO_MODE: return "fifo"; + case SC7A20H_FIFO_STREAM: return "stream"; + case SC7A20H_FIFO_TRIGGER: return "trigger"; + default: return "--"; + } +} + +const char* sc7a20h_int_route_name(uint8_t mask) { + if (mask & SC7A20H_INT1_CLICK) return "click"; + if (mask & SC7A20H_INT1_AOI1) return "AOI1"; + if (mask & SC7A20H_INT1_AOI2) return "AOI2"; + if (mask & SC7A20H_INT1_DRDY) return "data-ready"; + if (mask & SC7A20H_INT1_WTM) return "watermark"; + if (mask & SC7A20H_INT1_OVERRUN) return "overrun"; + return "none"; +} diff --git a/firmware/targets/mindreset_read_pico/components/sy7636a/CMakeLists.txt b/firmware/targets/mindreset_read_pico/components/sy7636a/CMakeLists.txt new file mode 100644 index 0000000..2337ad5 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/sy7636a/CMakeLists.txt @@ -0,0 +1,6 @@ +idf_component_register( + SRCS "sy7636a.c" + INCLUDE_DIRS "include" + REQUIRES esp_driver_gpio esp_driver_i2c + PRIV_REQUIRES esp_timer freertos esp_rom +) diff --git a/firmware/targets/mindreset_read_pico/components/sy7636a/LICENSE b/firmware/targets/mindreset_read_pico/components/sy7636a/LICENSE new file mode 100644 index 0000000..500992f --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/sy7636a/LICENSE @@ -0,0 +1,17 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +Copyright 2026 mindreset + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/firmware/targets/mindreset_read_pico/components/sy7636a/README.md b/firmware/targets/mindreset_read_pico/components/sy7636a/README.md new file mode 100644 index 0000000..b7f00c8 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/sy7636a/README.md @@ -0,0 +1,50 @@ +# sy7636a + +ESP-IDF 下的 **SY7636A** 墨水屏 PMIC C 驱动(矽力杰):VCOM 设置、轨控制、故障解码,以及波形查找要用的片上温度传感器。 + +ESP-IDF C driver for the **SY7636A** e-paper PMIC (Silergy): VCOM setting, rail control, fault decode, and the on-chip temperature sensor the waveform lookup needs. + +组件名 / Registry name: `mindreset/sy7636a` + +## 能力 / Features + +- Handle:调用方传入已有 `i2c_master` 总线;EN / VCOM_EN / PGOOD 可以是本机 GPIO 或回调,位于 IO 扩展上的信号线同样可用。/ Handle-based API: pass an existing `i2c_master` bus; EN / VCOM_EN / PGOOD can each be a native GPIO or a callback, so lines sitting on an IO expander work too. +- VCOM 以 mV 计,支持手动 / 自动 VCOM 控制,可回读已存设定。/ VCOM in mV, manual / auto VCOM control, readback of the stored setting. +- 电源轨开关并等待 PGOOD,VLDO 选择,各轨放电与上电延时寄存器。/ Rail on/off that waits for PGOOD, VLDO selection, per-rail discharge and power-up delay registers. +- 故障寄存器解码(各轨 UVP / SCP、OTP)为单一枚举。/ Fault register decode (UVP / SCP per rail, OTP) into a single enum. +- 温度以 °C 读出,用于选择波形温度档。/ Temperature in °C, used to pick the waveform temperature range. + +## 硬件 / Hardware + +| 项目 / Item | 规格 / Spec | +|---|---| +| 芯片 / Part | SY7636A(矽力杰)/ SY7636A (Silergy) | +| 总线 / Bus | I2C,400 kHz | +| 地址 / Address | `0x62`(`SY7636A_ADDR_DEFAULT`) | +| PGOOD | 开漏;小纸 Pico 上经 IO 扩展脚读回 / Open-drain; on Read Pico it is read back through an IO expander pin | + +## 用法 / Usage + +```c +sy7636a_config_t cfg = SY7636A_CONFIG_DEFAULT(); +cfg.en_fn = board_sy_en; // 本板的 EN 位于 FCA9555 上。/ EN sits on the FCA9555 on this board. +cfg.pgood_fn = board_pgood; + +sy7636a_handle_t pmic; +sy7636a_init(bus, &cfg, &pmic); +sy7636a_set_vcom(pmic, 1290); // -1.29 V +sy7636a_power_on(pmic); + +sy7636a_status_t st; +sy7636a_read(pmic, &st); // vcom_mv / temperature_c / fault / vldo +``` + +完整转储见 `examples/read_status`。 + +See `examples/read_status` for a full dump. + +## 许可 / License + +Apache-2.0,见 [LICENSE](LICENSE)。 + +Apache-2.0, see [LICENSE](LICENSE). diff --git a/firmware/targets/mindreset_read_pico/components/sy7636a/examples/read_status/CMakeLists.txt b/firmware/targets/mindreset_read_pico/components/sy7636a/examples/read_status/CMakeLists.txt new file mode 100644 index 0000000..ab6988f --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/sy7636a/examples/read_status/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 3.16) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(read_status) diff --git a/firmware/targets/mindreset_read_pico/components/sy7636a/examples/read_status/main/CMakeLists.txt b/firmware/targets/mindreset_read_pico/components/sy7636a/examples/read_status/main/CMakeLists.txt new file mode 100644 index 0000000..1cd20e1 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/sy7636a/examples/read_status/main/CMakeLists.txt @@ -0,0 +1 @@ +idf_component_register(SRCS "main.c" REQUIRES sy7636a driver) diff --git a/firmware/targets/mindreset_read_pico/components/sy7636a/examples/read_status/main/idf_component.yml b/firmware/targets/mindreset_read_pico/components/sy7636a/examples/read_status/main/idf_component.yml new file mode 100644 index 0000000..f99ef62 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/sy7636a/examples/read_status/main/idf_component.yml @@ -0,0 +1,4 @@ +dependencies: + mindreset/sy7636a: + version: "^1.0.0" + override_path: "../../.." diff --git a/firmware/targets/mindreset_read_pico/components/sy7636a/examples/read_status/main/main.c b/firmware/targets/mindreset_read_pico/components/sy7636a/examples/read_status/main/main.c new file mode 100644 index 0000000..9d3a77d --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/sy7636a/examples/read_status/main/main.c @@ -0,0 +1,62 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * EN 为高时 I2C 才有应答。本例把 EN 接到 GPIO,读一遍状态后关轨。 + * + * I2C ACKs only while EN is high. This example wires EN to a GPIO, reads + * status once, then drops the rails. + */ + +#include "sy7636a.h" + +#include "driver/i2c_master.h" +#include "esp_log.h" + +static const char* TAG = "sy7636a_ex"; + +#ifndef CONFIG_EXAMPLE_I2C_SDA +#define CONFIG_EXAMPLE_I2C_SDA 39 +#define CONFIG_EXAMPLE_I2C_SCL 40 +#define CONFIG_EXAMPLE_SY_EN 0 +#endif + +void app_main(void) { + i2c_master_bus_handle_t bus; + i2c_master_bus_config_t bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = CONFIG_EXAMPLE_I2C_SDA, + .scl_io_num = CONFIG_EXAMPLE_I2C_SCL, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .flags.enable_internal_pullup = true, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&bus_cfg, &bus)); + + sy7636a_handle_t pmic = NULL; + sy7636a_config_t config = SY7636A_CONFIG_DEFAULT(); + config.en_gpio = CONFIG_EXAMPLE_SY_EN; + if (sy7636a_init(bus, &config, &pmic) != ESP_OK) { + ESP_LOGE(TAG, "init failed"); + return; + } + + if (sy7636a_power_on(pmic) != ESP_OK) { + ESP_LOGE(TAG, "power on failed (EN / PGOOD?)"); + sy7636a_deinit(pmic); + return; + } + + sy7636a_status_t st; + if (sy7636a_read(pmic, &st) == ESP_OK) { + ESP_LOGI( + TAG, "on=%d VCOM=-%d mV %s T=%dC fault=%s", + st.on, st.vcom_mv, sy7636a_vldo_name(st.vldo), + st.temperature_c, sy7636a_fault_name(st.fault) + ); + } + + sy7636a_power_off(pmic); + sy7636a_deinit(pmic); + ESP_ERROR_CHECK(i2c_del_master_bus(bus)); +} diff --git a/firmware/targets/mindreset_read_pico/components/sy7636a/idf_component.yml b/firmware/targets/mindreset_read_pico/components/sy7636a/idf_component.yml new file mode 100644 index 0000000..bd192e0 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/sy7636a/idf_component.yml @@ -0,0 +1,14 @@ +version: "1.0.0" +description: "SY7636A EPD PMIC (Silergy) — VCOM, rails, and temperature" +url: "https://github.com/mindreset/sy7636a" +repository: "https://github.com/mindreset/sy7636a.git" +issues: "https://github.com/mindreset/sy7636a/issues" +license: "Apache-2.0" +tags: + - pmic + - sy7636a + - i2c + - epd + - eink +dependencies: + idf: ">=5.3" diff --git a/firmware/targets/mindreset_read_pico/components/sy7636a/include/sy7636a.h b/firmware/targets/mindreset_read_pico/components/sy7636a/include/sy7636a.h new file mode 100644 index 0000000..6c6d115 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/sy7636a/include/sy7636a.h @@ -0,0 +1,176 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * SY7636A 墨水屏 PMIC(Silergy)。 + * I2C 只在 EN 为高时有效(STANDBY / ACTIVE)。EN 拉低会复位全部寄存器。 + * 调用方提供 EN / VCOM_EN / PGOOD:可以是 GPIO,也可以是 IO 扩展口回调。 + * + * SY7636A EPD PMIC (Silergy). + * I2C is live only while EN is high (STANDBY / ACTIVE). Pulling EN low + * resets every register. The caller supplies EN / VCOM_EN / PGOOD: GPIO + * or IO-expander callbacks. + */ + +#pragma once + +#include +#include + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct sy7636a_dev_t* sy7636a_handle_t; + +#define SY7636A_ADDR_DEFAULT 0x62 +#define SY7636A_I2C_HZ_DEFAULT 400000 +#define SY7636A_VCOM_MAX_MV 5000 + +#define SY7636A_DISCHG_VCOM (1U << 0) +#define SY7636A_DISCHG_VNEG (1U << 1) +#define SY7636A_DISCHG_VPOS (1U << 2) +#define SY7636A_DISCHG_VDDH (1U << 4) +#define SY7636A_DISCHG_MASK 0x17 + +typedef enum { + SY7636A_VLDO_1525 = 2, + SY7636A_VLDO_1500 = 3, + SY7636A_VLDO_1475 = 4, + SY7636A_VLDO_1450 = 5, + SY7636A_VLDO_1425 = 6, +} sy7636a_vldo_t; + +typedef enum { + SY7636A_FAULT_NONE = 0, + SY7636A_FAULT_UVP_VP = 1, + SY7636A_FAULT_UVP_VN = 2, + SY7636A_FAULT_UVP_VPOS = 3, + SY7636A_FAULT_UVP_VNEG = 4, + SY7636A_FAULT_UVP_VDDH = 5, + SY7636A_FAULT_UVP_VEE = 6, + SY7636A_FAULT_SCP_VP = 7, + SY7636A_FAULT_SCP_VN = 8, + SY7636A_FAULT_SCP_VPOS = 9, + SY7636A_FAULT_SCP_VNEG = 10, + SY7636A_FAULT_SCP_VDDH = 11, + SY7636A_FAULT_SCP_VEE = 12, + SY7636A_FAULT_SCP_VCOM = 13, + SY7636A_FAULT_OTP = 15, + SY7636A_FAULT_UNKNOWN = 16, +} sy7636a_fault_t; + +typedef struct { + int vcom_mv; + sy7636a_vldo_t vldo; + uint8_t discharge; + uint8_t dly_ms[4]; + bool vcom_manual; +} sy7636a_power_config_t; + +#define SY7636A_POWER_CONFIG_DEFAULT() \ + { \ + .vcom_mv = 1250, \ + .vldo = SY7636A_VLDO_1500, \ + .discharge = 0, \ + .dly_ms = { 2, 2, 2, 2 }, \ + .vcom_manual = true, \ + } + +typedef struct { + bool on; + bool vcom_manual; + uint8_t discharge; + int vcom_mv; + sy7636a_vldo_t vldo; + uint8_t dly_ms[4]; + uint8_t fault_reg; + sy7636a_fault_t fault; + bool pg; + int8_t temperature_c; +} sy7636a_status_t; + +typedef struct { + uint8_t i2c_addr; + uint32_t scl_speed_hz; + gpio_num_t en_gpio; + gpio_num_t vcom_en_gpio; + gpio_num_t pgood_gpio; + esp_err_t (*en_fn)(bool on); + esp_err_t (*vcom_en_fn)(bool on); + int (*pgood_fn)(void); + uint16_t en_settle_ms; + uint16_t pgood_timeout_ms; + uint16_t poweroff_hold_ms; + sy7636a_power_config_t power; +} sy7636a_config_t; + +#define SY7636A_CONFIG_DEFAULT() \ + { \ + .i2c_addr = SY7636A_ADDR_DEFAULT, \ + .scl_speed_hz = SY7636A_I2C_HZ_DEFAULT, \ + .en_gpio = GPIO_NUM_NC, \ + .vcom_en_gpio = GPIO_NUM_NC, \ + .pgood_gpio = GPIO_NUM_NC, \ + .en_fn = NULL, \ + .vcom_en_fn = NULL, \ + .pgood_fn = NULL, \ + .en_settle_ms = 20, \ + .pgood_timeout_ms = 300, \ + .poweroff_hold_ms = 500, \ + .power = SY7636A_POWER_CONFIG_DEFAULT(), \ + } + +esp_err_t sy7636a_init(i2c_master_bus_handle_t bus_handle, const sy7636a_config_t* config, + sy7636a_handle_t* handle); +esp_err_t sy7636a_deinit(sy7636a_handle_t handle); + +/// EN 拉高并等待数字核就绪,不置 ON_OFF。*woke 表示这次是我们拉高的。 +/// / Raise EN and wait for the digital core; does not set ON_OFF. *woke means we raised it. +esp_err_t sy7636a_i2c_begin(sy7636a_handle_t h, bool* woke); +/// 若 woke 且高压未开,把 EN 拉低(寄存器回默认)。 +/// / If woke and the HV rails are off, drop EN (registers return to default). +void sy7636a_i2c_end(sy7636a_handle_t h, bool woke); + +/// 写 VCOM / VLDO / 延时后置 ON_OFF,再等 PGOOD,最后拉 VCOM_EN。 +/// / Write VCOM / VLDO / delays, set ON_OFF, wait for PGOOD, then raise VCOM_EN. +esp_err_t sy7636a_power_on(sy7636a_handle_t h); +esp_err_t sy7636a_power_off(sy7636a_handle_t h); +bool sy7636a_rails_on(sy7636a_handle_t h); + +esp_err_t sy7636a_read(sy7636a_handle_t h, sy7636a_status_t* status); +const sy7636a_status_t* sy7636a_last(sy7636a_handle_t h); +const sy7636a_power_config_t* sy7636a_get_config(sy7636a_handle_t h); + +esp_err_t sy7636a_set_vcom(sy7636a_handle_t h, int mv); +int sy7636a_get_vcom(sy7636a_handle_t h); +esp_err_t sy7636a_set_vcom_manual(sy7636a_handle_t h, bool manual); + +// --------------------------------------------------------------------------- +// 危险:改源极幅值、关轨放电或上电间隔会直接作用在面板高压上。 +// 接错屏会损坏面板。不要从产品界面调用。 +// 改之前对照该型号屏幕规格书(VPOS/VNEG、VCOM、上电顺序)。 +// EN 拉低后芯片回默认;下次 power_on 按这里缓存的值重写。 +// Dangerous: source amplitude, rail discharge, and power-on spacing hit +// the panel HV. A wrong panel can be damaged. Do not call from product UI. +// Check that panel's spec (VPOS/VNEG, VCOM, power-on order) first. +// EN low restores defaults; the next power_on rewrites this cache. +// --------------------------------------------------------------------------- +esp_err_t sy7636a_set_vldo(sy7636a_handle_t h, sy7636a_vldo_t vldo); +esp_err_t sy7636a_set_discharge(sy7636a_handle_t h, uint8_t bits); +esp_err_t sy7636a_set_poweron_delay( + sy7636a_handle_t h, + uint8_t dly1_ms, uint8_t dly2_ms, uint8_t dly3_ms, uint8_t dly4_ms +); + +sy7636a_fault_t sy7636a_fault_from_reg(uint8_t fault_reg); +const char* sy7636a_fault_name(sy7636a_fault_t fault); +const char* sy7636a_vldo_name(sy7636a_vldo_t vldo); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/targets/mindreset_read_pico/components/sy7636a/sy7636a.c b/firmware/targets/mindreset_read_pico/components/sy7636a/sy7636a.c new file mode 100644 index 0000000..52f8ccf --- /dev/null +++ b/firmware/targets/mindreset_read_pico/components/sy7636a/sy7636a.c @@ -0,0 +1,491 @@ +/* + * SPDX-FileCopyrightText: 2026 mindreset + * SPDX-License-Identifier: Apache-2.0 + * + * SY7636A 主机:EN 控制、I2C 读写、轨上电/掉电。 + * + * SY7636A host: EN control, I2C read/write, rail power-on/off. + */ + +#include "sy7636a.h" + +#include +#include + +#include "esp_log.h" +#include "esp_rom_sys.h" +#include "esp_timer.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#define REG_OPERATION 0x00 +#define REG_VCOM_LSB 0x01 +#define REG_VCOM_MSB 0x02 +#define REG_VLDO 0x03 +#define REG_DELAY 0x06 +#define REG_FAULT 0x07 +#define REG_TEMP 0x08 + +#define OP_ON 0x80 +#define OP_VCOMCTL 0x40 +#define VLDO_RESERVED 0x06 +#define I2C_TIMEOUT_MS 100 +#define I2C_RETRY 5 + +static const char* TAG = "sy7636a"; + +struct sy7636a_dev_t { + i2c_master_dev_handle_t dev; + gpio_num_t en_gpio; + gpio_num_t vcom_en_gpio; + gpio_num_t pgood_gpio; + esp_err_t (*en_fn)(bool on); + esp_err_t (*vcom_en_fn)(bool on); + int (*pgood_fn)(void); + uint16_t en_settle_ms; + uint16_t pgood_timeout_ms; + uint16_t poweroff_hold_ms; + sy7636a_power_config_t cfg; + sy7636a_status_t last; + bool last_ok; + bool rails; + bool en_on; +}; + +static bool gpio_ok(gpio_num_t gpio) { + return gpio != GPIO_NUM_NC && GPIO_IS_VALID_GPIO(gpio); +} + +static int dly_enc(uint8_t ms) { + switch (ms) { + case 0: return 0; + case 1: return 1; + case 2: return 2; + case 4: return 3; + default: return -1; + } +} + +static uint8_t dly_ms(uint8_t code) { + static const uint8_t ms[4] = { 0, 1, 2, 4 }; + return ms[code & 0x03]; +} + +static bool cfg_ok(const sy7636a_power_config_t* cfg) { + if (cfg->vcom_mv < 0 || cfg->vcom_mv > SY7636A_VCOM_MAX_MV) return false; + if (cfg->vldo < SY7636A_VLDO_1525 || cfg->vldo > SY7636A_VLDO_1425) return false; + if (cfg->discharge & (uint8_t)~SY7636A_DISCHG_MASK) return false; + for (int i = 0; i < 4; i++) { + if (dly_enc(cfg->dly_ms[i]) < 0) return false; + } + return true; +} + +static uint8_t pack_delay(const uint8_t dly[4]) { + return (uint8_t)( + (dly_enc(dly[3]) << 6) | (dly_enc(dly[2]) << 4) + | (dly_enc(dly[1]) << 2) | dly_enc(dly[0]) + ); +} + +static uint8_t pack_vldo(sy7636a_vldo_t vldo) { + return (uint8_t)(((uint8_t)vldo << 5) | VLDO_RESERVED); +} + +static uint8_t pack_op(const sy7636a_power_config_t* cfg, bool on) { + uint8_t op = (uint8_t)(cfg->discharge & SY7636A_DISCHG_MASK); + if (on) op |= OP_ON; + if (cfg->vcom_manual) op |= OP_VCOMCTL; + return op; +} + +static void unpack_delay(uint8_t reg, uint8_t out[4]) { + out[0] = dly_ms(reg); + out[1] = dly_ms((uint8_t)(reg >> 2)); + out[2] = dly_ms((uint8_t)(reg >> 4)); + out[3] = dly_ms((uint8_t)(reg >> 6)); +} + +static sy7636a_vldo_t unpack_vldo(uint8_t reg) { + return (sy7636a_vldo_t)((reg >> 5) & 0x07); +} + +static esp_err_t write_reg(sy7636a_handle_t h, uint8_t reg, uint8_t value) { + uint8_t data[2] = { reg, value }; + return i2c_master_transmit(h->dev, data, sizeof(data), I2C_TIMEOUT_MS); +} + +static esp_err_t read_reg(sy7636a_handle_t h, uint8_t reg, uint8_t* value) { + // 手册要求先 STOP 再读,不能 Repeated START。 + // / Datasheet requires STOP then read; Repeated START is not allowed. + esp_err_t err = i2c_master_transmit(h->dev, ®, 1, I2C_TIMEOUT_MS); + if (err != ESP_OK) return err; + return i2c_master_receive(h->dev, value, 1, I2C_TIMEOUT_MS); +} + +static esp_err_t read_reg_retry(sy7636a_handle_t h, uint8_t reg, uint8_t* value) { + esp_err_t err = ESP_FAIL; + for (int i = 0; i < I2C_RETRY; i++) { + err = read_reg(h, reg, value); + if (err == ESP_OK) return ESP_OK; + vTaskDelay(pdMS_TO_TICKS(10)); + } + return err; +} + +static esp_err_t set_en(sy7636a_handle_t h, bool on) { + if (h->en_fn) return h->en_fn(on); + if (gpio_ok(h->en_gpio)) return gpio_set_level(h->en_gpio, on ? 1 : 0); + return ESP_OK; +} + +static esp_err_t set_vcom_en(sy7636a_handle_t h, bool on) { + if (h->vcom_en_fn) return h->vcom_en_fn(on); + if (gpio_ok(h->vcom_en_gpio)) return gpio_set_level(h->vcom_en_gpio, on ? 1 : 0); + return ESP_OK; +} + +static int read_pgood(sy7636a_handle_t h) { + if (h->pgood_fn) return h->pgood_fn(); + if (gpio_ok(h->pgood_gpio)) return gpio_get_level(h->pgood_gpio); + return 1; +} + +static esp_err_t cfg_gpio_out(gpio_num_t gpio, int level) { + if (!gpio_ok(gpio)) return ESP_OK; + gpio_config_t io = { + .pin_bit_mask = 1ULL << gpio, + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + esp_err_t err = gpio_config(&io); + if (err != ESP_OK) return err; + return gpio_set_level(gpio, level); +} + +static esp_err_t cfg_gpio_in(gpio_num_t gpio) { + if (!gpio_ok(gpio)) return ESP_OK; + gpio_config_t io = { + .pin_bit_mask = 1ULL << gpio, + .mode = GPIO_MODE_INPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + return gpio_config(&io); +} + +static esp_err_t write_vcom(sy7636a_handle_t h) { + int code = h->cfg.vcom_mv / 10; + if (code < 0) code = 0; + if (code > 500) code = 500; + + uint8_t msb = 0x14; + esp_err_t err = read_reg(h, REG_VCOM_MSB, &msb); + if (err != ESP_OK) return err; + err = write_reg(h, REG_VCOM_LSB, (uint8_t)code); + if (err != ESP_OK) return err; + msb = (uint8_t)((msb & 0x7F) | ((code & 0x100) ? 0x80 : 0)); + return write_reg(h, REG_VCOM_MSB, msb); +} + +static esp_err_t write_pending(sy7636a_handle_t h) { + if (!h->en_on) return ESP_OK; + esp_err_t err = write_vcom(h); + if (err != ESP_OK) return err; + err = write_reg(h, REG_VLDO, pack_vldo(h->cfg.vldo)); + if (err != ESP_OK) return err; + err = write_reg(h, REG_DELAY, pack_delay(h->cfg.dly_ms)); + if (err != ESP_OK) return err; + return write_reg(h, REG_OPERATION, pack_op(&h->cfg, h->rails)); +} + +static void fill_status( + sy7636a_status_t* s, uint8_t op, uint8_t fault, uint8_t vldo, + uint8_t delay, int8_t temp, int vcom_mv +) { + s->on = (op & OP_ON) != 0; + s->vcom_manual = (op & OP_VCOMCTL) != 0; + s->discharge = (uint8_t)(op & SY7636A_DISCHG_MASK); + s->vcom_mv = vcom_mv; + s->vldo = unpack_vldo(vldo); + unpack_delay(delay, s->dly_ms); + s->fault_reg = fault; + s->fault = sy7636a_fault_from_reg(fault); + s->pg = (fault & 0x01) != 0; + s->temperature_c = temp; +} + +esp_err_t sy7636a_init(i2c_master_bus_handle_t bus_handle, const sy7636a_config_t* config, + sy7636a_handle_t* handle) { + if (bus_handle == NULL || config == NULL || handle == NULL) return ESP_ERR_INVALID_ARG; + + sy7636a_power_config_t cfg = config->power; + if (cfg.vcom_mv == 0 && cfg.vldo == 0) cfg = (sy7636a_power_config_t)SY7636A_POWER_CONFIG_DEFAULT(); + if (!cfg_ok(&cfg)) return ESP_ERR_INVALID_ARG; + + sy7636a_handle_t h = calloc(1, sizeof(*h)); + if (h == NULL) return ESP_ERR_NO_MEM; + + h->en_gpio = config->en_gpio; + h->vcom_en_gpio = config->vcom_en_gpio; + h->pgood_gpio = config->pgood_gpio; + h->en_fn = config->en_fn; + h->vcom_en_fn = config->vcom_en_fn; + h->pgood_fn = config->pgood_fn; + h->en_settle_ms = config->en_settle_ms ? config->en_settle_ms : 20; + h->pgood_timeout_ms = config->pgood_timeout_ms ? config->pgood_timeout_ms : 300; + h->poweroff_hold_ms = config->poweroff_hold_ms ? config->poweroff_hold_ms : 500; + h->cfg = cfg; + h->en_on = !h->en_fn && !gpio_ok(h->en_gpio); + + esp_err_t err = cfg_gpio_out(h->en_gpio, 0); + if (err == ESP_OK) err = cfg_gpio_out(h->vcom_en_gpio, 0); + if (err == ESP_OK) err = cfg_gpio_in(h->pgood_gpio); + if (err != ESP_OK) { + free(h); + return err; + } + + i2c_device_config_t dev_cfg = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = config->i2c_addr ? config->i2c_addr : SY7636A_ADDR_DEFAULT, + .scl_speed_hz = config->scl_speed_hz ? config->scl_speed_hz : SY7636A_I2C_HZ_DEFAULT, + }; + err = i2c_master_bus_add_device(bus_handle, &dev_cfg, &h->dev); + if (err != ESP_OK) { + ESP_LOGE(TAG, "I2C add failed: %s", esp_err_to_name(err)); + free(h); + return err; + } + + *handle = h; + return ESP_OK; +} + +esp_err_t sy7636a_deinit(sy7636a_handle_t h) { + if (h == NULL) return ESP_OK; + if (h->rails) (void)sy7636a_power_off(h); + if (h->dev) i2c_master_bus_rm_device(h->dev); + free(h); + return ESP_OK; +} + +esp_err_t sy7636a_i2c_begin(sy7636a_handle_t h, bool* woke) { + if (h == NULL || woke == NULL) return ESP_ERR_INVALID_ARG; + *woke = false; + if (h->en_on) return ESP_OK; + esp_err_t err = set_en(h, true); + if (err != ESP_OK) return err; + h->en_on = true; + *woke = true; + vTaskDelay(pdMS_TO_TICKS(h->en_settle_ms)); + return ESP_OK; +} + +void sy7636a_i2c_end(sy7636a_handle_t h, bool woke) { + if (h == NULL || !woke || h->rails) return; + (void)set_en(h, false); + h->en_on = false; +} + +esp_err_t sy7636a_power_on(sy7636a_handle_t h) { + if (h == NULL) return ESP_ERR_INVALID_ARG; + if (h->rails) return ESP_OK; + + bool woke = false; + esp_err_t err = sy7636a_i2c_begin(h, &woke); + if (err != ESP_OK) return err; + (void)set_vcom_en(h, false); + + h->rails = false; + for (int i = 0; i < I2C_RETRY; i++) { + err = write_vcom(h); + if (err == ESP_OK) err = write_reg(h, REG_VLDO, pack_vldo(h->cfg.vldo)); + if (err == ESP_OK) err = write_reg(h, REG_DELAY, pack_delay(h->cfg.dly_ms)); + if (err == ESP_OK) err = write_reg(h, REG_OPERATION, pack_op(&h->cfg, true)); + if (err == ESP_OK) break; + vTaskDelay(pdMS_TO_TICKS(10)); + } + if (err != ESP_OK) { + ESP_LOGE(TAG, "power on failed: %s", esp_err_to_name(err)); + sy7636a_i2c_end(h, true); + return err; + } + + bool good = false; + int64_t deadline = esp_timer_get_time() + (int64_t)h->pgood_timeout_ms * 1000; + do { + if (read_pgood(h) > 0) { + good = true; + break; + } + esp_rom_delay_us(200); + } while (esp_timer_get_time() < deadline); + + if (!good) { + uint8_t fault = 0xFF; + (void)read_reg(h, REG_FAULT, &fault); + ESP_LOGE(TAG, "PGOOD timeout, fault=0x%02X", fault); + (void)write_reg(h, REG_OPERATION, 0x00); + vTaskDelay(pdMS_TO_TICKS(150)); + sy7636a_i2c_end(h, true); + return ESP_ERR_TIMEOUT; + } + + err = set_vcom_en(h, true); + if (err != ESP_OK) { + (void)write_reg(h, REG_OPERATION, 0x00); + sy7636a_i2c_end(h, true); + return err; + } + vTaskDelay(pdMS_TO_TICKS(5)); + h->rails = true; + ESP_LOGI(TAG, "rails on, VCOM=-%d mV", h->cfg.vcom_mv); + return ESP_OK; +} + +esp_err_t sy7636a_power_off(sy7636a_handle_t h) { + if (h == NULL) return ESP_ERR_INVALID_ARG; + (void)set_vcom_en(h, false); + if (h->en_on) (void)write_reg(h, REG_OPERATION, 0x00); + vTaskDelay(pdMS_TO_TICKS(h->poweroff_hold_ms)); + (void)set_en(h, false); + h->en_on = false; + h->rails = false; + ESP_LOGI(TAG, "rails off"); + return ESP_OK; +} + +bool sy7636a_rails_on(sy7636a_handle_t h) { + return h != NULL && h->rails; +} + +esp_err_t sy7636a_read(sy7636a_handle_t h, sy7636a_status_t* status) { + if (h == NULL || status == NULL) return ESP_ERR_INVALID_ARG; + + uint8_t op = 0, fault = 0, temp = 0, vldo = 0, delay = 0; + esp_err_t err = read_reg_retry(h, REG_OPERATION, &op); + if (err == ESP_OK) err = read_reg_retry(h, REG_FAULT, &fault); + if (err == ESP_OK) err = read_reg_retry(h, REG_TEMP, &temp); + if (err == ESP_OK) err = read_reg_retry(h, REG_VLDO, &vldo); + if (err == ESP_OK) err = read_reg_retry(h, REG_DELAY, &delay); + if (err != ESP_OK) return err; + + fill_status(status, op, fault, vldo, delay, (int8_t)temp, h->cfg.vcom_mv); + h->last = *status; + h->last_ok = true; + return ESP_OK; +} + +const sy7636a_status_t* sy7636a_last(sy7636a_handle_t h) { + if (h == NULL || !h->last_ok) return NULL; + return &h->last; +} + +const sy7636a_power_config_t* sy7636a_get_config(sy7636a_handle_t h) { + return h ? &h->cfg : NULL; +} + +esp_err_t sy7636a_set_vcom(sy7636a_handle_t h, int mv) { + if (h == NULL) return ESP_ERR_INVALID_ARG; + if (mv < 0) mv = 0; + if (mv > SY7636A_VCOM_MAX_MV) mv = SY7636A_VCOM_MAX_MV; + h->cfg.vcom_mv = mv; + if (!h->en_on) return ESP_OK; + return write_vcom(h); +} + +int sy7636a_get_vcom(sy7636a_handle_t h) { + return h ? h->cfg.vcom_mv : 0; +} + +esp_err_t sy7636a_set_vcom_manual(sy7636a_handle_t h, bool manual) { + if (h == NULL) return ESP_ERR_INVALID_ARG; + h->cfg.vcom_manual = manual; + if (!h->en_on) return ESP_OK; + return write_reg(h, REG_OPERATION, pack_op(&h->cfg, h->rails)); +} + +esp_err_t sy7636a_set_vldo(sy7636a_handle_t h, sy7636a_vldo_t vldo) { + if (h == NULL) return ESP_ERR_INVALID_ARG; + if (vldo < SY7636A_VLDO_1525 || vldo > SY7636A_VLDO_1425) { + return ESP_ERR_INVALID_ARG; + } + h->cfg.vldo = vldo; + return write_pending(h); +} + +esp_err_t sy7636a_set_discharge(sy7636a_handle_t h, uint8_t bits) { + if (h == NULL) return ESP_ERR_INVALID_ARG; + h->cfg.discharge = (uint8_t)(bits & SY7636A_DISCHG_MASK); + return write_pending(h); +} + +esp_err_t sy7636a_set_poweron_delay( + sy7636a_handle_t h, + uint8_t dly1_ms, uint8_t dly2_ms, uint8_t dly3_ms, uint8_t dly4_ms +) { + if (h == NULL) return ESP_ERR_INVALID_ARG; + uint8_t dly[4] = { dly1_ms, dly2_ms, dly3_ms, dly4_ms }; + for (int i = 0; i < 4; i++) { + if (dly_enc(dly[i]) < 0) return ESP_ERR_INVALID_ARG; + } + memcpy(h->cfg.dly_ms, dly, sizeof(dly)); + return write_pending(h); +} + +sy7636a_fault_t sy7636a_fault_from_reg(uint8_t fault_reg) { + switch ((fault_reg >> 1) & 0x0F) { + case 0x0: return SY7636A_FAULT_NONE; + case 0x1: return SY7636A_FAULT_UVP_VP; + case 0x2: return SY7636A_FAULT_UVP_VN; + case 0x3: return SY7636A_FAULT_UVP_VPOS; + case 0x4: return SY7636A_FAULT_UVP_VNEG; + case 0x5: return SY7636A_FAULT_UVP_VDDH; + case 0x6: return SY7636A_FAULT_UVP_VEE; + case 0x7: return SY7636A_FAULT_SCP_VP; + case 0x8: return SY7636A_FAULT_SCP_VN; + case 0x9: return SY7636A_FAULT_SCP_VPOS; + case 0xA: return SY7636A_FAULT_SCP_VNEG; + case 0xB: return SY7636A_FAULT_SCP_VDDH; + case 0xC: return SY7636A_FAULT_SCP_VEE; + case 0xD: return SY7636A_FAULT_SCP_VCOM; + case 0xF: return SY7636A_FAULT_OTP; + default: return SY7636A_FAULT_UNKNOWN; + } +} + +const char* sy7636a_fault_name(sy7636a_fault_t fault) { + switch (fault) { + case SY7636A_FAULT_NONE: return "none"; + case SY7636A_FAULT_UVP_VP: return "UVP VP"; + case SY7636A_FAULT_UVP_VN: return "UVP VN"; + case SY7636A_FAULT_UVP_VPOS: return "UVP VPOS"; + case SY7636A_FAULT_UVP_VNEG: return "UVP VNEG"; + case SY7636A_FAULT_UVP_VDDH: return "UVP VDDH"; + case SY7636A_FAULT_UVP_VEE: return "UVP VEE"; + case SY7636A_FAULT_SCP_VP: return "SCP VP"; + case SY7636A_FAULT_SCP_VN: return "SCP VN"; + case SY7636A_FAULT_SCP_VPOS: return "SCP VPOS"; + case SY7636A_FAULT_SCP_VNEG: return "SCP VNEG"; + case SY7636A_FAULT_SCP_VDDH: return "SCP VDDH"; + case SY7636A_FAULT_SCP_VEE: return "SCP VEE"; + case SY7636A_FAULT_SCP_VCOM: return "SCP VCOM"; + case SY7636A_FAULT_OTP: return "OTP"; + default: return "fault"; + } +} + +const char* sy7636a_vldo_name(sy7636a_vldo_t vldo) { + switch (vldo) { + case SY7636A_VLDO_1525: return "±15.25 V"; + case SY7636A_VLDO_1500: return "±15.00 V"; + case SY7636A_VLDO_1475: return "±14.75 V"; + case SY7636A_VLDO_1450: return "±14.50 V"; + case SY7636A_VLDO_1425: return "±14.25 V"; + default: return "reserved"; + } +} diff --git a/firmware/targets/mindreset_read_pico/dependencies.lock b/firmware/targets/mindreset_read_pico/dependencies.lock new file mode 100644 index 0000000..2482819 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/dependencies.lock @@ -0,0 +1,44 @@ +dependencies: + esp_desktop_buddy: + component_hash: f1305e181359597ab959383220bf260f9ce6611a6c15a9945439dd759b670946 + dependencies: + - name: idf + version: '>=5.4' + - name: espressif/cjson + version: 1.7.19~2 + source: + git: https://github.com/espressif/esp-desktop-buddy.git + path: components/esp_desktop_buddy + type: git + version: b6bac05db208717676e70180e5269d79f32b2d68 + esp_desktop_buddy_transport_ble: + component_hash: d27a5886b6ff0d053acacaf6b10749627e5176f4679790ae560ab0b2012b5516 + dependencies: + - name: idf + version: '>=5.4' + source: + git: https://github.com/espressif/esp-desktop-buddy.git + path: components/esp_desktop_buddy_transport_ble + type: git + version: b6bac05db208717676e70180e5269d79f32b2d68 + espressif/cjson: + component_hash: e788323270d90738662d66fffa910bfe1fba019bba087f01557e70c40485b469 + dependencies: + - name: idf + require: private + version: '>=5.0' + source: + registry_url: https://components.espressif.com/ + type: service + version: 1.7.19~2 + idf: + source: + type: idf + version: 6.1.0 +direct_dependencies: +- esp_desktop_buddy +- esp_desktop_buddy_transport_ble +- idf +manifest_hash: ee2cd2f46dcf1203b693f87e019166038b139e621b251010bd28efd3590bb24b +target: esp32s3 +version: 3.0.0 diff --git a/firmware/targets/mindreset_read_pico/main/CMakeLists.txt b/firmware/targets/mindreset_read_pico/main/CMakeLists.txt new file mode 100644 index 0000000..43eb964 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/main/CMakeLists.txt @@ -0,0 +1,7 @@ +idf_component_register( + SRCS "app_main.cpp" "board.cpp" "demo_view.cpp" "display_ui.cpp" "fonts.cpp" + INCLUDE_DIRS "." + REQUIRES usage_core usage_ble usage_ota nvs_flash esp_driver_i2c + esp_driver_usb_serial_jtag esp_timer esp_system + read_pico read_pico_pmu epdiy e0470_epaper_waveform cst836u sc7a20h) +target_compile_features(${COMPONENT_LIB} PUBLIC cxx_std_17) diff --git a/firmware/targets/mindreset_read_pico/main/app_main.cpp b/firmware/targets/mindreset_read_pico/main/app_main.cpp new file mode 100644 index 0000000..75b32bf --- /dev/null +++ b/firmware/targets/mindreset_read_pico/main/app_main.cpp @@ -0,0 +1,294 @@ +#include "board.hpp" +#include "demo_view.hpp" +#include "display_ui.hpp" +#include "driver/usb_serial_jtag.h" +#include "esp_log.h" +#include "esp_mac.h" +#include "esp_system.h" +#include "esp_timer.h" +#include "nvs_flash.h" +#include "usage_ble/ble_service.hpp" +#include "usage_ota/backend.hpp" +#include "usage_ota/power_gate.hpp" +#include "usage_ota/presentation.hpp" +#include +#include +#include +using namespace usage_panel; +using namespace usage_panel::read_pico; +namespace { +AppEventQueue events; +BleService ble; +EspOtaBackend backend("mr_read_pico"); +OtaSession ota(backend); +AtomicPowerSource power; +Board board; +DisplayUi ui; +View view; +std::atomic location{Page::Home}; +constexpr char kAdvertisingPrefix[] = "QF-MR-PICO-"; +const char *page_name(void *) { + switch (location.load()) { + case Page::Home: + return "overview"; + case Page::Clock: + return "clock"; + case Page::Settings: + return "settings"; + } + return "overview"; +} +constexpr uint8_t warning(uint8_t old, uint8_t value) { + if (value >= 95) + return 2; + if (old == 2 && value >= 93) + return 2; + if (value >= 80 || (old >= 1 && value >= 78)) + return 1; + return 0; +} +static_assert(warning(0, 80) == 1 && warning(1, 78) == 1 && warning(1, 77) == 0); +static_assert(warning(0, 95) == 2 && warning(2, 93) == 2 && warning(2, 92) == 1); +} // namespace +extern "C" void app_main() { + // Preserve NVS on initialization failures; recovery must not silently erase bonds. + ESP_ERROR_CHECK(nvs_flash_init()); + ESP_ERROR_CHECK(board.begin() ? ESP_OK : ESP_FAIL); + view.settings = board.load(); + view.orientation = view.settings.rotation == 2 ? 1 : 0; + if (view.settings.rotation == 0) { + uint8_t measured; + if (board.orientation(measured)) + view.orientation = measured; + } + uint8_t mac[6]; + esp_read_mac(mac, ESP_MAC_BT); + snprintf(view.device_name, sizeof(view.device_name), "%s%02X%02X", kAdvertisingPrefix, mac[4], + mac[5]); + view.now_ms = esp_timer_get_time() / 1000; + board.poll(view); + power.publish(view.power_valid, board.external_power(), view.battery >= 0 ? view.battery : 0); + ota.set_power_source(&power); + ESP_ERROR_CHECK(events.begin(32) ? ESP_OK : ESP_ERR_NO_MEM); + ui.begin(board.display()); + BleServiceConfig config{kAdvertisingPrefix, + "MindReset Read Pico", + "mindreset_read_pico", + true, + &ota, + page_name, + nullptr, + true, + true}; + ESP_ERROR_CHECK(ble.start(events, config)); + usb_serial_jtag_driver_config_t usb = USB_SERIAL_JTAG_DRIVER_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(usb_serial_jtag_driver_install(&usb)); + uint64_t next_poll = 0, next_draw = 0, last_activity = view.now_ms, orientation_since = 0, + next_orientation = 0; + bool force = true, baseline = false, diagnostic = false, idle_clock = false, + reboot_drawn = false; + uint8_t candidate = view.orientation; + Page return_page = Page::Home; + OtaPhase last_phase = OtaPhase::Idle; + int last_progress = -1; + RetainedOtaFailure shown_failure; + // 255 marks a window without a displayed value. + uint8_t shown[2][2] = {{255, 255}, {255, 255}}; + ESP_LOGI("read_pico", "READY serial: 1-9 views, t test card, i state, r live"); + while (true) { + view.now_ms = esp_timer_get_time() / 1000; + ui.tick(view.now_ms); + ota.tick(view.now_ms); + const OtaError previous_error = view.ota.error; + view.ota = ota.snapshot(); + if (retain_ota_failure(view.ota, shown_failure, view.now_ms)) + force = true; + view.ota.error = shown_failure.error != OtaError::None && + view.now_ms - shown_failure.started_ms < 15000 + ? shown_failure.error + : OtaError::None; + force = force || view.ota.error != previous_error; + int progress = ota_progress_percent(view.ota.offset, view.ota.size) / 5; + if (view.ota.phase != last_phase || progress != last_progress) { + force = true; + last_phase = view.ota.phase; + last_progress = progress; + } + // The RESTARTING frame is drawn synchronously one iteration before the restart. + if (view.ota.phase == OtaPhase::Rebooting && reboot_drawn) + esp_restart(); + AppEvent event{}; + while (events.receive(event)) { + switch (event.type) { + case AppEventType::Usage: + view.model.apply(event.usage, view.now_ms); + break; + case AppEventType::Link: + // Online and offline differ by a gray level across both service blocks. + baseline = baseline || view.link.encrypted != event.link.encrypted; + view.link = event.link; + force = true; + break; + case AppEventType::TimeSync: + if (!board.sync(event.time_sync.calendar)) + ESP_LOGW("read_pico", "RTC sync failed"); + next_poll = 0; + force = true; + break; + case AppEventType::ScreenToggle: + case AppEventType::ScreenPage: + // Home and Clock are the whole ring, so every remote step is a toggle. + if (!ota.busy() && !view.link.has_passkey) { + view.page = view.page == Page::Clock ? Page::Home : Page::Clock; + idle_clock = false; + force = true; + last_activity = view.now_ms; + } + break; + } + } + for (int i = 0; i < 2; ++i) { + const auto &s = view.model.snapshot(i ? Provider::Claude : Provider::Codex); + const bool short_present = s.has_valid_data && s.short_window.present && + (s.latest_short_present || !view.link.encrypted); + const bool week_present = s.has_valid_data && s.week_window.present && + (s.latest_week_present || !view.link.encrypted); + // The alarm level follows the fuller of the reported windows. + uint8_t next = warning( + view.warning[i], + std::max(short_present ? s.short_window.used_percent : 0, + week_present ? s.week_window.used_percent : 0)); + if (next != view.warning[i]) { + view.warning[i] = next; + force = baseline = true; + } + const uint8_t values[2] = { + short_present ? s.short_window.used_percent : uint8_t{255}, + week_present ? s.week_window.used_percent : uint8_t{255}}; + for (int window = 0; window < 2; ++window) { + // A shrinking bar exposes track gray, which only a GC16 refresh restores cleanly. + if (shown[i][window] != 255 && values[window] < shown[i][window]) + baseline = true; + shown[i][window] = values[window]; + } + } + const Input input = board.input(view.now_ms); + if (input.active || input.key || input.tap) + last_activity = view.now_ms; + if (input.key || input.tap) { + diagnostic = false; + force = true; + const TapTarget target = input.tap ? ui.hit(view, input.x, input.y) : TapTarget{}; + if (view.ota.phase == OtaPhase::Confirming) { + if (input.key == 2 || target.kind == TapTarget::OtaConfirm) + ota.confirm(view.now_ms); + else if (input.key == 5 || target.kind == TapTarget::OtaDeny) + ota.deny(); + } else if (ota.busy() || view.link.has_passkey) { + } else if (view.page == Page::Settings) { + if (input.key == 5 || target.kind == TapTarget::Done) + view.page = return_page; + else if (target.kind == TapTarget::Option) { + auto &s = view.settings; + uint8_t *fields[] = {&s.refresh, &s.idle, &s.rotation}; + *fields[target.row] = target.index; + view.save_error = !board.save(s); + } else if (input.key == 2) + baseline = true; + } else if (input.key == 5) { + return_page = view.page; + view.page = Page::Settings; + } else if (input.key == 2) { + baseline = true; + ESP_LOGI("read_pico", "Manual display refresh; Bridge publication is host-driven"); + } else if (input.key || view.page == Page::Clock) { + view.page = view.page == Page::Clock ? Page::Home : Page::Clock; + idle_clock = false; + } + } + if (view.now_ms >= next_orientation) { + next_orientation = view.now_ms + 100; + uint8_t measured; + const bool decisive = board.orientation(measured); + if (view.settings.rotation) { + const uint8_t fixed = view.settings.rotation == 2 ? 1 : 0; + if (view.orientation != fixed) { + view.orientation = fixed; + force = true; + } + } else if (ota.busy() || view.link.has_passkey || !decisive) + orientation_since = view.now_ms; + else if (candidate != measured) { + candidate = measured; + orientation_since = view.now_ms; + } else if (measured != view.orientation && view.now_ms - orientation_since >= 1500) { + view.orientation = measured; + force = true; + } + // Picking the device up leaves the idle clock; a clock opened by key stays. + if (board.moved() && idle_clock && view.page == Page::Clock) { + view.page = Page::Home; + idle_clock = false; + force = true; + last_activity = view.now_ms; + } + } + const int idle_minutes[] = {0, 5, 15, 30}; + if (!ota.busy() && !view.link.has_passkey && view.page == Page::Home && + idle_minutes[view.settings.idle] && + view.now_ms - last_activity >= idle_minutes[view.settings.idle] * 60000ULL) { + view.page = Page::Clock; + idle_clock = true; + force = true; + } + if (view.now_ms >= next_poll) { + next_poll = view.now_ms + 1000; + const bool clock_valid = view.clock_valid; + const auto calendar = view.calendar; + board.poll(view); + if (clock_valid != view.clock_valid || + (view.clock_valid && (calendar.minute != view.calendar.minute || + calendar.hour != view.calendar.hour || + calendar.day != view.calendar.day))) + force = true; + board.sample_trend(view); + power.publish(view.power_valid, board.external_power(), + view.battery >= 0 ? view.battery : 0); + } + uint8_t command = 0; + if (usb_serial_jtag_read_bytes(&command, 1, 0) == 1 && !ota.busy()) { + if (command == 'i') { + uint8_t measured; + board.orientation(measured, true); + ESP_LOGI("read_pico", + "STATE clock=%d battery=%d external=%d sd=%d orientation=%u " + "rotation_mode=%u page=%s", + view.clock_valid, view.battery, board.external_power(), view.sd, + view.orientation, view.settings.rotation, page_name(nullptr)); + } else if (command == 'r') { + diagnostic = false; + force = baseline = true; + } else if ((command >= '1' && command <= '9') || command == 't') { + ui.render(demo_view(static_cast(command), view), true); + diagnostic = true; + } + } + if (diagnostic && (ota.busy() || view.link.has_passkey)) { + diagnostic = false; + force = baseline = true; + } + location = view.page; + if (!diagnostic && (force || view.now_ms >= next_draw)) { + ui.render(view, baseline); + force = false; + baseline = false; + reboot_drawn = ui.ready() && view.ota.phase == OtaPhase::Rebooting; + const uint64_t intervals[] = {15000, 30000, 60000, 120000}; + next_draw = esp_timer_get_time() / 1000 + + (!ui.ready() ? 1000 : ota.busy() ? 60000 : intervals[view.settings.refresh]); + } + if (ui.ready()) + ble.verify_boot(); + vTaskDelay(pdMS_TO_TICKS(10)); + } +} diff --git a/firmware/targets/mindreset_read_pico/main/board.cpp b/firmware/targets/mindreset_read_pico/main/board.cpp new file mode 100644 index 0000000..09c966b --- /dev/null +++ b/firmware/targets/mindreset_read_pico/main/board.cpp @@ -0,0 +1,288 @@ +#include "board.hpp" +#include "epdiy.h" +#include "esp_log.h" +#include "nvs.h" +#include "read_pico_pmu.h" +#include "read_pico_sd.h" +#include +#include +#include +#include +#include +#include +namespace usage_panel::read_pico { +namespace { +// The three capacitive keys sit below the display on the same touch sensor. +constexpr int kKeyAreaTop = 1300, kKeyPitch = 160, kDisplayHeight = 1216; +constexpr uint64_t kHoldMs = 800, kReleaseDebounceMs = 30; +// Device-frame axis signs that read positive in the upright views: X runs along the long edge +// (keys at the bottom), Y along the short edge. The `i` serial command reports the raw sample. +constexpr int kUprightXSign = 1, kLandscapeYSign = -1; +constexpr int kMotionMg = 250; +int key_of(int y, int x) { return y >= kKeyAreaTop && x / kKeyPitch < 3 ? x / kKeyPitch + 1 : 0; } +// Days from 1970-01-01 to a proleptic Gregorian date. +constexpr int64_t days_from_civil(int year, unsigned month, unsigned day) { + year -= month <= 2; + const int era = (year >= 0 ? year : year - 399) / 400; + const unsigned yoe = static_cast(year - era * 400); + const unsigned doy = (153 * (month > 2 ? month - 3 : month + 9) + 2) / 5 + day - 1; + const unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; + return era * 146097LL + static_cast(doe) - 719468; +} +static_assert(days_from_civil(1970, 1, 1) == 0 && days_from_civil(2026, 10, 5) == 20731); +} // namespace +bool Board::begin() { + if (read_pico_init(&hw_) != ESP_OK) + return false; + // The panel VCOM is calibrated at the factory and stored in the PMU. + int vcom_mv = 0; + for (int i = 0; i < 3 && hw_.pmu_ready; ++i) + if (read_pico_pmu_vcom_get(&vcom_mv) == ESP_OK) { + epd_set_vcom(static_cast(vcom_mv)); + break; + } + if (!vcom_mv) + ESP_LOGW("read_pico", "Panel VCOM unavailable; using the board default"); + if (hw_.sensor_ready) + read_pico_sensor_wake(hw_.sensor); + nvs_handle_t n; + bool offset_valid = false; + if (nvs_open("read_pico", NVS_READONLY, &n) == ESP_OK) { + offset_valid = nvs_get_i16(n, "utc_off", &utc_offset_minutes_) == ESP_OK; + nvs_close(n); + } + if (hw_.pmu_ready && read_pico_pmu_cmd(PMU_CMD_TIME_GET, nullptr, 0) == ESP_OK) { + const pmu_snapshot_t *p = read_pico_pmu_get(); + if (p->time_synced && p->unix_sec > 1700000000) { + const timeval tv{static_cast(p->unix_sec), 0}; + settimeofday(&tv, nullptr); + clock_set_ = offset_valid; + } + } + ESP_LOGI("read_pico", "PMU=%d touch=%d accelerometer=%d clock=%d", hw_.pmu_ready, + hw_.touch_ready, hw_.sensor_ready, clock_set_); + return true; +} +void Board::poll(View &v) { + read_pico_sd_info_t sd{}; + const bool sd_ok = read_pico_sd_get_info(&sd) == ESP_OK; + // The driver mounts only on request; a card inserted after the boot probe or reinserted + // after removal stays unmounted until a remount starts a new probe. + if (sd.present && !sd_present_) { + read_pico_sd_remount(); + trend_loaded_ = false; + } + sd_present_ = sd.present; + v.sd = sd_ok && sd.mounted; + v.power_valid = v.charging = external_ = false; + v.battery = -1; + if (hw_.pmu_ready && read_pico_pmu_poll() == ESP_OK) { + const pmu_snapshot_t *p = read_pico_pmu_get(); + // The driver retains old power fields when STATUS fails CRC validation. + if (p->status_ok) { + const bool known = p->soc_permille <= 1000; + external_ = p->charge_state == PMU_CHARGE_CHARGING || + p->charge_state == PMU_CHARGE_FULL_INFERRED; + v.power_valid = known || external_; + v.battery = known ? (p->soc_permille + 5) / 10 : -1; + v.charging = p->charge_state == PMU_CHARGE_CHARGING; + } + // Power-key events are not used; unacknowledged events would fill the PMU queue. + if (p->pending_events) + read_pico_pmu_drain_events(); + } + v.clock_valid = clock_set_; + v.utc_epoch = clock_set_ ? static_cast(time(nullptr)) : 0; + if (clock_set_) { + const time_t local = static_cast(v.utc_epoch) + utc_offset_minutes_ * 60; + tm t{}; + gmtime_r(&local, &t); + v.calendar.year = t.tm_year + 1900; + v.calendar.month = t.tm_mon + 1; + v.calendar.day = t.tm_mday; + v.calendar.weekday = t.tm_wday; + v.calendar.hour = t.tm_hour; + v.calendar.minute = t.tm_min; + v.calendar.second = t.tm_sec; + v.calendar.utc_offset_minutes = utc_offset_minutes_; + } +} +bool Board::sync(const LocalCalendarTime &c) { + const int64_t local = days_from_civil(c.year, c.month, c.day) * 86400 + c.hour * 3600 + + c.minute * 60 + c.second; + const uint32_t utc = static_cast(local - c.utc_offset_minutes * 60); + const timeval tv{static_cast(utc), 0}; + settimeofday(&tv, nullptr); + utc_offset_minutes_ = c.utc_offset_minutes; + clock_set_ = true; + const uint8_t payload[] = {static_cast(utc), static_cast(utc >> 8), + static_cast(utc >> 16), static_cast(utc >> 24)}; + bool ok = hw_.pmu_ready && + read_pico_pmu_cmd(PMU_CMD_TIME_SYNC, payload, sizeof(payload)) == ESP_OK; + nvs_handle_t n; + if (nvs_open("read_pico", NVS_READWRITE, &n) != ESP_OK) + return false; + ok = nvs_set_i16(n, "utc_off", utc_offset_minutes_) == ESP_OK && nvs_commit(n) == ESP_OK && ok; + nvs_close(n); + return ok; +} +Input Board::input(uint64_t now) { + Input out; + if (!hw_.touch_ready) + return out; + static struct { + bool down = false, held = false; + int x = 0, y = 0; + uint64_t start = 0, released = 0; + } state; + cst836u_touch_t touch{}; + const bool down = cst836u_read(hw_.touch, &touch) == ESP_OK && touch.touched; + if (down) { + out.active = true; + state.released = 0; + if (!state.down) + state = {true, false, touch.x, touch.y, now, 0}; + else if (!state.held && now - state.start >= kHoldMs) { + state.held = true; + if (key_of(state.y, state.x) == 2) + out.key = 5; + } + } else if (state.down) { + // The controller drops single reports while a finger rests on the glass. + if (!state.released) + state.released = now; + else if (now - state.released >= kReleaseDebounceMs) { + state.down = false; + if (!state.held) { + out.key = key_of(state.y, state.x); + out.tap = state.y < kDisplayHeight; + out.x = state.x; + out.y = state.y; + } + } + } + return out; +} +bool Board::orientation(uint8_t &out, bool report) { + sc7a20h_sample_t s{}; + if (!hw_.sensor_ready || read_pico_accel_read(hw_.sensor, &s) != ESP_OK) + return false; + const int mg[] = {s.x_mg, s.y_mg, s.z_mg}; + for (int i = 0; i < 3; ++i) { + if (sampled_ && abs(mg[i] - last_mg_[i]) > kMotionMg) + moved_ = true; + last_mg_[i] = mg[i]; + } + sampled_ = true; + if (report) + ESP_LOGI("read_pico", "ACCEL x=%d y=%d z=%d mg", mg[0], mg[1], mg[2]); + const int x = abs(mg[0]), y = abs(mg[1]), z = abs(mg[2]); + if (x * 10 < z * 7 && y * 10 < z * 7) + return false; + if (abs(x - y) * 100 < 15 * std::max(x, y)) + return false; + out = x > y ? (mg[0] * kUprightXSign > 0 ? 0 : 2) : (mg[1] * kLandscapeYSign > 0 ? 1 : 3); + return true; +} +bool Board::moved() { + const bool moved = moved_; + moved_ = false; + return moved; +} +Settings Board::load() { + Settings s; + nvs_handle_t n; + if (nvs_open("read_pico", NVS_READONLY, &n) == ESP_OK) { + nvs_get_u8(n, "refresh", &s.refresh); + nvs_get_u8(n, "idle", &s.idle); + nvs_get_u8(n, "rotation", &s.rotation); + nvs_close(n); + } + s.refresh = std::min(s.refresh, 3); + s.idle = std::min(s.idle, 3); + s.rotation = std::min(s.rotation, 2); + return s; +} +bool Board::save(const Settings &s) { + nvs_handle_t n; + if (nvs_open("read_pico", NVS_READWRITE, &n) != ESP_OK) + return false; + bool ok = nvs_set_u8(n, "refresh", s.refresh) == ESP_OK && + nvs_set_u8(n, "idle", s.idle) == ESP_OK && + nvs_set_u8(n, "rotation", s.rotation) == ESP_OK && nvs_commit(n) == ESP_OK; + nvs_close(n); + return ok; +} +void Board::sample_trend(View &v) { + if (!v.sd) + return; + if (!trend_loaded_) { + trend_loaded_ = true; + // The card is the store: history from a previous card is not carried onto this one. + v.trend_count = 0; + for (const char *path : {"/sdcard/trend.bin", "/sdcard/trend.bak"}) { + FILE *f = fopen(path, "rb"); + if (!f) + continue; + bool valid = true; + TrendPoint p; + v.trend_count = 0; + while (true) { + const size_t bytes = fread(&p, 1, sizeof(p), f); + if (!bytes) + break; + if (bytes != sizeof(p) || v.trend_count == 48 || + p.epoch < 1700000000 || (p.codex > 100 && p.codex != 255) || + (p.claude > 100 && p.claude != 255)) { + valid = false; + break; + } + v.trend[v.trend_count++] = p; + } + valid = valid && !ferror(f); + fclose(f); + if (valid && v.trend_count) + break; + v.trend_count = 0; + } + } + uint32_t epoch = std::max(v.model.estimated_epoch(Provider::Codex, v.now_ms), + v.model.estimated_epoch(Provider::Claude, v.now_ms)); + if (!epoch || (v.trend_count && epoch / 1800 <= v.trend[v.trend_count - 1].epoch / 1800)) + return; + if (!v.link.encrypted) + return; + auto value = [&](Provider p) -> uint8_t { + const auto &s = v.model.snapshot(p); + return s.latest_short_present && s.short_window.present ? s.short_window.used_percent : 255; + }; + // Commit the in-memory bucket only after persistence succeeds so failed writes are retried. + auto trend = v.trend; + int count = v.trend_count; + if (count == 48) { + std::move(trend.begin() + 1, trend.end(), trend.begin()); + --count; + } + trend[count++] = {epoch, value(Provider::Codex), value(Provider::Claude), {}}; + FILE *f = fopen("/sdcard/trend.tmp", "wb"); + if (!f) + return; + bool ok = fwrite(trend.data(), sizeof(TrendPoint), count, f) == static_cast(count); + ok = fclose(f) == 0 && ok; + if (!ok) + return; + // A failed replacement may leave only the backup; preserve it until installation succeeds. + struct stat st; + if (stat("/sdcard/trend.bin", &st) == 0) { + remove("/sdcard/trend.bak"); + if (rename("/sdcard/trend.bin", "/sdcard/trend.bak") != 0) + return; + } + if (rename("/sdcard/trend.tmp", "/sdcard/trend.bin") != 0) { + ESP_LOGW("read_pico", "Trend persistence failed; backup retained"); + return; + } + v.trend = trend; + v.trend_count = count; +} +} // namespace usage_panel::read_pico diff --git a/firmware/targets/mindreset_read_pico/main/board.hpp b/firmware/targets/mindreset_read_pico/main/board.hpp new file mode 100644 index 0000000..eee3d54 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/main/board.hpp @@ -0,0 +1,50 @@ +#pragma once +#include "display_ui.hpp" +#include "read_pico_init.h" +namespace usage_panel::read_pico { +struct Input { + // 1-3: KEY1-KEY3 released before the hold time; 5: KEY2 held for 800 ms. + int key = 0; + // A touch on the display released before the hold time, in upright portrait coordinates. + bool tap = false; + int x = 0, y = 0; + // A finger is on the sensor. + bool active = false; +}; +class Board { + public: + /** Requires nvs_flash_init(). Returns false only when the panel cannot start. */ + bool begin(); + EpdiyHighlevelState *display() { return &hw_.hl; } + /** Updates the SD, power and clock fields of the view; power is invalid without PMU status. */ + void poll(View &view); + /** Sets the system clock and the PMU RTC, and stores the UTC offset. */ + bool sync(const LocalCalendarTime &time); + /** Reads the touch sensor; reports a key or tap once, on release or when the hold elapses. */ + Input input(uint64_t now); + /** Samples the accelerometer; false when the device lies flat or reads diagonally. */ + bool orientation(uint8_t &out, bool report = false); + /** True once after an orientation() sample differed from the one before it. */ + bool moved(); + bool external_power() const { return external_; } + /** Stores the settings in NVS; false when they could not be committed. */ + bool save(const Settings &settings); + /** Returns the stored settings, with defaults for missing keys and indices clamped to range. */ + Settings load(); + /** + * Loads the trend history on the first call after a card is mounted, then appends + * one sample per half hour while the link is encrypted. Does nothing without a card. + */ + void sample_trend(View &view); + + private: + read_pico_handle_t hw_{}; + // The PMU RTC counts UTC seconds; the wall clock needs the offset from the last time sync. + int16_t utc_offset_minutes_ = 0; + bool clock_set_ = false, external_ = false, moved_ = false, sampled_ = false; + // Starts true so that the boot probe is not repeated. + bool sd_present_ = true; + bool trend_loaded_ = false; + int last_mg_[3]{}; +}; +} // namespace usage_panel::read_pico diff --git a/firmware/targets/mindreset_read_pico/main/demo_view.cpp b/firmware/targets/mindreset_read_pico/main/demo_view.cpp new file mode 100644 index 0000000..1e86570 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/main/demo_view.cpp @@ -0,0 +1,61 @@ +#include "demo_view.hpp" +#include +namespace usage_panel::read_pico { +View demo_view(char command, const View &live) { + View demo{}; + demo.settings = live.settings; + demo.now_ms = live.now_ms; + demo.battery = 75; + demo.sd = true; + snprintf(demo.device_name, sizeof(demo.device_name), "%s", live.device_name); + demo.link = {true, true, false, 0}; + demo.clock_valid = true; + demo.calendar.year = 2026; + demo.calendar.month = 8; + demo.calendar.day = 12; + demo.calendar.weekday = 3; + demo.calendar.hour = 14; + demo.calendar.minute = 32; + demo.calendar.utc_offset_minutes = 480; + const bool alarm = command == '2'; + const uint8_t short_used[2] = {static_cast(alarm ? 96 : 42), + static_cast(alarm ? 87 : 70)}; + for (int i = 0; i < 2; ++i) { + if (command == '8' && i) + break; + UsageUpdate u; + u.provider = i ? Provider::Claude : Provider::Codex; + u.state = SourceState::Ok; + u.sampled_at = 1786516320; + u.sent_at = u.sampled_at; + u.short_window = {true, short_used[i], true, + u.sent_at + (i ? 2820 : 8040)}; + u.week_window = {true, static_cast(i ? 54 : 68), true, u.sent_at + 86400}; + demo.model.apply(u, demo.now_ms); + demo.warning[i] = alarm ? (i ? 1 : 2) : 0; + } + demo.trend_count = 48; + for (int i = 0; i < 48; ++i) + demo.trend[i] = {static_cast(1786516200 - (47 - i) * 1800), + static_cast(8 + i * (short_used[0] - 8) / 47), + static_cast(5 + i * (short_used[1] - 5) / 47), {}}; + if (command == '3') + demo.page = Page::Clock; + if (command == '4') + demo.orientation = 1; + if (command == '5') + demo.link = {true, false, true, 418502}; + if (command == '6' || command == '9') { + demo.ota.phase = command == '6' ? OtaPhase::Receiving : OtaPhase::Confirming; + demo.ota.version = "v1.2.0"; + demo.ota.size = 6500000; + demo.ota.offset = command == '6' ? 4225000 : 0; + } + if (command == '7') + demo.page = Page::Settings; + if (command == '8') + demo.link = {}; + demo.test_card = command == 't'; + return demo; +} +} // namespace usage_panel::read_pico diff --git a/firmware/targets/mindreset_read_pico/main/demo_view.hpp b/firmware/targets/mindreset_read_pico/main/demo_view.hpp new file mode 100644 index 0000000..5dadb1e --- /dev/null +++ b/firmware/targets/mindreset_read_pico/main/demo_view.hpp @@ -0,0 +1,10 @@ +#pragma once +#include "display_ui.hpp" +namespace usage_panel::read_pico { +/** + * Builds one synthetic view for display checks without a Bridge. + * command '1'-'9': home, alarms, clock, landscape home, pairing, OTA progress, settings, + * offline, OTA confirmation; 't': gray-level test card. Settings, uptime and the device name are taken from live. + */ +View demo_view(char command, const View &live); +} // namespace usage_panel::read_pico diff --git a/firmware/targets/mindreset_read_pico/main/display_ui.cpp b/firmware/targets/mindreset_read_pico/main/display_ui.cpp new file mode 100644 index 0000000..909e912 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/main/display_ui.cpp @@ -0,0 +1,899 @@ +#include "display_ui.hpp" +#include "epdiy.h" +#include "esp_log.h" +#include "esp_timer.h" +#include "fonts.hpp" +#include "read_pico_board.h" +#include "read_pico_epd_timing.h" +#include "usage_ota/presentation.hpp" +#include +#include +#include +#include +#include +namespace usage_panel::read_pico { +namespace { +// The panel gray levels in use; epdiy reads the upper nibble. The panel renders light +// grays close to paper, so tracks, rules and washes use darker levels than an emissive display. +// kSub: labels and secondary text. kDim: offline values and the secondary bar fill. +// kRule: rules, dashes and offline bar fills. kWash: alarm background. kTint: chart 80-100% band. +constexpr uint8_t kInk = 0x00, kSub = 0x20, kDim = 0x40, kRule = 0x60, kTrack = 0x90, + kWash = 0xC0, kTint = 0xD0, kPaper = 0xF0; +// DU and GL16 updates accumulate ghosting; a GC16 refresh renews the optical baseline. +constexpr unsigned kSoftRefreshesPerBaseline = 14; +// Rail power-down discharges for 500 ms, so the rails stay up across a burst of refreshes. +constexpr uint64_t kRailsIdleMs = 8000; +constexpr int kPanelTemperatureC = 25; +constexpr int kTouchWidth = 684, kTouchHeight = 1216; +uint32_t trend_now(const View &v) { + return v.utc_epoch ? v.utc_epoch + : std::max(v.model.estimated_epoch(Provider::Codex, v.now_ms), + v.model.estimated_epoch(Provider::Claude, v.now_ms)); +} +uint32_t trend_end(const View &v) { + return v.sd && v.trend_count ? v.trend[v.trend_count - 1].epoch : 0; +} +void trend_age(uint32_t now, uint32_t epoch, char *out, size_t size) { + const uint32_t minutes = now > epoch ? (now - epoch) / 60 : 0; + if (!minutes) + snprintf(out, size, "NOW"); + else if (minutes < 60) + snprintf(out, size, "%luM AGO", static_cast(minutes)); + else + snprintf(out, size, "%luH %02luM AGO", static_cast(minutes / 60), + static_cast(minutes % 60)); +} +void trend_time(const View &v, char *out, size_t size) { + const uint32_t epoch = trend_end(v); + if (!epoch) { + snprintf(out, size, "--:--"); + } else if (!v.clock_valid) { + trend_age(trend_now(v), epoch, out, size); + } else { + const time_t local = static_cast(epoch) + v.calendar.utc_offset_minutes * 60; + tm t{}; + gmtime_r(&local, &t); + snprintf(out, size, "%02d:%02d", t.tm_hour, t.tm_min); + } +} +struct Box { + int x, y, w, h; + bool contains(int px, int py) const { return px >= x && px < x + w && py >= y && py < y + h; } +}; +Box option_box(int width, int height, int row, int index) { + const bool portrait = height > width; + const int count = row == 2 ? 3 : 4; + const int left = portrait ? 40 : 380, top = portrait ? 224 + row * 242 : 113 + row * 142; + const int w = (width - 40 - left - (count - 1) * 10) / count; + return {left + index * (w + 10), top, w, 82}; +} +Box done_box(int width, int height) { + return height > width ? Box{40, height - 136, width - 80, 96} + : Box{40, height - 120, width - 80, 84}; +} +Box ota_button(int width, int height, int index) { + const int w = (width - 80 - 14) / 2; + return {40 + index * (w + 14), height - (height > width ? 40 : 36) - 140, w, 96}; +} +int scene_of(const View &v) { + if (v.test_card) + return 6; + if (v.ota.phase == OtaPhase::Confirming) + return 5; + if (v.ota.phase != OtaPhase::Idle) + return 3; + if (v.link.has_passkey) + return 4; + return static_cast(v.page); +} +const Font *font_for(int size, int family) { + const Font *f = &fonts[0]; + int distance = 10000; + for (int i = 0; i < font_count; ++i) { + int d = abs(fonts[i].size - size) + (fonts[i].family != family ? 1000 : 0); + if (d < distance) { + distance = d; + f = &fonts[i]; + } + } + return f; +} +const Glyph *glyph_for(const Font *f, unsigned char c, bool uppercase) { + if (uppercase && c >= 'a' && c <= 'z') + c -= 32; + for (int i = 0; i < f->count; ++i) + if (f->glyphs[i].character == c) + return &f->glyphs[i]; + return nullptr; +} +// Width of the inked run; the spacing after the last glyph is excluded. +int text_width(const char *s, int size, int family, int spacing) { + const Font *f = font_for(size, family); + int width = 0; + for (const unsigned char *p = reinterpret_cast(s); *p; ++p) + if (const Glyph *g = glyph_for(f, *p, true)) + width += g->advance + spacing; + return width ? width - spacing : 0; +} +// True when every changed pixel turns to paper or to a level the threshold DU waveform shows as +// ink. Changes to a lighter gray, such as text redrawn on an alarm wash, need a gray waveform. +bool du_reaches(const uint8_t *front, const uint8_t *back, size_t size) { + constexpr uint8_t kDarkest = kDim >> 4, kWhite = kPaper >> 4; + for (size_t i = 0; i < size; ++i) { + if (front[i] == back[i]) + continue; + for (int shift = 0; shift <= 4; shift += 4) { + const uint8_t to = front[i] >> shift & 0x0F; + if (to != (back[i] >> shift & 0x0F) && to > kDarkest && to != kWhite) + return false; + } + } + return true; +} +// Height of the digits, which text() places with their top edge at y. +int digit_height(int size, int family) { return glyph_for(font_for(size, family), '0', false)->height; } +void format_clock(const View &v, char *time, size_t time_size, char *date, size_t date_size) { + static const char *days[] = {"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"}; + static const char *months[] = {"JAN", "FEB", "MAR", "APR", "MAY", "JUN", + "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"}; + if (!v.clock_valid) { + snprintf(time, time_size, "--:--"); + snprintf(date, date_size, "CLOCK NOT SET"); + return; + } + snprintf(time, time_size, "%02u:%02u", v.calendar.hour, v.calendar.minute); + snprintf(date, date_size, "%s / %s %u", days[v.calendar.weekday % 7], + months[(v.calendar.month + 11) % 12], v.calendar.day); +} +} // namespace +struct DisplayUi::WindowSummary { + const char *label; + bool present, warn; + int percent; + char reset[32]; +}; +struct DisplayUi::ServiceSummary { + const char *name; + // none: no window can be shown. tag: alarm label, or nullptr below the alarm level. + bool none, offline; + const char *tag; + // The short window leads; the week window takes its place when no short window is reported. + WindowSummary primary, secondary; + char age[16]; +}; +DisplayUi::ServiceSummary DisplayUi::summarize(const View &v, int index) { + const Provider provider = index ? Provider::Claude : Provider::Codex; + const auto &s = v.model.snapshot(provider); + const bool online = v.link.encrypted; + const uint32_t epoch = v.model.estimated_epoch(provider, v.now_ms); + // Offline keeps the last valid sample; online shows only what the latest sample reported. + const auto window = [&](const char *label, const UsageWindow &w, bool latest) { + WindowSummary out{}; + out.label = label; + out.present = s.has_valid_data && w.present && (latest || !online); + out.percent = w.used_percent; + out.warn = out.present && online && w.used_percent >= 80 && w.used_percent < 95; + char countdown[24]; + format_countdown(w.has_reset, w.reset_at, epoch, countdown, sizeof(countdown)); + if (!out.present) + snprintf(out.reset, sizeof(out.reset), "--"); + else if (!online || !w.has_reset) + snprintf(out.reset, sizeof(out.reset), "RESET --"); + else if (!strcmp(countdown, "WAIT")) + snprintf(out.reset, sizeof(out.reset), "RESET WAIT"); + else + snprintf(out.reset, sizeof(out.reset), "RESETS %s", countdown); + return out; + }; + const WindowSummary brief = window("SHORT", s.short_window, s.latest_short_present), + week = window("WEEK", s.week_window, s.latest_week_present); + ServiceSummary r{}; + r.name = index ? "CLAUDE" : "CODEX"; + r.offline = !online; + r.primary = brief.present ? brief : week; + r.secondary = brief.present ? week : brief; + r.none = !r.primary.present; + if (!r.none && online && v.warning[index] == 2) + r.tag = std::max(r.primary.percent, r.secondary.present ? r.secondary.percent : 0) >= 100 + ? "EXHAUSTED" + : "NEAR LIMIT"; + const uint32_t age = epoch > s.sampled_at ? epoch - s.sampled_at : 0; + // Minute granularity keeps the label from changing the frame on every redraw. + if (r.none) + snprintf(r.age, sizeof(r.age), "NO DATA"); + else if (age < 60) + snprintf(r.age, sizeof(r.age), "NOW"); + else + snprintf(r.age, sizeof(r.age), "%luM AGO", static_cast(age / 60)); + return r; +} +void DisplayUi::begin(EpdiyHighlevelState *state) { + state_ = state; + pixels_ = epd_hl_get_framebuffer(state); +} +void DisplayUi::tick(uint64_t now_ms) { + if (rails_deadline_ms_ && now_ms >= rails_deadline_ms_) { + rails_deadline_ms_ = 0; + epd_poweroff(); + } +} +void DisplayUi::pixel(int x, int y, uint8_t c) { + if (x >= 0 && y >= 0 && x < width_ && y < height_) + epd_draw_pixel(x, y, c, pixels_); +} +void DisplayUi::rect(int x, int y, int w, int h, uint8_t c) { + for (int j = std::max(0, y); j < std::min(height_, y + h); ++j) + for (int i = std::max(0, x); i < std::min(width_, x + w); ++i) + epd_draw_pixel(i, j, c, pixels_); +} +void DisplayUi::frame(int x, int y, int w, int h, uint8_t c) { + rect(x, y, w, 2, c); + rect(x, y + h - 2, w, 2, c); + rect(x, y, 2, h, c); + rect(x + w - 2, y, 2, h, c); +} +void DisplayUi::line(int x, int y, int x2, int y2, uint8_t c, int thick, bool dash) { + int dx = abs(x2 - x), sx = x < x2 ? 1 : -1, dy = -abs(y2 - y), sy = y < y2 ? 1 : -1, + e = dx + dy; + for (;;) { + // Dashes are phased by position so that consecutive short segments form one pattern. + if (!dash || (dx >= -dy ? x : y) % 16 < 9) + rect(x - thick / 2, y - thick / 2, thick, thick, c); + if (x == x2 && y == y2) + break; + int e2 = 2 * e; + if (e2 >= dy) { + e += dy; + x += sx; + } + if (e2 <= dx) { + e += dx; + y += sy; + } + } +} +// y is the top of the digit height; the return value is the pen advance. +int DisplayUi::text(int x, int y, const char *s, int size, uint8_t c, int family, int spacing, + bool uppercase) { + const Font *f = font_for(size, family); + int start = x; + for (const unsigned char *p = reinterpret_cast(s); *p; ++p) { + const Glyph *g = glyph_for(f, *p, uppercase); + if (!g) + continue; + for (int j = 0; j < g->height; ++j) + for (int i = 0; i < g->width; ++i) { + int n = j * g->width + i; + if (f->bitmap[g->offset + n / 8] & (128 >> (n % 8))) + pixel(x + g->left + i, y + g->top + j, c); + } + x += g->advance + spacing; + } + return x - start; +} +void DisplayUi::text_right(int right, int y, const char *s, int size, uint8_t c, int family, + int spacing) { + text(right - text_width(s, size, family, spacing), y, s, size, c, family, spacing); +} +void DisplayUi::text_center(int x, int w, int y, const char *s, int size, uint8_t c, int family, + int spacing) { + text(x + (w - text_width(s, size, family, spacing)) / 2, y, s, size, c, family, spacing); +} +void DisplayUi::bar(int x, int y, int w, int h, int p, uint8_t fill, bool hatch, bool notches) { + rect(x, y, w, h, kTrack); + const int filled = w * std::clamp(p, 0, 100) / 100; + if (hatch) { + for (int j = 0; j < h; ++j) + for (int i = 0; i < filled; ++i) + pixel(x + i, y + j, (i + j) % 10 < 5 ? kInk : kPaper); + } else + rect(x, y, filled, h, fill); + // The 80% and 95% thresholds are gaps, which stay visible on both the track and the fill. + for (int mark : {80, 95}) + if (notches) + rect(x + w * mark / 100, y, 3, h, kPaper); +} +int DisplayUi::badge(int x, int center_y, const char *s, int size, bool filled, bool align_right) { + const int ascent = digit_height(size, 2), w = text_width(s, size, 2, 2) + 20, h = ascent + 16; + if (align_right) + x -= w; + if (filled) + rect(x, center_y - h / 2, w, h, kInk); + else + frame(x, center_y - h / 2, w, h, kInk); + text(x + 10, center_y - h / 2 + 8, s, size, filled ? kPaper : kInk, 2, 2); + return w; +} +// bottom is the baseline of the digits; the unit follows on the same baseline. +int DisplayUi::value(int x, int bottom, const char *digits, int size, int unit_size, uint8_t color, + int spacing, const char *unit) { + int advance = text(x, bottom - digit_height(size, 0), digits, size, color, 0, spacing); + if (*unit) + advance += text(x + advance + size / 24, bottom - digit_height(unit_size, 0), unit, + unit_size, color, 0, 0); + return advance; +} +void DisplayUi::battery(const View &v, int y, uint8_t ink) { + const int x = width_ - 91; + frame(x, y, 46, 22, ink); + rect(x + 48, y + 6, 3, 10, ink); + const int segments = v.battery < 0 ? 0 : (v.battery + 24) / 25; + for (int i = 0; i < 4; ++i) + rect(x + 5 + i * 9, y + 5, 7, 12, i < segments ? ink : kTrack); + // The bolt slot left of the battery stays reserved while not charging. + if (v.charging) { + const int bx = x - 17; + line(bx + 7, y + 1, bx + 1, y + 11, ink, 2); + line(bx + 1, y + 11, bx + 9, y + 11, ink, 2); + line(bx + 9, y + 11, bx + 3, y + 21, ink, 2); + } +} +void DisplayUi::status(const View &v, int y) { + battery(v, y, kSub); + // A healthy link shows no status; only exceptions are named. + const char *label = v.link.has_passkey ? "PAIRING" + : v.ota.phase != OtaPhase::Idle ? "UPDATING" + : !v.link.encrypted ? "OFFLINE" + : nullptr; + if (label) + badge(width_ - 91 - (v.charging ? 34 : 14), y + 11, label, 18, false, true); +} +void DisplayUi::header(const View &v, const char *title) { + const bool portrait = height_ > width_; + const int top = portrait ? 40 : 30, size = portrait ? 40 : 34; + const int baseline = top + (size + digit_height(size, 0)) / 2; + char time[16], date[32]; + format_clock(v, time, sizeof(time), date, sizeof(date)); + const int advance = + text(40, baseline - digit_height(size, 0), title ? title : time, size, kInk, 0, -1); + if (!title && v.clock_valid) + text(40 + advance + 18, baseline - digit_height(20, 2), date, 20, kSub, 2, 2); + status(v, top + (size - 22) / 2); + rect(40, top + size + (portrait ? 18 : 16), width_ - 80, 2, kInk); +} +void DisplayUi::service_title(const ServiceSummary &s, int x, int w, int top, int size) { + text(x, top + (size - digit_height(size, 0)) / 2, s.name, size, + s.none ? kSub : s.offline ? kDim : kInk, 0, -1); + const int center = top + size / 2; + text_right(x + w, center - digit_height(18, 2) / 2, s.age, 18, kSub, 2, 2); + if (s.tag) + badge(x + w - text_width(s.age, 18, 2, 2) - 14, center, s.tag, 18, true, true); +} +void DisplayUi::window_column(const ServiceSummary &s, bool primary, int x, int w, int bar_y) { + const WindowSummary &win = primary ? s.primary : s.secondary; + const int size = primary ? 120 : 60, box = size * 9 / 10, label_y = bar_y - 46 - box; + text(x, label_y + 5, win.label, 18, kSub, 2, 3); + if (!win.present) { + text(x, label_y + 40, "NOT REPORTED", 20, kSub, 2, 1); + line(x, bar_y + 6, x + w, bar_y + 6, kRule, 2, true); + text(x, bar_y + 32, "--", 20, kSub, 2, 1); + return; + } + if (win.warn) + badge(x + w, label_y + 12, "WARN", 16, false, true); + char b[12]; + snprintf(b, sizeof(b), "%d", win.percent); + value(x, bar_y - 16 - (box - digit_height(size, 0)) / 2, b, size, primary ? 46 : 26, + s.offline ? kDim : kInk, primary ? -5 : -2); + bar(x, bar_y, w, 14, win.percent, s.offline ? kRule : primary ? kInk : kDim, win.warn, true); + text(x, bar_y + 32, win.reset, primary ? 22 : 20, + !primary ? kSub : s.offline ? kDim : kInk, 2, 1); +} +int DisplayUi::service_portrait(const View &v, int index, int y, bool rule) { + const ServiceSummary s = summarize(v, index); + const int x = 40, w = width_ - 80, height = s.none ? 146 : 329; + if (s.tag) + rect(16, y, width_ - 32, height, kWash); + else if (rule) + rect(16, y, width_ - 32, 2, kRule); + service_title(s, x, w, y + 28, 40); + if (s.none) { + const int advance = text(x, y + 96, "WAITING FOR FIRST SAMPLE", 20, kSub, 2, 1); + line(x + advance + 18, y + 103, x + w, y + 103, kRule, 2, true); + return height; + } + // The primary and secondary windows share the width 1.4 : 1 and one bar baseline. + const int primary_w = (w - 40) * 14 / 24; + window_column(s, true, x, primary_w, y + 244); + window_column(s, false, x + primary_w + 40, w - primary_w - 40, y + 244); + return height; +} +void DisplayUi::service_landscape(const View &v, int index, int x, int w, int top, int bottom) { + const ServiceSummary s = summarize(v, index); + if (s.tag) + rect(x, top, w, bottom - top, kWash); + if (index) + rect(x, top, 2, bottom - top, kRule); + const int cx = x + 32, cw = w - 64; + service_title(s, cx, cw, top + 22, 34); + if (s.none) { + text(cx, top + 84, "WAITING FOR", 20, kSub, 2, 1); + text(cx, top + 114, "FIRST SAMPLE", 20, kSub, 2, 1); + line(cx, top + 162, cx + cw, top + 162, kRule, 2, true); + return; + } + const uint8_t ink = s.offline ? kDim : kInk; + char b[12]; + text(cx, top + 83, s.primary.label, 18, kSub, 2, 3); + if (s.primary.warn) + badge(cx + cw, top + 90, "WARN", 16, false, true); + snprintf(b, sizeof(b), "%d", s.primary.percent); + value(cx, top + 224 - (116 - digit_height(132, 0)) / 2, b, 132, 50, ink, -7); + bar(cx, top + 240, cw, 14, s.primary.percent, s.offline ? kRule : kInk, s.primary.warn, true); + text(cx, top + 272, s.primary.reset, 22, ink, 2, 1); + // The secondary window is one row along the bottom edge of the column. + const int center = bottom - 40, text_y = center - digit_height(18, 2) / 2; + int left = cx + text(cx, text_y, s.secondary.label, 18, kSub, 2, 3) + 14; + if (!s.secondary.present) { + left += text(left, text_y, "NOT REPORTED", 18, kSub, 2, 1) + 16; + line(left, center, cx + cw, center, kRule, 2, true); + return; + } + snprintf(b, sizeof(b), "%d", s.secondary.percent); + left += value(left, center + digit_height(36, 0) / 2, b, 36, 18, ink, -1) + 16; + const int right = cx + cw - text_width(s.secondary.reset, 18, 2, 1); + text(right, text_y, s.secondary.reset, 18, kSub, 2, 1); + bar(left, center - 5, right - 16 - left, 10, s.secondary.percent, s.offline ? kRule : kDim, + s.secondary.warn, false); +} +DisplayUi::TrendValue DisplayUi::trend_last(const View &v, int provider) const { + const uint32_t now = trend_now(v); + if (!v.sd) + return {}; + for (int i = v.trend_count - 1; i >= 0; --i) { + const int value = provider ? v.trend[i].claude : v.trend[i].codex; + if (value <= 100 && v.trend[i].epoch <= now && now - v.trend[i].epoch <= 86400) + return {v.trend[i].epoch, value}; + } + return {}; +} +void DisplayUi::trend_swatch(int x, int y, int provider) { + for (int i = 0; i < 22; ++i) + if (!provider || i % 11 < 7) + rect(x + i, y, 1, 4, kInk); +} +// Plots the last 24 hours of the short window in a w x h area whose bottom edge is 0%. +void DisplayUi::chart(const View &v, int x, int y, int w, int h) { + rect(x, y, w, h / 5, kTint); + line(x, y + h / 5, x + w, y + h / 5, kRule, 2, true); + rect(x, y + h / 2, w, 2, kTrack); + line(x + w / 2, y, x + w / 2, y + h, kTrack, 2, true); + rect(x, y + h, w, 2, kRule); + const uint32_t now = trend_now(v); + // The axis stays anchored to the stored snapshot between half-hour samples. + const uint32_t end = trend_end(v); + for (int provider = 0; provider < 2; ++provider) { + const TrendValue last = trend_last(v, provider); + if (last.percent < 0) + continue; + TrendValue previous; + const int count = v.sd ? v.trend_count : 0; + for (int i = 0; i <= count; ++i) { + const TrendValue point = i == count ? last + : TrendValue{v.trend[i].epoch, provider ? v.trend[i].claude : v.trend[i].codex}; + // Draw the final valid stored value once, including its endpoint marker. + if (i < count && point.epoch >= last.epoch) + continue; + if (point.percent > 100 || point.epoch > now || point.epoch > end || + now - point.epoch > 86400) { + previous = {}; + continue; + } + const int px = x + w - static_cast((end - point.epoch) * static_cast(w) / 86400), + py = y + h - point.percent * h / 100; + // Gaps longer than an hour are left open rather than interpolated. + if (previous.percent >= 0 && previous.epoch <= point.epoch && + point.epoch - previous.epoch <= 3600) { + const int prev_x = x + w - static_cast((end - previous.epoch) * static_cast(w) / 86400); + line(prev_x, y + h - previous.percent * h / 100, px, py, kInk, 3, provider); + } + if (i == count) { + rect(px - 2, py - 3, 5, 7, kInk); + rect(px - 3, py - 2, 7, 5, kInk); + } + previous = point; + } + } +} +void DisplayUi::home(const View &v) { + header(v); + const bool alarm[2] = {summarize(v, 0).tag != nullptr, summarize(v, 1).tag != nullptr}; + const bool none[2] = {summarize(v, 0).none, summarize(v, 1).none}; + const TrendValue last[2] = {trend_last(v, 0), trend_last(v, 1)}; + const bool empty = last[0].percent < 0 && last[1].percent < 0; + char b[12]; + char sampled[20], caption[32]; + trend_time(v, sampled, sizeof(sampled)); + snprintf(caption, sizeof(caption), "AS OF %s", sampled); + if (height_ > width_) { + int y = 100; + for (int i = 0; i < 2; ++i) + y += service_portrait(v, i, y, i && !alarm[0] && !alarm[1]); + const int x = 40, w = width_ - 220, top = y + 72, h = height_ - 77 - top; + rect(x, y, width_ - 80, 2, kRule); + text(x, y + 31, "LAST 24H / SHORT", 18, kSub, 2, 3); + text_right(width_ - 40, y + 32, empty ? "NO TREND DATA" : caption, 16, kSub, 2, 1); + chart(v, x, top, w, h); + text(x, height_ - 56, "-24H", 16, kSub, 2, 1); + text_center(x, w, height_ - 56, "-12H", 16, kSub, 2, 1); + text_right(x + w, height_ - 56, empty ? "--:--" : sampled, 16, kSub, 2, 1); + for (int provider = 0, row = 0; provider < 2; ++provider) { + if (last[provider].percent < 0) + continue; + const int lx = x + w + 20, ly = top + 12 + row++ * 100; + trend_swatch(lx, ly + 5, provider); + text(lx + 30, ly, provider ? "CLAUDE" : "CODEX", 16, kSub, 2, 1); + snprintf(b, sizeof(b), "%d", last[provider].percent); + value(lx, ly + 24 + digit_height(30, 0), b, 30, 16, kInk, -1); + char age[20]; + trend_age(trend_now(v), last[provider].epoch, age, sizeof(age)); + text(lx, ly + 58, age, 16, kSub, 2, 0); + } + return; + } + const int top = 82, bottom = height_ - 150; + // A service without data yields its width to the one that has some. + const int w0 = none[0] ? 340 : none[1] ? width_ - 16 - 340 : (width_ - 16) / 2; + service_landscape(v, 0, 8, w0, top, bottom); + service_landscape(v, 1, 8 + w0, none[1] ? 340 : width_ - 16 - w0, top, bottom); + rect(40, bottom, width_ - 80, 2, kRule); + text(40, bottom + 23, "LAST 24H", 18, kSub, 2, 3); + text(40, bottom + 48, empty ? "NO DATA" : "SHORT", 18, kSub, 2, 3); + text(40, height_ - 42, "-24H", 16, kSub, 2, 1); + chart(v, 184, bottom + 20, width_ - 418, 98); + const int lx = width_ - 210; + for (int provider = 0, row = 0; provider < 2; ++provider) { + if (last[provider].percent < 0) + continue; + const int ly = bottom + 12 + row++ * 44; + trend_swatch(lx, ly + 8, provider); + text(lx + 32, ly + 4, provider ? "CLAUDE" : "CODEX", 16, kSub, 2, 1); + snprintf(b, sizeof(b), "%d", last[provider].percent); + value(lx + 112, ly + 4 + digit_height(24, 0), b, 24, 16, kInk, 0); + char age[20]; + trend_age(trend_now(v), last[provider].epoch, age, sizeof(age)); + text(lx + 32, ly + 27, age, 16, kSub, 2, 0); + } + text(lx, height_ - 42, empty ? "--:--" : caption, 16, kSub, 2, 1); +} +// One usage summary row of the clock; x and w bound the text, the wash extends past them. +int DisplayUi::clock_row(const View &v, int index, int x, int w, int y, bool rule) { + const bool portrait = height_ > width_; + const ServiceSummary s = summarize(v, index); + const int height = (portrait ? 111 : 150) + (s.none ? 0 : 26); + const int right = portrait ? x + w + 20 : width_; + if (s.tag) + rect(x - 20, y, right - x + 20, height, kWash); + else if (rule) + rect(x - 20, y, right - x + 20, 2, kRule); + const uint8_t ink = s.none ? kSub : s.offline ? kDim : kInk; + const int advance = text(x, y + 30, s.name, 22, ink, 2, 3); + if (s.tag) + badge(x + advance + 12, y + 38, s.tag, 16, true, false); + char sub[64], b[12] = "--"; + if (s.none) + snprintf(sub, sizeof(sub), "WAITING FOR FIRST SAMPLE"); + else { + snprintf(sub, sizeof(sub), "%s%s%s%s", s.primary.label[0] == 'W' ? "WEEK / " : "", + s.primary.reset, s.offline ? " / " : "", s.offline ? s.age : ""); + snprintf(b, sizeof(b), "%d", s.primary.percent); + } + const char *unit = s.none ? "" : "%"; + const int size = portrait ? 64 : 72, unit_size = portrait ? 28 : 30; + const int bottom = y + (portrait ? 87 : 126), sub_y = bottom - digit_height(18, 2); + if (portrait) { + text(x, sub_y, sub, 18, kSub, 2, 1); + const int value_w = text_width(b, size, 0, -2) + (s.none ? 0 : size / 24 - 2 + + text_width(unit, unit_size, 0, 0)); + value(x + w - value_w, bottom, b, size, unit_size, ink, -2, unit); + } else { + value(x, bottom, b, size, unit_size, ink, -2, unit); + text_right(x + w, sub_y, sub, 18, kSub, 2, 1); + } + if (!s.none) + bar(x, bottom + 16, w, 10, s.primary.percent, s.offline ? kRule : kInk, s.primary.warn, + false); + return height; +} +void DisplayUi::clock(const View &v) { + const bool portrait = height_ > width_; + char time[16], date[32]; + format_clock(v, time, sizeof(time), date, sizeof(date)); + const bool alarm = summarize(v, 0).tag || summarize(v, 1).tag; + const int rows = (portrait ? 222 : 300) + (summarize(v, 0).none ? 0 : 26) + + (summarize(v, 1).none ? 0 : 26); + const int size = portrait ? 212 : 236, line_h = size * 85 / 100; + if (portrait) { + status(v, 40); + const int x = 44, w = width_ - 88, section = height_ - 44 - 41 - rows; + // Time and date are centered between the status row and the usage summary. + const int top = (62 + section - line_h - 70) / 2; + text(x - 8, top + (line_h - digit_height(size, 0)) / 2, time, size, kInk, 0, -8); + text(x, top + line_h + 42, date, 28, kSub, 2, 4); + rect(x, section, w, 2, kRule); + int y = section + 2; + for (int i = 0; i < 2; ++i) + y += clock_row(v, i, x, w, y, i && !alarm); + text(x, y + 22, "TAP OR LIFT TO WAKE", 16, kSub, 2, 2); + return; + } + const int column = width_ - 460, x = column + 36, w = width_ - 40 - x; + const int top = (height_ - line_h - 73) / 2; + text(30, top + (line_h - digit_height(size, 0)) / 2, time, size, kInk, 0, -9); + text(40, top + line_h + 42, date, 30, kSub, 2, 4); + rect(column, 36, 2, height_ - 72, kRule); + status(v, 36); + int y = (58 + height_ - 57 - rows) / 2; + for (int i = 0; i < 2; ++i) + y += clock_row(v, i, x, w, y, i && !alarm); + text(x, height_ - 52, "TAP OR LIFT TO WAKE", 16, kSub, 2, 2); +} +void DisplayUi::settings(const View &v) { + const bool portrait = height_ > width_; + header(v, "DISPLAY"); + const char *labels[] = {"AUTO UPDATE", "IDLE CLOCK", "ROTATION"}; + const char *subs[] = {"INTERVAL / SEC", "AFTER IDLE / MIN", "1.5S HOLD"}; + const char *values[][4] = {{"15", "30", "60", "120"}, + {"OFF", "5", "15", "30"}, + {"AUTO", "PORTRAIT", "LANDSCAPE", nullptr}}; + const int selected[] = {v.settings.refresh, v.settings.idle, v.settings.rotation}; + for (int row = 0; row < 3; ++row) { + const int top = option_box(width_, height_, row, 0).y - (portrait ? 85 : 0); + text(40, top + (portrait ? 6 : 14), labels[row], 30, kInk, 0, 0); + text(40, top + (portrait ? 46 : 54), subs[row], 18, kSub, 2, 1); + if (row < 2) + rect(40, top + (portrait ? 205 : 112), width_ - 80, 1, kRule); + for (int i = 0; i < (row == 2 ? 3 : 4); ++i) { + const Box box = option_box(width_, height_, row, i); + if (i == selected[row]) + rect(box.x, box.y, box.w, box.h, kInk); + else + frame(box.x, box.y, box.w, box.h, kInk); + text_center(box.x, box.w, box.y + 32, values[row][i], 24, + i == selected[row] ? kPaper : kInk, 2, 1); + } + } + const Box done = done_box(width_, height_); + text(40, done.y - 37, + v.save_error ? "SAVE FAILED / TAP TO RETRY" : "EACH TAP SAVES / HOLD 0.8S OR DONE TO EXIT", + 18, kSub, 2, 1); + rect(done.x, done.y, done.w, done.h, kInk); + text_center(done.x, done.w, done.y + (done.h - 18) / 2, "DONE", 24, kPaper, 2, 3); +} +void DisplayUi::pairing(const View &v) { + const bool portrait = height_ > width_; + const int pad = portrait ? 40 : 36, content = portrait ? 101 : 93; + header(v); + const int top = (content + height_ - pad - 24 - 376) / 2; + text(40, top + 6, "PAIRING CODE", 22, kSub, 2, 4); + char b[24]; + snprintf(b, sizeof(b), "%03lu %03lu", static_cast(v.link.passkey / 1000), + static_cast(v.link.passkey % 1000)); + text(40, top + 71, b, 124, kInk, 0, 2); + rect(40, top + 221, width_ - 80, 1, kRule); + text(40, top + 260, "ENTER THIS CODE", 30, kInk, 0, 0); + text(40, top + 302, "ON THE DESKTOP BRIDGE", 30, kInk, 0, 0); + text(40, top + 355, "DEVICE CONNECTS AFTER CONFIRMATION", 19, kSub, 2, 1); + text(40, height_ - pad - 19, v.device_name, 18, kSub, 2, 2); +} +void DisplayUi::ota(const View &v) { + const bool portrait = height_ > width_; + const int pad = portrait ? 40 : 36, content = portrait ? 101 : 93; + header(v); + char b[80]; + if (v.ota.phase == OtaPhase::Confirming) { + const Box deny = ota_button(width_, height_, 0), confirm = ota_button(width_, height_, 1); + const int top = (content + deny.y - 340) / 2; + text(40, top + 6, "FIRMWARE", 22, kSub, 2, 4); + text(40, top + 57, v.ota.version.c_str(), 52, kInk, 0, -1, false); + snprintf(b, sizeof(b), "%.1f MB / FROM DESKTOP BRIDGE", v.ota.size / 1000000.); + text(40, top + 124, b, 20, kSub, 2, 1); + rect(40, top + 200, width_ - 80, 1, kRule); + text(40, top + 249, "CONFIRM UPDATE", 56, kInk, 0, -1); + text(40, top + 320, "INSTALL STARTS ONLY AFTER CONFIRMATION", 19, kSub, 2, 1); + frame(deny.x, deny.y, deny.w, deny.h, kInk); + text_center(deny.x, deny.w, deny.y + 39, "DENY", 24, kInk, 2, 3); + rect(confirm.x, confirm.y, confirm.w, confirm.h, kInk); + text_center(confirm.x, confirm.w, confirm.y + 39, "CONFIRM", 24, kPaper, 2, 3); + text(40, height_ - pad - 19, "KEY2 PRESS CONFIRM / HOLD DENY", 18, kSub, 2, 1); + return; + } + const int gap = portrait ? 52 : 24, gap2 = portrait ? 56 : 28; + int y = (content + height_ - pad - (421 + gap + gap2)) / 2; + text(40, y + 6, "FIRMWARE", 22, kSub, 2, 4); + y += 47; + text(40, y + 10, v.ota.version.c_str(), 52, kInk, 0, -1, false); + y += 57 + gap; + // Progress advances in 5% cells so a whole transfer costs about twenty refreshes. + const int percent = ota_progress_percent(v.ota.offset, v.ota.size) / 5 * 5; + snprintf(b, sizeof(b), "%d", percent); + const int advance = text(40, y + 14, b, 144, kInk, 0, -6); + text(40 + advance + 8, y + 76, "%", 58, kInk, 0, 0); + y += 150; + const int pitch = width_ - 80 + 4; + for (int i = 0; i < 20; ++i) + rect(40 + i * pitch / 20, y, (i + 1) * pitch / 20 - i * pitch / 20 - 4, 26, + i < percent / 5 ? kInk : kTrack); + y += 48; + const char *phase = v.ota.phase == OtaPhase::Rebooting ? "RESTARTING" + : v.ota.phase == OtaPhase::Verifying ? "VERIFYING" + : "WRITING"; + snprintf(b, sizeof(b), "%s / %.1f MB / %.1f MB", phase, v.ota.offset / 1000000., + v.ota.size / 1000000.); + text(40, y + 6, b, 20, kSub, 2, 1); + y += 26 + gap2; + rect(40, y, width_ - 80, 1, kRule); + y += 27; + text(40, y + 5, "DO NOT UNPLUG OR POWER OFF", 28, kInk, 0, 0); + text(40, y + 46, "DEVICE RESTARTS AND RECONNECTS AUTOMATICALLY", 19, kSub, 2, 1); +} +TapTarget DisplayUi::hit(const View &v, int touch_x, int touch_y) const { + // The touch panel reports upright portrait coordinates regardless of the drawn rotation. + const bool portrait = !(v.orientation & 1); + const int width = portrait ? kTouchWidth : kTouchHeight, + height = portrait ? kTouchHeight : kTouchWidth; + int x = touch_x, y = touch_y; + switch (v.orientation & 3) { + case 1: + x = touch_y; + y = kTouchWidth - 1 - touch_x; + break; + case 2: + x = kTouchWidth - 1 - touch_x; + y = kTouchHeight - 1 - touch_y; + break; + case 3: + x = kTouchHeight - 1 - touch_y; + y = touch_x; + break; + } + const int scene = scene_of(v); + if (scene == 5) { + if (ota_button(width, height, 0).contains(x, y)) + return {TapTarget::OtaDeny}; + if (ota_button(width, height, 1).contains(x, y)) + return {TapTarget::OtaConfirm}; + } else if (scene == static_cast(Page::Settings)) { + if (done_box(width, height).contains(x, y)) + return {TapTarget::Done}; + for (int row = 0; row < 3; ++row) + for (int i = 0; i < (row == 2 ? 3 : 4); ++i) + if (option_box(width, height, row, i).contains(x, y)) + return {TapTarget::Option, row, i}; + } + return {}; +} +// Five rows of four candidates each for small text, bar tracks, rules, large digits and alarm +// washes, labeled with a code and the gray level as its hex digit tripled. Portrait only. +void DisplayUi::test_card() { + struct Sample { + const char *note; + uint8_t gray; + // Font family for T, thickness in pixels for R; unused otherwise. + int arg; + }; + struct Row { + const char *title; + Sample samples[4]; + }; + static const Row rows[] = { + {"T / SMALL TEXT 18PX", {{"555/500", 0x50, 1}, {"444/700", 0x40, 2}, {"222/700", 0x20, 2}, {"000/700", 0x00, 2}}}, + {"B / BAR TRACK", {{"DDD", 0xD0, 0}, {"CCC", 0xC0, 0}, {"BBB", 0xB0, 0}, {"999", 0x90, 0}}}, + {"R / RULE", {{"BBB 1PX", 0xB0, 1}, {"BBB 2PX", 0xB0, 2}, {"888 2PX", 0x80, 2}, {"666 2PX", 0x60, 2}}}, + {"N / LARGE DIGITS", {{"000", 0x00, 0}, {"222", 0x20, 0}, {"333", 0x30, 0}, {"444", 0x40, 0}}}, + {"W / ALARM WASH", {{"000", 0x00, 0}, {"EEE", 0xE0, 0}, {"DDD", 0xD0, 0}, {"CCC", 0xC0, 0}}}, + }; + const int w = 139; + for (int r = 0; r < 5; ++r) { + const Row &row = rows[r]; + const int top = 40 + r * 242, sy = top + 38; + text(40, top + 4, row.title, 18, 0x20, 2, 3); + for (int i = 0; i < 4; ++i) { + const Sample &s = row.samples[i]; + const int x = 40 + i * (w + 16); + switch (row.title[0]) { + case 'T': + text(x, sy + 28, "RESETS", 18, s.gray, s.arg, 1); + text(x, sy + 55, "4D 18H", 18, s.gray, s.arg, 1); + break; + case 'B': + rect(x, sy + 41, w, 14, s.gray); + rect(x, sy + 41, w * 45 / 100, 14, kInk); + break; + case 'R': + rect(x, sy + 48 - s.arg / 2, w, s.arg, s.gray); + break; + case 'N': + text(x, sy + 15, "98", 96, s.gray, 0, -4); + break; + default: + rect(x, sy + 15, w, 66, s.gray); + text(x + 14, sy + 37, "98%", 30, s.gray ? kInk : kPaper, 0, 0); + } + const char code[] = {row.title[0], static_cast('1' + i), 0}; + const int advance = text(x, sy + 111, code, 18, kInk, 2, 1); + text(x + advance + 8, sy + 112, s.note, 16, 0x40, 2, 0); + } + } +} +void DisplayUi::render(const View &v, bool baseline) { + // The upright portrait view is epdiy's inverted portrait; each quarter turn advances one step. + epd_set_rotation(static_cast((EPD_ROT_INVERTED_PORTRAIT + v.orientation) % 4)); + const bool portrait = !(v.orientation & 1); + width_ = portrait ? kTouchWidth : kTouchHeight; + height_ = portrait ? kTouchHeight : kTouchWidth; + epd_hl_set_all_white(state_); + const int scene = scene_of(v); + if (scene == 6) + test_card(); + else if (scene == 3 || scene == 5) + ota(v); + else if (scene == 4) + pairing(v); + else if (v.page == Page::Clock) + clock(v); + else if (v.page == Page::Settings) + settings(v); + else + home(v); + if (v.ota.error != OtaError::None && v.ota.phase == OtaPhase::Idle) { + const int pad = portrait ? 40 : 36; + rect(0, height_ - pad - 34, width_, pad + 34, kPaper); + text(40, height_ - pad - 19, ota_error_presentation(v.ota.error).title, 19, kInk, 2, 1); + } + const bool first = rendered_scene_ < 0; + const bool full = baseline || scene != rendered_scene_ || v.orientation != rendered_orientation_; + if (!full && !memcmp(state_->front_fb, state_->back_fb, epd_width() / 2 * epd_height())) + return; + read_pico_epd_use_scan(READ_PICO_EPD_SCAN_FULL); + epd_lcd_set_prefill_lines(32); + epd_poweron(); + if (!read_pico_rails_on()) { + // epdiy reports success without panel rails; the frame must not count as displayed. + rendered_scene_ = -1; + ESP_LOGE("read_pico", "Panel rails did not power up"); + return; + } + const int64_t start = esp_timer_get_time(); + const char *mode = "GL16"; + EpdDrawError result; + if (first) { + // Startup and failed refreshes require a known optical baseline before drawing. + epd_clear(); + result = epd_hl_update_screen_from_white(state_, MODE_GC16, kPanelTemperatureC); + mode = "GC16 from white"; + soft_refreshes_ = 0; + } else if (full || soft_refreshes_ + 1 >= kSoftRefreshesPerBaseline) { + result = epd_hl_update_screen_full(state_, MODE_GC16, kPanelTemperatureC); + mode = "GC16"; + soft_refreshes_ = 0; + } else if (du_reaches(state_->front_fb, state_->back_fb, epd_width() / 2 * epd_height())) { + // DU drives the changed pixels alone, so the rest of the frame does not flicker. + result = epd_hl_update_screen(state_, MODE_DU, kPanelTemperatureC); + mode = "DU"; + ++soft_refreshes_; + } else { + // GL16 drives every pixel; skipping unchanged white pixels leaves a gray floor. + result = epd_hl_update_screen_full(state_, MODE_GL16, kPanelTemperatureC); + ++soft_refreshes_; + } + if (result & EPD_DRAW_EMPTY_LINE_QUEUE) { + // A starved line queue leaves a torn frame; redraw from white at the safe pixel clock. + read_pico_epd_set_pclk(READ_PICO_EPD_PCLK_MIN_MHZ); + epd_clear(); + result = epd_hl_update_screen_from_white(state_, MODE_GC16, kPanelTemperatureC); + soft_refreshes_ = 0; + ESP_LOGW("read_pico", "Line queue underrun; pixel clock lowered to %d MHz", + READ_PICO_EPD_PCLK_MIN_MHZ); + } + rails_deadline_ms_ = esp_timer_get_time() / 1000 + kRailsIdleMs; + if (result != EPD_DRAW_SUCCESS) { + rendered_scene_ = -1; + ESP_LOGE("read_pico", "Display refresh failed: 0x%x", static_cast(result)); + return; + } + rendered_scene_ = scene; + rendered_orientation_ = v.orientation; + ESP_LOGI("read_pico", "REFRESH %s %lld ms", mode, (esp_timer_get_time() - start) / 1000); +} +} // namespace usage_panel::read_pico diff --git a/firmware/targets/mindreset_read_pico/main/display_ui.hpp b/firmware/targets/mindreset_read_pico/main/display_ui.hpp new file mode 100644 index 0000000..f8702af --- /dev/null +++ b/firmware/targets/mindreset_read_pico/main/display_ui.hpp @@ -0,0 +1,113 @@ +#pragma once +#include "epd_highlevel.h" +#include "usage_ble/app_events.hpp" +#include "usage_core/usage_state.hpp" +#include "usage_ota/session.hpp" +#include "usage_protocol/time_sync.hpp" +#include +#include +namespace usage_panel::read_pico { +enum class Page { Home, Clock, Settings }; +struct Settings { + // Option indices. refresh: 15, 30, 60, 120 s. idle: off, 5, 15, 30 min. + // rotation: automatic, portrait, landscape. + uint8_t refresh = 1, idle = 2, rotation = 0; +}; +struct TrendPoint { + uint32_t epoch; + uint8_t codex, claude; + uint8_t reserved[2]{}; +}; +struct View { + UsageModel model; + LinkUpdate link{}; + LocalCalendarTime calendar{}; + bool clock_valid = false, power_valid = false, charging = false; + int battery = -1; + Page page = Page::Home; + // Replaces the page with the gray-level test card; set only by diagnostic views. + bool test_card = false; + Settings settings{}; + // Quarter turns from the upright portrait view; odd values are landscape. + uint8_t orientation = 0; + bool sd = false, save_error = false; + // Alarm level per service: 0 none, 1 from 80%, 2 from 95%. + std::array warning{}; + std::array trend{}; + int trend_count = 0; + OtaSnapshot ota; + uint64_t now_ms = 0; + // Current UTC from the board clock; zero until a valid clock is available. + uint32_t utc_epoch = 0; + char device_name[24]{}; +}; +struct TapTarget { + enum Kind { None, Option, Done, OtaConfirm, OtaDeny } kind = None; + int row = 0, index = 0; +}; +class DisplayUi { + public: + /** Binds the epdiy state owned by the board; the first render clears the panel. */ + void begin(EpdiyHighlevelState *state); + /** + * Draws the view and refreshes the panel when the frame changed. The call blocks for the + * refresh. baseline requests a flashing GC16 refresh even when the scene is unchanged. + */ + void render(const View &view, bool baseline = false); + /** Maps a touch in the panel's upright portrait coordinates to a control of the view. */ + TapTarget hit(const View &view, int touch_x, int touch_y) const; + /** Powers the high-voltage rails down once no refresh has run for the idle timeout. */ + void tick(uint64_t now_ms); + /** True after a successful frame refresh, until a subsequent refresh fails. */ + bool ready() const { return rendered_scene_ >= 0; } + + private: + EpdiyHighlevelState *state_ = nullptr; + uint8_t *pixels_ = nullptr; + int width_ = 684, height_ = 1216; + int rendered_scene_ = -1, rendered_orientation_ = -1; + unsigned soft_refreshes_ = 0; + uint64_t rails_deadline_ms_ = 0; + void pixel(int x, int y, uint8_t color); + void rect(int x, int y, int w, int h, uint8_t color); + void frame(int x, int y, int w, int h, uint8_t color); + void line(int x, int y, int x2, int y2, uint8_t color, int thickness = 1, bool dashed = false); + // family: 0 Archivo 700, 1 JetBrains Mono 500, 2 JetBrains Mono 700. + int text(int x, int y, const char *value, int size, uint8_t color, int family = 1, + int spacing = 1, bool uppercase = true); + void text_right(int right, int y, const char *value, int size, uint8_t color, int family = 1, + int spacing = 1); + void text_center(int x, int w, int y, const char *value, int size, uint8_t color, + int family = 1, int spacing = 1); + struct WindowSummary; + struct ServiceSummary; + static ServiceSummary summarize(const View &v, int index); + void bar(int x, int y, int w, int h, int percent, uint8_t fill, bool hatch, bool notches); + /** Draws a filled or outlined label vertically centered on center_y; returns its width. */ + int badge(int x, int center_y, const char *label, int size, bool filled, bool align_right); + int value(int x, int bottom, const char *digits, int size, int unit_size, uint8_t color, + int spacing, const char *unit = "%"); + void battery(const View &v, int y, uint8_t ink); + void status(const View &v, int y); + void header(const View &v, const char *title = nullptr); + void service_title(const ServiceSummary &s, int x, int w, int top, int size); + void window_column(const ServiceSummary &s, bool primary, int x, int w, int bar_y); + int service_portrait(const View &v, int index, int y, bool rule); + void service_landscape(const View &v, int index, int x, int w, int top, int bottom); + struct TrendValue { + uint32_t epoch = 0; + int percent = -1; + }; + /** Latest stored short-window sample within the last 24 hours. */ + TrendValue trend_last(const View &v, int provider) const; + void trend_swatch(int x, int y, int provider); + void chart(const View &v, int x, int y, int w, int h); + void home(const View &v); + int clock_row(const View &v, int index, int x, int w, int y, bool rule); + void clock(const View &v); + void settings(const View &v); + void pairing(const View &v); + void ota(const View &v); + void test_card(); +}; +} // namespace usage_panel::read_pico diff --git a/firmware/targets/mindreset_read_pico/main/fonts.cpp b/firmware/targets/mindreset_read_pico/main/fonts.cpp new file mode 100644 index 0000000..4655a5c --- /dev/null +++ b/firmware/targets/mindreset_read_pico/main/fonts.cpp @@ -0,0 +1,73 @@ +#include "fonts.hpp" +static const uint8_t f0_16_bits[]={99,24,198,49,140,3,24,108,217,179,96,0,0,0,0,0,0,13,129,48,255,31,225,176,38,31,227,252,50,6,64,216,0,8,4,15,143,230,179,192,248,30,107,63,143,129,0,128,60,48,204,193,153,3,54,7,216,7,47,0,251,3,102,4,204,25,152,97,224,31,3,254,28,48,224,3,207,30,121,195,12,24,112,193,254,7,192,102,102,0,0,0,0,8,99,12,113,134,24,97,135,12,48,96,128,96,131,12,24,97,134,24,97,140,48,134,0,16,169,227,197,66,0,0,0,0,0,12,3,0,192,48,127,159,224,192,48,12,0,99,8,68,0,121,224,0,0,0,99,0,24,132,99,16,140,98,17,140,0,30,15,199,57,134,97,152,102,25,134,115,143,193,224,6,15,131,224,24,6,1,128,96,24,6,15,227,248,30,15,199,57,134,3,129,192,224,112,56,31,231,248,30,31,230,24,14,15,3,192,57,134,115,143,193,224,12,1,160,100,13,131,48,102,24,195,254,127,192,96,12,0,63,15,195,0,248,127,12,224,25,134,115,143,193,224,30,15,199,49,184,127,28,230,25,134,115,143,193,224,127,159,224,48,24,6,3,0,192,96,24,6,1,128,30,31,230,25,206,63,15,199,57,134,115,143,193,224,30,15,199,57,134,115,143,225,217,134,115,143,193,224,48,192,0,0,0,12,48,99,0,0,0,12,97,8,128,0,128,224,241,240,96,30,1,240,30,0,128,127,159,224,1,254,127,128,0,0,64,28,3,192,62,1,129,227,225,224,64,0,30,15,231,57,134,3,129,192,224,48,0,3,0,192,7,224,31,248,59,204,39,228,102,98,64,98,67,226,71,226,70,102,71,252,103,56,48,0,31,192,15,192,14,0,240,31,1,184,25,131,152,49,199,252,127,230,14,224,96,127,143,249,131,48,103,248,255,24,51,6,96,207,249,254,0,15,131,252,48,231,6,96,6,0,96,7,6,48,227,252,15,128,127,7,252,96,198,14,96,102,6,96,102,14,96,199,252,127,0,127,207,249,128,48,7,248,255,24,3,0,96,15,249,255,0,127,159,230,1,128,127,31,230,1,128,96,24,6,0,15,129,255,12,28,224,102,0,49,249,143,206,6,48,113,255,131,236,96,204,25,131,48,103,252,255,152,51,6,96,204,25,131,0,102,102,102,102,102,96,1,128,96,24,6,1,128,96,25,134,115,159,193,224,97,204,49,142,51,135,224,252,31,195,156,97,140,57,131,128,96,24,6,1,128,96,24,6,1,128,96,31,231,248,120,115,199,158,60,249,230,203,54,217,182,204,246,103,51,57,153,204,112,103,6,120,103,198,110,102,118,103,230,62,97,230,14,96,224,15,129,255,12,28,224,102,3,48,25,128,206,6,48,113,255,3,224,127,143,249,131,48,102,12,255,159,195,0,96,12,1,128,0,15,129,255,12,28,224,102,3,48,25,128,206,6,48,113,255,3,240,1,192,14,0,127,143,249,131,48,102,12,255,159,227,12,97,140,57,131,0,31,7,241,135,56,7,224,63,0,243,6,112,199,248,124,0,127,207,248,24,3,0,96,12,1,128,48,6,0,192,24,0,96,204,25,131,48,102,12,193,152,51,6,113,199,240,124,0,224,236,25,195,56,227,24,115,6,224,216,31,1,192,56,0,227,134,99,198,99,206,115,204,118,204,54,204,54,124,62,120,60,120,28,120,28,56,112,195,28,59,129,240,15,0,224,31,1,184,49,135,28,224,224,112,99,14,57,193,216,15,128,240,6,0,96,6,0,96,6,0,127,207,248,14,3,128,112,28,7,0,192,56,15,249,255,0,60,227,12,48,195,12,48,195,12,48,227,192,198,16,134,48,132,49,132,33,128,241,195,12,48,195,12,48,195,12,49,207,0,4,3,1,224,104,19,12,195,16,0,0,0,0,0,0,0,255,255,70,0,0,0,0,0,30,15,194,48,124,63,28,198,49,254,57,128,96,24,6,1,184,127,28,230,25,134,97,156,231,241,184,30,15,199,57,128,96,24,71,24,252,30,0,1,128,96,24,118,63,156,230,25,134,97,156,227,248,118,30,15,199,25,254,127,152,7,24,252,30,0,28,120,195,231,198,12,24,48,96,193,128,0,64,24,126,31,195,24,99,15,225,248,48,7,240,255,48,103,252,127,0,96,24,6,1,188,127,156,230,25,134,97,152,102,25,134,102,6,102,102,102,102,49,128,99,24,198,49,140,99,57,128,96,48,24,12,102,115,113,240,248,126,51,152,204,112,102,102,102,102,102,102,110,113,255,231,57,152,198,99,25,140,102,49,152,198,99,24,111,31,231,57,134,97,152,102,25,134,97,128,30,15,199,25,134,97,152,103,24,252,30,0,110,31,199,57,134,97,152,103,57,252,110,24,6,1,128,29,143,231,57,134,97,152,103,56,254,29,128,96,24,6,0,217,243,134,12,24,48,96,192,62,63,152,207,3,224,57,140,254,62,0,48,97,243,227,6,12,24,48,120,112,97,152,102,25,134,97,152,103,57,254,61,128,227,177,152,206,195,97,176,240,56,28,0,199,63,57,153,204,222,102,218,30,240,247,135,60,24,192,99,157,195,224,120,28,15,131,97,220,227,128,225,177,156,198,99,97,240,120,56,28,14,30,14,0,127,63,131,129,129,192,192,192,254,127,0,24,227,12,48,195,56,112,195,12,48,225,128,102,102,102,102,102,102,102,96,97,195,12,48,195,135,24,195,12,49,198,0,56,159,228,112,0,0,0,0,0}; +static const Glyph f0_16_glyphs[]={{0,3,0,0,11,3,32},{0,5,11,0,0,5,33},{7,7,11,0,0,7,34},{17,11,11,0,0,11,35},{33,9,13,0,-1,9,36},{48,15,11,0,0,15,37},{69,13,11,0,0,13,38},{87,4,11,0,0,4,39},{93,6,15,0,-1,6,40},{105,6,15,0,-1,6,41},{117,7,11,0,0,7,42},{127,10,9,0,2,10,43},{139,5,5,0,9,5,44},{143,6,6,0,5,6,45},{148,5,2,0,9,5,46},{150,5,13,0,-1,5,47},{159,10,11,0,0,10,48},{173,10,11,0,0,10,49},{187,10,11,0,0,10,50},{201,10,11,0,0,10,51},{215,11,11,0,0,11,52},{231,10,11,0,0,10,53},{245,10,11,0,0,10,54},{259,10,11,0,0,10,55},{273,10,11,0,0,10,56},{287,10,11,0,0,10,57},{301,6,9,0,2,6,58},{308,5,12,0,2,5,59},{316,10,9,0,2,10,60},{328,10,7,0,4,10,61},{337,10,9,0,2,10,62},{349,10,11,0,0,10,63},{363,16,14,0,0,16,64},{391,12,11,0,0,12,65},{408,11,11,0,0,11,66},{424,12,11,0,0,12,67},{441,12,11,0,0,12,68},{458,11,11,0,0,11,69},{474,10,11,0,0,10,70},{488,13,11,0,0,13,71},{506,11,11,0,0,11,72},{522,4,11,0,0,4,73},{528,10,11,0,0,10,74},{542,11,11,0,0,11,75},{558,10,11,0,0,10,76},{572,13,11,0,0,13,77},{590,12,11,0,0,12,78},{607,13,11,0,0,13,79},{625,11,11,0,0,11,80},{641,13,13,0,0,13,81},{663,11,11,0,0,11,82},{679,11,11,0,0,11,83},{695,11,11,0,0,11,84},{711,11,11,0,0,11,85},{727,11,11,0,0,11,86},{743,16,11,0,0,15,87},{765,12,11,0,0,11,88},{782,12,11,-1,0,11,89},{799,11,11,0,0,11,90},{815,6,15,0,-1,6,91},{827,5,13,0,-1,5,92},{836,6,15,0,-1,6,93},{848,10,12,0,-1,10,94},{863,8,3,0,11,8,95},{866,4,12,0,-1,4,96},{872,10,9,0,2,10,97},{884,10,12,0,-1,10,98},{899,10,9,0,2,10,99},{911,10,12,0,-1,10,100},{926,10,9,0,2,10,101},{938,7,12,0,-1,7,102},{949,11,14,0,0,11,103},{969,10,12,0,-1,10,104},{984,4,12,0,-1,4,105},{990,5,15,-1,-1,4,106},{1000,9,12,0,-1,9,107},{1014,4,12,0,-1,4,108},{1020,14,9,0,2,14,109},{1036,10,9,0,2,10,110},{1048,10,9,0,2,10,111},{1060,10,12,0,2,10,112},{1075,10,12,0,2,10,113},{1090,7,10,0,1,7,114},{1099,9,9,0,2,9,115},{1110,7,11,0,0,7,116},{1120,10,9,0,2,10,117},{1132,9,9,0,2,9,118},{1143,13,9,0,2,13,119},{1158,10,9,0,2,9,120},{1170,9,12,0,2,9,121},{1184,9,9,0,2,9,122},{1195,6,15,0,-1,6,123},{1207,4,15,0,-1,4,124},{1215,6,15,0,-1,6,125},{1227,10,7,0,4,10,126}}; +static const uint8_t f0_18_bits[]={56,227,12,48,195,12,48,0,14,56,126,126,126,102,102,0,0,0,0,0,0,0,0,12,192,204,12,195,254,63,225,152,25,129,152,127,199,252,51,3,48,51,0,4,1,1,225,254,117,157,3,240,126,5,221,103,248,124,4,1,0,0,60,8,51,12,25,140,12,196,6,102,3,246,0,242,0,3,60,3,55,1,25,129,140,193,134,97,129,224,15,128,255,131,142,28,24,56,0,121,227,231,156,28,112,113,193,135,14,15,248,31,128,115,156,198,0,0,0,0,0,0,12,48,97,131,14,28,56,112,225,193,131,3,6,6,96,192,193,195,131,6,14,28,48,96,195,134,24,48,16,169,241,199,202,4,0,0,0,0,0,0,12,1,128,48,6,7,252,255,131,0,96,12,1,128,99,156,66,32,0,124,248,0,0,0,0,99,156,8,97,134,16,195,8,33,134,16,67,0,0,31,7,240,198,56,231,28,227,156,115,142,113,206,56,198,31,193,240,7,3,224,252,3,128,112,14,1,192,56,7,0,224,28,31,227,252,31,7,241,199,56,230,28,3,128,224,120,30,7,129,224,63,231,252,31,7,241,199,48,224,28,31,3,224,14,1,206,57,199,31,193,240,14,0,192,12,129,216,25,131,184,51,134,56,127,231,254,3,128,56,3,128,63,135,240,192,24,3,240,127,12,112,14,1,206,57,199,31,193,240,31,7,240,199,56,231,0,254,31,227,142,113,206,56,199,31,193,240,127,207,248,6,1,128,112,12,3,128,96,12,3,128,112,14,1,192,31,7,241,199,56,231,28,127,15,227,142,113,206,57,199,31,193,240,31,7,241,198,56,231,28,227,143,240,254,1,198,56,230,31,193,240,56,227,0,0,0,12,56,224,56,227,0,0,0,12,56,225,4,32,0,0,0,56,62,31,7,128,224,31,0,124,3,192,16,127,207,248,0,0,7,252,255,128,0,0,0,3,128,62,0,248,1,224,14,7,193,240,56,2,0,31,7,241,198,56,226,24,7,1,192,112,14,1,128,0,7,0,224,3,248,1,255,192,120,28,28,249,195,63,152,198,113,152,14,51,15,198,99,248,204,103,25,156,230,25,255,131,61,224,56,0,3,252,0,31,128,7,0,124,3,224,27,1,220,12,224,227,135,28,63,227,255,156,28,192,110,3,128,127,199,254,112,103,7,112,103,252,127,199,14,112,103,7,112,231,254,127,192,15,192,255,14,28,224,103,3,56,1,192,14,0,112,51,129,142,28,63,192,252,0,127,131,254,28,56,224,199,7,56,57,193,206,14,112,115,131,28,56,255,135,248,0,127,231,254,112,7,0,112,7,252,127,199,0,112,7,0,112,7,254,127,224,127,207,249,192,56,7,0,224,31,243,252,112,14,1,192,56,7,0,7,192,127,195,135,140,14,112,1,192,7,31,156,126,112,57,192,227,135,135,254,15,216,112,115,131,156,28,224,231,7,63,249,255,206,14,112,115,131,156,28,224,231,7,0,115,156,231,57,206,115,156,231,0,1,192,56,7,0,224,28,3,128,112,14,1,206,57,199,31,193,240,112,243,143,28,112,231,7,112,63,129,252,15,240,115,131,142,28,56,225,199,7,0,112,14,1,192,56,7,0,224,28,3,128,112,14,1,192,63,231,252,120,60,248,121,241,243,227,231,199,207,207,157,183,59,110,119,220,239,185,206,115,156,231,57,192,112,115,195,158,28,248,231,231,63,57,221,206,254,115,243,143,156,60,225,231,7,0,7,192,127,195,135,140,14,112,25,192,103,1,220,6,112,24,192,227,135,135,252,15,192,127,199,254,112,231,7,112,119,14,127,231,252,112,7,0,112,7,0,112,0,7,192,127,195,135,140,14,112,25,192,103,1,220,6,112,24,192,227,135,135,252,7,224,3,192,7,128,127,199,254,112,231,7,112,119,14,127,231,252,113,199,12,112,231,6,112,112,15,131,254,48,231,6,56,3,248,31,192,126,0,247,6,112,227,254,15,128,127,243,255,129,192,14,0,112,3,128,28,0,224,7,0,56,1,192,14,0,112,0,112,115,131,156,28,224,231,7,56,57,193,206,14,112,115,131,142,56,127,192,248,0,224,115,131,156,24,225,195,142,28,96,231,3,184,29,128,252,3,224,30,0,112,0,225,195,152,120,231,30,57,207,140,115,227,12,221,195,183,112,236,216,63,62,7,143,129,227,224,120,112,30,28,0,112,113,199,14,48,59,128,248,7,192,60,1,240,31,193,206,14,56,225,239,7,0,112,115,131,14,56,115,129,220,7,192,60,0,224,7,0,56,1,192,14,0,112,0,127,231,254,1,192,56,7,128,112,14,0,224,28,3,192,56,7,254,127,224,60,120,225,195,135,14,28,56,112,225,195,135,15,30,195,4,24,97,130,12,48,193,6,24,96,128,124,248,112,225,195,135,14,28,56,112,225,195,159,62,6,0,224,15,1,176,25,131,24,49,128,0,0,0,0,0,0,0,0,0,0,127,255,224,97,128,0,0,0,0,0,0,0,31,3,248,113,128,28,31,195,252,113,199,60,127,227,206,112,14,1,192,59,135,248,227,156,115,142,113,206,57,199,63,198,240,31,7,241,199,56,231,0,224,28,115,142,63,131,224,1,192,56,7,15,227,252,227,156,115,142,113,206,57,199,31,225,236,31,7,241,199,56,231,252,255,156,3,142,63,131,224,30,30,56,126,126,56,56,56,56,56,56,56,56,0,96,6,15,227,252,56,195,142,31,193,248,48,3,0,63,227,254,112,119,254,63,192,112,14,1,192,59,135,248,231,28,115,142,113,206,57,199,56,231,28,115,128,231,57,206,115,156,231,0,56,224,14,56,227,142,56,227,142,56,231,28,112,28,7,1,206,115,157,199,225,248,126,29,199,57,206,113,192,115,156,231,57,206,115,156,231,0,111,56,127,252,115,204,115,140,115,140,115,140,115,140,115,140,115,140,115,140,103,15,241,198,56,231,28,227,156,115,142,113,206,56,31,7,249,199,56,103,14,225,220,51,142,63,195,224,111,15,241,199,56,231,28,227,156,115,142,127,143,225,192,56,7,0,30,199,249,199,56,231,28,227,156,115,142,63,195,184,7,0,224,28,110,249,195,135,14,28,56,112,224,31,31,231,25,192,63,3,224,29,198,63,135,192,56,112,227,231,199,14,28,56,112,225,225,192,113,206,57,199,56,231,28,227,156,115,158,127,199,184,225,216,103,57,206,51,12,195,224,120,30,7,0,227,141,199,25,158,115,188,199,109,134,219,15,60,30,120,28,240,56,224,113,206,112,236,15,129,224,60,15,193,156,115,156,56,225,216,103,24,206,59,14,193,240,120,14,3,129,193,224,112,0,127,159,224,112,56,14,7,1,128,224,127,159,224,28,120,225,195,135,12,120,112,96,225,195,135,15,14,102,102,102,102,102,102,102,102,97,195,142,56,227,135,24,227,142,56,231,24,0,7,9,255,33,192,0,0,0,0,0}; +static const Glyph f0_18_glyphs[]={{0,4,0,0,13,4,32},{0,6,13,0,0,6,33},{10,8,13,0,0,8,34},{23,12,13,0,0,12,35},{43,10,15,0,0,10,36},{62,17,13,0,0,17,37},{90,14,13,0,0,14,38},{113,5,13,0,0,5,39},{122,7,16,0,0,7,40},{136,7,16,0,0,7,41},{150,7,13,0,0,7,42},{162,11,10,0,3,11,43},{176,5,7,0,10,5,44},{181,7,6,0,7,7,45},{187,5,3,0,10,5,46},{189,6,15,0,0,5,47},{201,11,13,0,0,11,48},{219,11,13,0,0,11,49},{237,11,13,0,0,11,50},{255,11,13,0,0,11,51},{273,12,13,0,0,12,52},{293,11,13,0,0,11,53},{311,11,13,0,0,11,54},{329,11,13,0,0,11,55},{347,11,13,0,0,11,56},{365,11,13,0,0,11,57},{383,6,10,0,3,6,58},{391,6,14,0,3,6,59},{402,11,10,0,3,11,60},{416,11,8,0,5,11,61},{427,12,10,0,3,12,62},{442,11,13,0,0,11,63},{460,19,16,0,0,19,64},{498,13,13,0,0,13,65},{520,12,13,0,0,12,66},{540,13,13,0,0,13,67},{562,13,13,0,0,13,68},{584,12,13,0,0,12,69},{604,11,13,0,0,11,70},{622,14,13,0,0,14,71},{645,13,13,0,0,13,72},{667,5,13,0,0,5,73},{676,11,13,0,0,11,74},{694,13,13,0,0,13,75},{716,11,13,0,0,11,76},{734,15,13,0,0,15,77},{759,13,13,0,0,13,78},{781,14,13,0,0,14,79},{804,12,13,0,0,12,80},{824,14,15,0,0,14,81},{851,12,13,0,0,12,82},{871,12,13,0,0,12,83},{891,13,13,0,0,13,84},{913,13,13,0,0,13,85},{935,13,13,0,0,12,86},{957,18,13,0,0,17,87},{987,13,13,0,0,13,88},{1009,13,13,0,0,12,89},{1031,12,13,0,0,12,90},{1051,7,16,0,0,7,91},{1065,6,15,0,0,5,92},{1077,7,16,0,0,7,93},{1091,12,13,0,0,12,94},{1111,9,3,0,13,9,95},{1115,5,13,0,0,4,96},{1124,12,10,0,3,12,97},{1139,11,13,0,0,11,98},{1157,11,10,0,3,11,99},{1171,11,13,0,0,11,100},{1189,11,10,0,3,11,101},{1203,8,13,0,0,8,102},{1216,12,15,0,1,12,103},{1239,11,13,0,0,11,104},{1257,5,13,0,0,5,105},{1266,6,16,-1,0,5,106},{1278,10,13,0,0,10,107},{1295,5,13,0,0,5,108},{1304,16,10,0,3,16,109},{1324,11,10,0,3,11,110},{1338,11,10,0,3,11,111},{1352,11,13,0,3,11,112},{1370,11,13,0,3,11,113},{1388,7,10,0,3,7,114},{1397,10,10,0,3,10,115},{1410,7,13,0,0,7,116},{1422,11,10,0,3,11,117},{1436,10,10,0,3,10,118},{1449,15,10,0,3,14,119},{1468,11,10,0,3,10,120},{1482,10,13,0,3,10,121},{1499,10,10,0,3,10,122},{1512,7,16,0,0,7,123},{1526,4,16,0,0,4,124},{1534,6,16,0,0,6,125},{1546,11,8,0,5,11,126}}; +static const uint8_t f0_24_bits[]={60,112,225,195,135,14,28,56,112,225,192,0,14,28,56,115,156,231,57,206,115,156,231,48,0,0,0,0,0,0,0,0,0,0,0,0,0,7,24,14,112,24,224,49,128,231,135,255,143,255,6,56,12,96,56,193,255,227,255,199,255,131,24,14,48,28,224,49,192,2,0,16,0,128,63,131,255,61,121,233,207,192,63,192,255,128,252,4,231,167,63,248,255,129,248,2,0,16,0,128,15,0,192,63,3,128,231,6,1,134,24,3,12,112,6,24,192,14,115,0,15,206,0,15,56,224,0,99,224,1,206,224,7,24,192,12,49,128,56,99,0,224,198,1,129,252,7,0,240,7,240,7,254,3,255,192,240,120,56,14,14,0,3,192,0,126,62,31,143,143,227,231,128,225,192,56,112,30,30,7,131,255,192,127,224,7,224,0,113,199,28,113,199,0,0,0,0,0,0,0,0,6,7,7,3,131,129,192,224,240,120,56,28,14,7,131,192,224,112,56,14,7,1,192,96,96,24,14,3,129,192,224,120,28,14,7,3,129,192,224,112,56,56,28,14,14,6,6,0,12,3,6,216,252,30,15,198,216,48,12,0,0,0,0,0,0,0,0,0,0,0,3,128,3,128,3,128,3,128,3,128,63,252,63,252,63,252,3,128,3,128,3,128,3,128,3,128,56,112,224,65,135,12,0,126,126,126,0,0,0,0,0,56,112,224,6,6,14,12,12,28,28,24,24,56,48,48,48,112,96,96,96,224,192,15,192,127,131,255,14,28,120,121,225,231,3,156,14,112,57,192,231,3,158,30,120,120,225,195,255,7,248,15,192,1,128,30,3,248,15,224,3,128,14,0,56,0,224,3,128,14,0,56,0,224,3,128,14,3,255,143,254,63,248,15,192,127,195,255,30,30,120,57,193,224,7,128,60,1,240,15,128,124,7,224,62,0,240,7,255,159,254,127,248,15,192,255,195,255,30,30,112,56,1,224,7,1,248,7,224,15,192,7,128,14,112,57,225,227,255,15,252,15,192,7,128,15,0,28,192,57,128,243,1,206,7,156,14,56,56,112,112,225,193,195,255,231,255,207,255,128,28,0,56,0,112,63,240,255,195,255,14,0,56,0,224,3,188,15,252,63,240,225,224,7,128,14,120,121,225,227,255,7,248,15,192,7,192,127,195,255,14,30,120,121,224,7,60,31,252,127,241,225,231,135,156,14,120,120,225,227,255,7,248,15,192,127,249,255,231,255,0,60,0,224,7,0,28,0,224,3,128,28,0,112,3,192,14,0,56,0,224,3,128,14,0,7,192,127,195,255,14,30,56,56,225,227,199,135,252,31,224,255,199,135,156,14,112,57,225,227,255,143,252,15,192,15,192,127,131,255,30,28,120,121,192,231,135,158,30,63,248,127,224,99,128,30,56,120,225,195,255,7,248,15,192,56,56,56,0,0,0,0,0,0,0,56,56,56,56,56,56,0,0,0,0,0,0,0,56,56,56,8,24,56,48,0,0,0,0,28,0,252,3,248,15,192,63,0,56,0,62,0,31,128,7,240,1,248,0,60,0,8,63,252,63,252,63,252,0,0,0,0,63,252,63,252,63,252,0,0,0,0,0,0,112,0,248,0,252,0,127,0,31,0,15,0,62,3,248,31,128,252,1,224,2,0,0,15,192,127,131,255,30,28,112,113,193,192,15,0,124,3,224,31,0,120,1,192,0,0,0,0,112,1,192,7,0,0,255,0,3,255,224,15,0,240,28,0,56,24,62,28,48,255,12,49,255,134,113,231,134,96,3,134,96,63,134,96,255,134,97,255,134,97,199,140,97,199,156,113,255,248,49,251,240,56,32,192,28,0,0,15,1,0,7,255,0,0,254,0,3,224,0,248,0,63,0,31,192,7,120,3,222,0,243,128,56,240,30,60,7,135,1,193,224,255,248,63,255,31,255,199,128,241,192,30,240,7,128,63,252,31,255,15,255,199,1,227,128,113,192,120,224,60,127,252,63,252,31,255,14,3,199,0,227,128,113,192,56,255,252,127,252,63,252,0,3,240,7,254,7,255,135,129,227,128,115,192,57,224,0,224,0,112,0,56,0,30,0,15,0,227,128,113,224,120,127,248,31,248,3,240,0,63,240,31,254,15,255,135,3,195,128,241,192,56,224,28,112,14,56,7,28,3,142,1,199,0,227,128,241,192,240,255,248,127,248,63,240,0,63,254,63,254,63,254,56,0,56,0,56,0,56,0,63,252,63,252,63,252,56,0,56,0,56,0,56,0,63,254,63,254,63,254,63,252,127,248,255,241,192,3,128,7,0,14,0,31,252,63,248,127,240,224,1,192,3,128,7,0,14,0,28,0,56,0,3,248,1,255,192,127,252,30,3,195,128,56,240,7,30,0,3,128,0,112,127,142,15,241,225,254,60,1,195,128,120,120,15,7,255,224,127,252,3,241,128,56,7,14,1,195,128,112,224,28,56,7,14,1,195,128,112,255,252,63,255,15,255,195,128,112,224,28,56,7,14,1,195,128,112,224,28,56,7,0,56,112,225,195,135,14,28,56,112,225,195,135,14,28,56,0,56,0,112,0,224,1,192,3,128,7,0,14,0,28,0,56,0,112,0,227,129,199,7,143,15,15,252,31,240,15,192,56,15,28,15,14,15,135,15,131,143,129,207,128,231,128,127,192,63,240,31,248,15,158,7,143,3,131,193,192,240,224,120,112,30,56,15,128,56,0,112,0,224,1,192,3,128,7,0,14,0,28,0,56,0,112,0,224,1,192,3,128,7,0,15,255,31,254,63,252,63,3,225,248,31,15,193,248,127,15,195,248,126,29,195,112,238,59,135,121,220,57,206,225,206,103,14,119,56,113,249,195,143,142,28,124,112,227,227,135,15,28,56,112,224,60,7,15,1,195,224,112,252,28,63,135,15,225,195,252,112,239,156,57,231,14,61,195,143,240,225,252,56,63,14,7,195,129,240,224,60,56,7,0,3,248,1,255,192,127,252,30,3,195,128,56,240,7,158,0,243,128,14,112,1,206,0,57,224,15,60,1,227,128,56,120,15,7,255,192,127,240,3,248,0,63,248,31,255,15,255,135,1,227,128,241,192,56,224,60,112,30,63,254,31,254,15,254,7,0,3,128,1,192,0,224,0,112,0,56,0,0,3,248,1,255,192,127,252,30,3,195,128,56,240,7,158,0,243,128,14,112,1,206,0,57,224,15,60,1,227,128,56,120,15,7,255,192,127,240,3,252,0,3,192,0,60,0,3,192,63,252,15,255,131,255,240,224,60,56,7,14,1,195,128,112,224,60,63,255,15,255,131,255,192,224,240,56,30,14,7,131,128,240,224,60,56,7,128,7,240,31,252,63,252,56,30,56,14,60,0,63,128,31,240,15,252,3,252,0,126,0,30,112,14,120,30,63,252,31,248,7,224,127,254,127,254,127,254,1,192,1,192,1,192,1,192,1,192,1,192,1,192,1,192,1,192,1,192,1,192,1,192,1,192,1,192,56,7,14,1,195,128,112,224,28,56,7,14,1,195,128,112,224,28,56,7,14,1,195,128,112,240,28,60,7,7,131,193,255,224,63,240,3,240,0,240,7,60,7,158,3,199,1,195,193,225,224,224,112,112,60,120,30,56,7,28,3,222,1,238,0,127,0,63,128,31,128,7,192,3,224,0,240,124,30,224,248,61,225,240,115,195,240,231,135,227,199,29,199,143,59,142,30,119,156,60,231,120,59,142,224,119,29,192,254,63,129,252,127,1,248,124,3,224,248,7,193,240,15,131,224,120,15,30,15,15,143,131,199,128,247,128,127,128,31,192,7,192,3,224,3,248,1,252,1,239,1,227,192,241,224,240,120,240,62,120,15,0,120,7,30,7,143,7,131,195,193,227,192,121,192,31,224,15,224,3,224,0,240,0,112,0,56,0,28,0,14,0,7,0,3,128,1,192,0,63,254,63,254,63,252,0,124,0,248,0,240,1,240,3,224,3,192,7,192,15,128,15,128,31,0,62,0,63,254,127,254,127,254,63,63,62,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,62,63,63,192,224,96,96,96,112,48,48,48,56,24,24,28,28,12,12,14,6,6,252,252,124,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,124,252,252,1,128,7,0,15,0,62,0,110,1,204,3,24,14,56,28,48,48,112,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,255,97,195,128,0,0,0,0,0,0,0,0,0,0,15,192,127,192,255,193,195,128,7,1,254,15,252,63,248,120,112,241,225,255,241,255,225,227,192,56,0,112,0,224,1,192,3,158,7,255,15,254,30,30,60,60,112,56,224,113,192,227,195,199,135,143,254,31,252,57,224,15,192,127,195,255,30,30,120,57,192,7,0,28,0,120,57,225,227,255,7,252,15,192,0,56,0,112,0,224,1,192,243,135,255,15,254,60,60,120,120,224,113,192,227,129,199,135,143,15,15,254,31,252,15,56,7,192,127,195,255,14,14,120,57,255,231,255,159,254,120,0,224,227,255,135,252,7,192,15,15,135,3,135,243,249,252,56,28,14,7,3,129,192,224,112,56,28,0,0,12,0,30,0,30,7,252,31,248,63,248,60,56,60,56,31,248,31,240,31,224,56,0,56,0,63,240,63,252,63,254,120,30,127,254,127,252,63,248,56,0,112,0,224,1,192,3,158,7,255,15,254,30,28,60,56,112,112,224,225,193,195,131,135,7,14,14,28,28,56,56,56,112,224,3,135,14,28,56,112,225,195,135,14,28,56,28,28,28,0,28,28,28,28,28,28,28,28,28,28,28,28,28,28,124,252,120,56,0,224,3,128,14,0,56,120,225,195,143,14,120,59,192,255,3,252,15,248,60,224,227,195,135,142,30,56,60,56,112,225,195,135,14,28,56,112,225,195,135,14,28,56,57,227,224,239,223,195,255,255,15,31,30,60,56,120,224,225,227,131,135,142,14,30,56,56,120,224,225,227,131,135,142,14,30,56,56,120,57,224,119,240,255,225,225,195,195,135,7,14,14,28,28,56,56,112,112,224,225,193,195,131,128,7,192,63,224,255,225,193,199,131,206,3,156,7,56,14,120,60,112,112,255,224,255,128,124,0,57,224,127,240,255,225,225,227,195,199,3,142,7,28,14,60,60,120,120,255,225,255,131,158,7,0,14,0,28,0,56,0,15,56,127,240,255,227,195,199,135,142,7,28,14,56,28,120,120,240,240,255,224,255,192,243,128,7,0,14,0,28,0,56,57,206,243,252,248,60,15,3,128,224,56,14,3,128,224,56,0,15,193,255,31,252,240,231,128,31,192,127,128,126,0,115,195,159,252,127,192,252,0,28,14,7,3,135,243,249,252,56,28,14,7,3,129,192,224,124,62,15,0,112,113,193,199,7,28,28,112,113,193,199,7,28,28,112,113,227,199,255,15,252,30,48,240,57,193,199,7,30,60,56,224,227,131,206,7,112,29,192,63,0,248,3,224,7,0,240,240,231,15,14,113,241,199,31,28,121,249,195,155,152,59,187,131,187,184,31,159,129,241,240,31,31,0,241,240,15,14,0,120,120,225,195,207,7,248,15,192,63,0,120,3,240,31,224,119,131,207,30,30,112,120,120,60,224,227,131,143,30,28,112,121,192,231,3,184,15,224,31,0,124,0,240,3,128,30,3,240,15,192,60,0,127,243,255,159,248,7,128,60,3,192,60,1,224,30,1,224,15,252,255,231,255,0,7,15,135,3,129,192,224,112,56,28,60,28,15,1,192,224,112,56,28,14,7,3,224,112,48,195,12,48,195,12,48,195,12,48,195,12,48,195,12,48,56,15,0,224,56,14,3,128,224,56,14,1,224,56,62,14,3,128,224,56,14,3,128,224,240,56,0,0,0,112,39,255,159,254,64,224,0,0,0,0,0,0,0,0,0}; +static const Glyph f0_24_glyphs[]={{0,5,0,0,17,5,32},{0,7,17,0,0,7,33},{15,10,17,0,0,10,34},{37,15,17,0,0,15,35},{69,13,19,0,0,13,36},{100,23,17,0,0,23,37},{149,18,17,0,0,18,38},{188,6,17,0,0,6,39},{201,9,21,0,0,9,40},{225,9,21,0,0,9,41},{249,10,17,0,0,10,42},{271,16,13,0,4,16,43},{297,7,8,0,14,7,44},{304,8,8,0,9,8,45},{312,7,3,0,14,7,46},{315,8,19,0,0,7,47},{334,14,17,0,0,14,48},{364,14,17,0,0,14,49},{394,14,17,0,0,14,50},{424,14,17,0,0,14,51},{454,15,17,0,0,15,52},{486,14,17,0,0,14,53},{516,14,17,0,0,14,54},{546,14,17,0,0,14,55},{576,14,17,0,0,14,56},{606,14,17,0,0,14,57},{636,8,13,0,4,8,58},{649,8,18,0,4,8,59},{667,16,13,0,4,16,60},{693,16,10,0,7,16,61},{713,15,13,0,4,15,62},{738,14,17,0,0,14,63},{768,24,21,0,0,24,64},{831,18,17,0,0,17,65},{870,17,17,0,0,17,66},{907,17,17,0,0,17,67},{944,17,17,0,0,17,68},{981,16,17,0,0,16,69},{1015,15,17,0,0,15,70},{1047,19,17,0,0,19,71},{1088,18,17,0,0,18,72},{1127,7,17,0,0,7,73},{1142,15,17,0,0,15,74},{1174,17,17,0,0,17,75},{1211,15,17,0,0,15,76},{1243,21,17,0,0,21,77},{1288,18,17,0,0,18,78},{1327,19,17,0,0,19,79},{1368,17,17,0,0,17,80},{1405,19,20,0,0,19,81},{1453,18,17,0,0,18,82},{1492,16,17,0,0,16,83},{1526,16,17,0,0,16,84},{1560,18,17,0,0,18,85},{1599,17,17,0,0,17,86},{1636,23,17,0,0,23,87},{1685,17,17,0,0,17,88},{1722,17,17,0,0,17,89},{1759,16,17,0,0,16,90},{1793,8,21,0,0,8,91},{1814,8,19,0,0,7,92},{1833,8,21,0,0,8,93},{1854,15,17,0,0,15,94},{1886,12,4,0,17,12,95},{1892,6,17,0,0,5,96},{1905,15,13,0,4,15,97},{1930,15,17,0,0,15,98},{1962,14,13,0,4,14,99},{1985,15,17,0,0,15,100},{2017,14,13,0,4,14,101},{2040,9,17,0,0,9,102},{2060,16,20,0,1,16,103},{2100,15,17,0,0,15,104},{2132,7,17,0,0,7,105},{2147,8,21,-1,0,7,106},{2168,14,17,0,0,14,107},{2198,7,17,0,0,7,108},{2213,22,13,0,4,22,109},{2249,15,13,0,4,15,110},{2274,15,13,0,4,15,111},{2299,15,17,0,4,15,112},{2331,15,17,0,4,15,113},{2363,10,13,0,4,10,114},{2380,13,13,0,4,13,115},{2402,9,17,0,0,9,116},{2422,14,13,0,4,14,117},{2445,14,13,0,4,13,118},{2468,20,13,0,4,19,119},{2501,14,13,0,4,14,120},{2524,14,17,-1,4,13,121},{2554,13,13,0,4,13,122},{2576,9,21,0,0,9,123},{2600,6,21,0,0,6,124},{2616,10,21,0,0,10,125},{2643,14,10,0,7,14,126}}; +static const uint8_t f0_26_bits[]={60,60,60,60,60,56,56,56,56,56,56,56,56,0,60,60,60,60,61,227,222,61,227,222,57,227,142,24,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,28,7,28,7,28,7,24,6,56,63,254,63,254,63,254,14,48,12,112,28,112,127,252,127,252,127,252,24,224,56,224,56,224,56,192,1,0,2,0,4,0,126,3,255,7,255,30,94,60,128,63,0,127,224,63,224,15,199,19,207,39,143,254,31,248,15,224,2,0,4,0,8,0,0,0,15,128,112,15,224,112,14,56,48,7,28,48,3,142,56,1,199,24,0,231,152,0,63,156,0,15,140,0,0,12,124,0,14,127,0,6,113,192,6,56,224,7,28,112,3,14,56,3,7,28,3,129,252,1,128,124,0,3,248,0,255,224,31,255,1,224,120,60,7,131,192,0,30,0,0,254,126,7,231,240,254,127,30,7,131,192,120,60,7,131,192,120,62,15,129,255,240,15,254,0,63,128,60,120,241,227,135,6,0,0,0,0,0,0,0,0,0,0,3,128,192,112,56,14,7,129,192,112,60,15,3,192,240,60,15,3,192,112,28,7,128,224,56,7,0,192,56,48,14,1,192,112,14,3,128,240,28,7,1,224,120,30,7,129,224,112,28,15,3,128,224,112,28,14,3,0,12,1,129,182,31,193,224,127,27,96,96,12,0,0,0,0,0,0,0,0,0,0,0,0,0,1,192,0,224,0,112,0,56,0,28,0,14,0,255,248,127,252,63,254,0,224,0,112,0,56,0,28,0,14,0,60,60,60,60,12,8,24,48,0,127,63,159,192,0,0,0,0,0,60,60,60,60,7,6,6,14,14,12,12,28,28,24,24,56,56,48,48,112,112,96,96,224,64,7,192,63,224,255,225,227,199,131,207,7,158,15,60,30,120,60,240,121,224,243,193,231,131,207,7,143,30,31,252,31,240,15,128,0,224,3,224,31,224,31,224,1,224,1,224,1,224,1,224,1,224,1,224,1,224,1,224,1,224,1,224,1,224,31,254,31,254,31,254,7,192,63,224,255,227,195,199,131,207,7,140,15,0,60,0,248,3,224,15,128,126,1,248,7,192,15,0,63,254,127,252,255,248,7,224,63,224,255,225,227,231,131,192,7,128,30,1,248,3,224,7,224,1,224,1,231,3,207,7,158,15,31,252,31,240,15,192,3,128,7,128,7,152,7,56,15,56,14,56,30,120,28,120,60,120,56,120,112,120,127,254,127,254,127,254,0,120,0,120,0,120,0,120,63,248,127,240,255,225,192,3,128,7,0,14,248,31,248,63,248,120,248,0,240,1,231,131,207,7,159,30,31,252,31,240,15,128,7,224,63,224,255,225,227,195,131,207,0,30,112,63,248,127,248,248,249,224,243,193,231,131,207,7,143,30,31,252,31,240,15,128,127,252,255,249,255,240,3,192,7,0,28,0,120,0,224,3,192,7,0,30,0,60,0,112,0,224,3,192,7,128,15,0,30,0,7,192,63,224,255,225,227,199,131,207,7,143,30,31,252,31,240,127,240,241,227,193,231,131,207,7,159,31,31,252,31,240,31,192,7,192,63,224,255,227,225,199,131,207,7,158,15,62,30,63,252,127,248,62,240,1,231,131,207,7,15,30,31,252,31,240,15,128,60,60,60,60,0,0,0,0,0,0,60,60,60,60,60,60,60,60,0,0,0,0,0,0,60,60,60,60,12,8,24,48,0,0,0,0,7,0,15,128,63,128,127,1,252,0,248,0,120,0,63,0,7,240,0,254,0,31,192,1,224,0,32,63,254,31,255,15,255,128,0,0,0,1,255,240,255,248,127,252,0,0,0,0,0,0,0,0,0,56,0,62,0,63,128,7,240,1,252,0,62,0,30,0,252,3,248,31,224,63,0,60,0,32,0,3,240,15,248,31,252,60,62,60,30,60,30,0,60,0,124,0,248,1,224,3,192,3,192,0,0,0,0,3,192,3,192,3,192,3,192,0,127,192,0,255,252,0,124,7,192,56,0,56,28,31,7,14,31,224,195,15,252,48,195,199,6,96,225,193,152,0,112,102,3,252,25,131,255,6,96,241,195,152,120,112,199,30,60,112,195,255,248,48,254,252,14,14,28,1,192,0,0,60,2,0,7,255,128,0,63,192,0,0,0,0,1,240,0,126,0,15,224,1,252,0,123,192,15,120,1,207,0,120,240,15,30,3,195,192,120,60,15,255,131,255,248,127,255,30,1,227,192,30,120,3,222,0,120,63,252,7,255,224,255,254,30,3,195,192,120,120,15,15,1,225,255,248,63,254,7,255,224,240,30,30,1,195,192,60,120,7,143,1,225,255,252,63,255,7,255,192,3,248,1,255,192,127,252,31,7,195,192,120,240,7,158,0,243,192,0,120,0,15,0,1,224,0,60,1,199,128,60,120,15,15,131,224,255,248,15,254,0,127,0,63,248,7,255,192,255,252,30,7,195,192,120,120,7,143,0,241,224,30,60,3,199,128,120,240,15,30,1,227,192,60,120,15,15,3,225,255,248,63,254,7,255,0,63,255,143,255,227,255,240,240,0,60,0,15,0,3,192,0,255,248,63,254,15,255,131,192,0,240,0,60,0,15,0,3,192,0,255,252,63,255,143,255,224,63,254,63,254,63,254,60,0,60,0,60,0,60,0,60,0,63,252,63,252,63,252,60,0,60,0,60,0,60,0,60,0,60,0,60,0,1,252,0,255,240,31,255,131,224,124,60,3,199,128,30,120,0,7,128,0,120,63,231,135,254,120,127,231,128,30,120,1,227,192,62,62,7,225,255,254,15,254,224,63,142,60,3,195,192,60,60,3,195,192,60,60,3,195,192,60,60,3,195,255,252,63,255,195,255,252,60,3,195,192,60,60,3,195,192,60,60,3,195,192,60,60,3,195,192,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,0,60,0,60,0,60,0,60,0,60,0,60,0,60,0,60,0,60,0,60,0,60,0,60,120,60,120,56,120,120,63,248,31,240,15,192,60,7,199,129,240,240,124,30,31,3,199,192,121,240,15,124,1,255,0,63,240,7,255,0,253,224,31,30,3,195,224,120,60,15,7,193,224,124,60,7,135,128,248,60,0,60,0,60,0,60,0,60,0,60,0,60,0,60,0,60,0,60,0,60,0,60,0,60,0,60,0,60,0,63,254,63,254,63,254,63,1,248,126,3,240,254,15,225,252,31,195,248,63,135,240,239,15,241,222,30,227,188,61,199,120,123,220,240,243,185,225,231,115,195,207,231,135,159,143,15,31,30,30,62,60,60,120,120,120,240,240,60,3,195,224,60,62,3,195,240,60,63,131,195,252,60,63,195,195,254,60,61,243,195,207,188,60,123,195,199,252,60,63,195,193,252,60,15,195,192,252,60,7,195,192,60,1,248,0,255,240,31,255,131,224,124,60,3,199,128,30,120,1,231,128,30,120,1,231,128,30,120,1,231,128,30,120,1,227,192,60,62,7,193,255,248,15,255,0,31,128,63,252,15,255,131,255,240,240,62,60,7,143,1,227,192,120,240,30,60,15,15,255,195,255,224,255,224,60,0,15,0,3,192,0,240,0,60,0,15,0,0,1,248,0,255,240,31,255,131,224,124,60,3,199,128,30,120,1,231,128,30,120,1,231,128,30,120,1,231,128,30,120,1,227,192,60,62,7,193,255,248,15,255,0,31,224,0,31,0,0,248,0,7,192,63,254,7,255,224,255,254,30,3,195,192,60,120,7,143,0,241,224,60,63,255,135,255,224,255,248,30,15,3,192,240,120,30,15,1,225,224,60,60,3,199,128,120,7,240,15,254,15,255,135,131,231,128,243,192,0,252,0,127,192,31,252,3,255,0,63,192,3,231,128,243,192,121,240,60,127,252,31,252,3,248,0,127,255,63,255,159,255,192,60,0,30,0,15,0,7,128,3,192,1,224,0,240,0,120,0,60,0,30,0,15,0,7,128,3,192,1,224,0,240,0,60,3,195,192,60,60,3,195,192,60,60,3,195,192,60,60,3,195,192,60,60,3,195,192,60,60,3,195,192,60,60,3,195,192,120,30,15,129,255,240,15,254,0,31,128,248,3,222,1,231,128,120,240,30,60,15,15,3,193,224,224,120,120,30,30,3,199,0,243,192,60,240,7,184,1,254,0,63,128,15,192,3,240,0,120,0,240,62,7,188,31,3,158,15,193,207,7,225,231,135,240,241,227,248,112,241,220,56,120,239,60,60,247,158,15,113,206,7,184,231,3,220,127,129,254,63,192,127,15,192,63,7,224,31,131,240,7,193,248,3,224,120,0,124,7,135,193,240,120,124,15,143,0,251,192,15,248,1,254,0,31,128,3,240,0,126,0,31,224,3,254,0,243,192,62,124,7,135,193,224,120,124,15,143,0,248,248,7,158,3,227,192,240,248,120,30,30,3,207,0,247,128,31,224,3,240,0,248,0,30,0,7,128,1,224,0,120,0,30,0,7,128,1,224,0,120,0,63,255,31,255,143,255,128,7,192,7,192,7,192,3,224,3,224,3,224,1,240,1,240,1,240,0,248,0,248,0,248,0,127,254,127,255,63,255,128,31,143,199,195,129,192,224,112,56,28,14,7,3,129,192,224,112,56,28,14,7,3,129,192,248,126,63,224,224,96,96,112,112,48,48,56,56,24,24,28,12,12,12,14,6,6,7,2,252,126,31,3,129,192,224,112,56,28,14,7,3,129,192,224,112,56,28,14,7,3,129,195,227,241,248,1,192,1,224,0,248,0,252,0,119,0,51,128,56,224,28,112,28,56,14,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,240,113,225,192,0,0,0,0,0,0,0,0,0,0,0,15,224,63,248,63,248,120,60,0,60,7,252,31,252,63,252,124,60,120,60,120,124,127,254,63,254,31,30,60,0,60,0,60,0,60,0,60,0,61,240,63,248,63,252,62,60,60,30,60,30,60,30,60,30,60,30,60,30,62,60,63,252,59,248,57,240,7,224,63,224,255,225,225,231,131,207,0,30,0,60,0,120,0,240,120,240,241,255,193,255,0,252,0,0,60,0,60,0,60,0,60,0,60,15,188,31,252,63,252,60,124,120,60,120,60,120,60,120,60,120,60,120,60,60,124,63,252,31,220,15,156,7,192,63,224,255,225,225,199,131,207,255,159,255,63,254,120,0,240,120,240,241,255,193,255,128,252,0,7,131,225,248,120,30,31,231,249,254,30,7,129,224,120,30,7,129,224,120,30,7,129,224,0,15,0,7,129,255,129,255,129,255,192,241,224,240,120,120,60,30,60,15,254,3,254,3,252,3,192,1,255,224,127,248,127,254,120,15,60,7,159,255,207,255,193,255,128,60,0,60,0,60,0,60,0,60,0,60,240,63,252,63,252,62,62,60,30,60,30,60,30,60,30,60,30,60,30,60,30,60,30,60,30,60,30,60,120,240,0,7,143,30,60,120,241,227,199,143,30,60,120,240,30,30,30,0,0,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,124,252,120,60,0,120,0,240,1,224,3,192,7,135,143,30,30,124,60,240,123,192,255,1,255,3,254,7,222,15,60,30,60,60,120,120,120,240,120,60,120,241,227,199,143,30,60,120,241,227,199,143,30,60,120,240,57,241,240,119,247,240,255,255,225,241,241,227,195,195,199,135,135,143,15,15,30,30,30,60,60,60,120,120,120,240,240,241,225,225,227,195,195,199,135,135,128,56,240,59,252,63,252,62,62,60,30,60,30,60,30,60,30,60,30,60,30,60,30,60,30,60,30,60,30,7,224,31,248,63,252,60,60,120,30,120,30,120,30,120,30,120,30,120,30,60,60,63,252,31,248,7,224,57,240,59,248,63,252,62,60,60,30,60,30,60,30,60,30,60,30,60,30,62,60,63,252,63,248,61,240,60,0,60,0,60,0,60,0,60,0,15,156,31,220,63,252,60,124,120,60,120,60,120,60,120,60,120,60,120,60,60,124,63,252,31,252,15,188,0,60,0,60,0,60,0,60,0,60,57,199,120,255,31,227,224,120,15,1,224,60,7,128,240,30,3,192,120,0,15,192,127,224,255,227,195,199,128,7,224,15,248,7,252,0,248,224,121,224,241,255,195,255,129,252,0,28,7,1,192,112,127,159,231,248,240,60,15,3,192,240,60,15,3,192,254,31,131,224,60,30,60,30,60,30,60,30,60,30,60,30,60,30,60,30,60,30,60,30,60,62,63,254,31,254,15,142,240,60,224,121,224,227,195,195,135,7,142,15,60,14,112,30,224,31,192,63,0,126,0,124,0,240,0,240,120,123,135,195,156,62,28,241,241,231,143,206,28,238,112,231,115,135,187,184,29,157,192,252,126,7,227,240,31,31,0,240,248,7,131,192,120,60,120,240,241,224,247,128,254,1,252,1,240,3,224,15,224,63,224,115,193,227,199,135,207,7,128,120,30,240,56,240,113,225,225,195,131,199,7,158,7,56,15,112,15,224,31,128,63,0,62,0,120,0,240,3,192,63,128,126,0,248,0,63,248,255,227,255,0,124,1,224,15,0,124,1,224,15,0,124,3,224,15,254,127,249,255,224,3,128,240,60,7,0,224,28,3,128,112,14,1,192,240,60,7,192,60,3,128,112,14,1,192,56,7,0,240,15,128,224,56,112,225,195,135,14,28,56,112,225,195,135,14,28,56,112,225,195,135,14,28,56,15,128,120,7,0,224,28,3,128,112,14,1,192,30,1,224,124,30,3,128,112,14,1,192,56,7,1,224,120,14,0,0,0,7,1,7,225,135,255,195,15,193,3,192,0,0,0,0,0,0,0,0,0,0,0,0,0}; +static const Glyph f0_26_glyphs[]={{0,5,0,0,18,5,32},{0,8,18,0,0,8,33},{18,12,18,0,0,12,34},{45,16,18,0,0,16,35},{81,15,21,0,-1,15,36},{121,25,18,0,0,25,37},{178,20,18,0,0,20,38},{223,7,18,0,0,7,39},{239,10,23,0,-1,10,40},{268,10,23,0,-1,10,41},{297,11,18,0,0,11,42},{322,17,14,0,4,17,43},{352,8,9,0,14,8,44},{361,9,8,0,10,9,45},{370,8,4,0,14,8,46},{374,8,21,0,-1,8,47},{395,15,18,0,0,15,48},{429,16,18,0,0,16,49},{465,15,18,0,0,15,50},{499,15,18,0,0,15,51},{533,16,18,0,0,16,52},{569,15,18,0,0,15,53},{603,15,18,0,0,15,54},{637,15,18,0,0,15,55},{671,15,18,0,0,15,56},{705,15,18,0,0,15,57},{739,8,14,0,4,8,58},{753,8,19,0,4,8,59},{772,17,14,0,4,17,60},{802,17,11,0,7,17,61},{826,16,14,0,4,16,62},{854,16,18,0,0,16,63},{890,26,23,0,0,26,64},{965,19,18,0,0,19,65},{1008,19,18,0,0,19,66},{1051,19,18,0,0,19,67},{1094,19,18,0,0,19,68},{1137,18,18,0,0,18,69},{1178,16,18,0,0,16,70},{1214,20,18,0,0,20,71},{1259,20,18,0,0,20,72},{1304,8,18,0,0,8,73},{1322,16,18,0,0,16,74},{1358,19,18,0,0,19,75},{1401,16,18,0,0,16,76},{1437,23,18,0,0,23,77},{1489,20,18,0,0,20,78},{1534,20,18,0,0,20,79},{1579,18,18,0,0,18,80},{1620,20,21,0,0,20,81},{1673,19,18,0,0,19,82},{1716,17,18,0,0,17,83},{1755,17,18,0,0,17,84},{1794,20,18,0,0,20,85},{1839,18,18,0,0,18,86},{1880,25,18,0,0,25,87},{1937,19,18,0,0,18,88},{1980,18,18,0,0,18,89},{2021,17,18,0,0,17,90},{2060,9,24,0,-1,9,91},{2087,8,21,0,-1,8,92},{2108,9,24,0,-1,9,93},{2135,17,18,0,0,17,94},{2174,13,4,0,18,13,95},{2181,6,18,0,0,6,96},{2195,16,14,0,4,16,97},{2223,16,19,0,-1,16,98},{2261,15,14,0,4,15,99},{2288,16,19,0,-1,16,100},{2326,15,14,0,4,15,101},{2353,10,19,0,-1,10,102},{2377,17,21,0,2,17,103},{2422,16,19,0,-1,16,104},{2460,7,19,0,-1,7,105},{2477,8,24,-1,-1,7,106},{2501,15,19,0,-1,15,107},{2537,7,19,0,-1,7,108},{2554,23,14,0,4,23,109},{2595,16,14,0,4,16,110},{2623,16,14,0,4,16,111},{2651,16,19,0,4,16,112},{2689,16,19,0,4,16,113},{2727,11,14,0,4,11,114},{2747,15,14,0,4,15,115},{2774,10,18,0,0,10,116},{2797,16,14,0,4,16,117},{2825,15,14,0,4,14,118},{2852,21,14,0,4,21,119},{2889,15,14,0,4,15,120},{2916,15,19,-1,4,14,121},{2952,14,14,0,4,14,122},{2977,11,23,0,-1,11,123},{3009,7,24,0,-1,7,124},{3030,11,23,0,-1,11,125},{3062,17,12,0,6,17,126}}; +static const uint8_t f0_28_bits[]={60,60,60,60,60,60,60,60,60,56,56,56,56,56,0,60,60,60,60,60,240,243,195,207,15,60,60,240,243,195,142,6,24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,142,1,199,1,195,128,227,128,113,193,255,248,255,252,127,254,14,56,7,28,3,14,15,255,199,255,227,255,240,113,192,48,224,56,112,28,112,14,56,0,1,128,3,0,6,0,62,1,255,7,255,31,127,60,222,125,128,255,0,255,192,127,192,31,192,63,158,111,62,254,63,248,63,240,63,128,12,0,24,0,48,0,15,128,56,3,248,7,0,227,129,192,28,112,112,3,142,28,0,113,195,128,14,56,224,0,254,56,0,15,134,0,0,1,207,192,0,115,252,0,28,115,128,3,28,56,0,227,135,0,56,112,224,6,14,28,1,192,231,0,112,31,224,12,1,248,0,3,252,0,63,252,1,255,248,15,129,240,60,3,192,240,0,1,224,0,3,248,254,7,227,248,63,143,225,224,60,15,0,240,60,3,192,240,15,3,192,60,7,129,224,31,255,128,63,252,0,31,192,0,60,60,60,60,60,60,56,24,0,0,0,0,0,0,0,0,0,0,0,1,128,112,28,3,128,240,28,7,128,240,60,7,128,240,30,3,192,120,15,1,224,28,3,192,120,7,0,240,14,1,192,28,1,128,48,7,0,112,14,1,224,28,3,192,120,7,0,240,30,3,192,120,15,1,224,60,7,1,224,60,7,1,224,56,7,1,192,48,0,6,0,193,155,63,225,240,62,31,243,54,6,0,192,0,0,0,0,0,0,0,0,0,0,0,0,0,1,192,0,112,0,28,0,7,0,1,192,0,112,3,255,240,255,252,63,255,0,112,0,28,0,7,0,1,192,0,112,0,28,0,60,60,60,60,12,12,24,56,32,127,63,159,192,0,0,0,0,0,0,0,60,60,60,60,3,3,129,192,192,96,112,56,24,12,14,7,3,3,129,192,224,96,112,56,28,12,14,7,0,7,224,31,248,31,248,60,60,60,60,120,30,120,30,120,30,120,30,120,30,120,30,120,30,120,30,120,30,60,60,60,60,31,248,31,248,7,224,0,112,0,248,7,252,3,254,1,255,0,7,128,3,192,1,224,0,240,0,120,0,60,0,30,0,15,0,7,128,3,192,1,224,31,255,15,255,135,255,192,7,224,31,248,63,252,124,62,120,30,120,30,120,30,0,62,0,124,0,252,1,248,3,240,15,192,31,128,63,0,60,0,127,254,127,254,127,254,7,224,31,248,63,252,124,62,120,30,0,30,0,60,3,248,3,240,3,248,0,60,0,30,0,30,120,30,120,30,60,62,63,252,31,248,7,224,3,192,1,224,0,243,0,241,128,121,192,120,224,60,240,28,120,30,60,30,30,15,15,15,7,135,255,243,255,249,255,252,0,120,0,60,0,30,0,15,0,63,252,63,252,63,252,60,0,60,0,60,0,61,240,63,248,63,252,60,60,0,30,0,30,0,30,120,30,120,30,60,60,63,252,31,248,7,224,7,240,15,248,31,252,60,62,56,30,120,0,121,240,127,248,127,252,124,60,120,30,120,30,120,30,120,30,56,30,60,60,31,252,31,248,7,224,127,254,127,254,127,254,0,60,0,120,0,112,0,240,1,224,1,224,3,192,3,192,7,128,7,128,7,128,7,0,15,0,15,0,15,0,15,0,7,224,31,248,63,252,124,62,120,30,120,30,60,60,63,252,15,240,31,248,60,60,120,30,120,30,120,30,120,30,124,62,63,252,31,248,7,224,7,224,31,248,63,248,60,60,120,28,120,30,120,30,120,30,124,62,63,254,31,254,15,158,0,30,120,30,120,60,124,60,63,248,31,240,7,224,30,7,129,224,120,0,0,0,0,0,0,0,0,0,120,30,7,129,224,30,7,129,224,120,0,0,0,0,0,0,0,0,0,120,30,7,129,224,24,6,3,1,192,64,0,0,0,1,192,1,240,3,248,3,252,7,248,3,240,0,240,0,62,0,15,240,0,255,0,7,248,0,127,0,3,192,0,32,63,255,15,255,195,255,240,0,0,0,0,0,0,3,255,240,255,252,63,255,0,0,0,0,0,0,0,0,0,28,0,15,128,7,240,0,255,0,15,224,1,252,0,30,0,63,0,127,1,254,3,252,3,240,1,224,0,128,0,3,240,3,255,1,255,224,248,124,60,15,15,3,192,0,240,0,248,0,124,0,62,0,31,0,7,128,1,224,0,0,0,0,0,7,128,1,224,0,120,0,30,0,0,63,192,0,63,255,0,31,129,248,7,128,7,129,193,248,48,48,127,131,12,31,248,97,131,143,6,112,112,224,204,0,28,25,128,63,131,48,31,240,102,7,142,12,193,193,195,152,56,56,115,135,143,28,48,255,255,7,15,239,224,96,240,240,15,0,0,0,248,4,0,7,255,128,0,63,240,0,0,0,0,1,248,0,15,192,0,127,0,7,248,0,63,192,3,207,0,30,120,0,243,224,15,15,0,120,120,7,195,224,60,15,1,255,252,31,255,224,255,255,7,128,124,120,1,227,192,15,190,0,124,63,254,3,255,248,63,255,131,192,124,60,3,195,192,60,60,3,195,192,120,63,255,131,255,240,63,255,195,192,60,60,1,227,192,30,60,1,227,192,62,63,255,195,255,248,63,255,0,1,252,0,127,240,31,255,129,240,124,60,3,227,192,30,120,1,231,128,0,120,0,7,128,0,120,0,7,128,0,120,1,227,192,30,60,3,227,240,124,31,255,128,255,240,1,252,0,63,248,3,255,224,63,255,131,192,248,60,3,195,192,60,60,1,227,192,30,60,1,227,192,30,60,1,227,192,30,60,1,227,192,60,60,3,195,192,248,63,255,131,255,224,63,248,0,63,255,199,255,248,255,254,30,0,3,192,0,120,0,15,0,1,224,0,63,255,7,255,224,255,252,30,0,3,192,0,120,0,15,0,1,224,0,63,255,135,255,248,255,255,0,63,255,31,255,143,255,199,128,3,192,1,224,0,240,0,120,0,63,254,31,255,15,255,135,128,3,192,1,224,0,240,0,120,0,60,0,30,0,15,0,0,1,254,0,31,254,1,255,254,7,192,248,62,1,240,240,3,199,128,0,30,0,0,120,63,241,224,255,199,131,255,30,0,60,120,0,240,240,7,195,192,31,7,193,252,31,255,240,31,253,192,31,199,0,60,1,225,224,15,15,0,120,120,3,195,192,30,30,0,240,240,7,135,128,60,63,255,225,255,255,15,255,248,120,3,195,192,30,30,0,240,240,7,135,128,60,60,1,225,224,15,15,0,120,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,0,30,0,15,0,7,128,3,192,1,224,0,240,0,120,0,60,0,30,0,15,0,7,128,3,192,1,227,192,241,224,120,248,120,63,252,15,252,1,248,0,60,3,227,192,124,60,15,131,193,240,60,62,3,199,192,60,248,3,223,0,63,240,3,255,128,63,252,3,243,192,62,62,3,193,240,60,15,3,192,248,60,7,195,192,124,60,3,224,60,0,30,0,15,0,7,128,3,192,1,224,0,240,0,120,0,60,0,30,0,15,0,7,128,3,192,1,224,0,240,0,120,0,63,255,31,255,143,255,192,63,128,252,63,129,252,63,129,252,63,129,252,63,193,252,63,195,188,61,195,188,61,227,188,61,227,188,61,231,60,60,231,60,60,247,60,60,247,60,60,126,60,60,126,60,60,126,60,60,124,60,60,60,60,60,60,60,60,1,225,240,15,15,192,120,127,3,195,248,30,31,224,240,255,135,135,190,60,61,241,225,231,207,15,31,120,120,255,195,195,254,30,15,240,240,63,135,129,252,60,7,225,224,31,15,0,120,1,254,0,31,254,0,255,252,7,192,248,62,1,240,240,3,199,128,7,158,0,30,120,0,121,224,1,231,128,7,158,0,30,120,0,120,240,3,195,224,31,7,192,248,15,255,192,31,254,0,31,224,0,63,254,7,255,224,255,254,30,3,227,192,60,120,7,143,0,241,224,30,60,7,135,255,240,255,252,31,254,3,192,0,120,0,15,0,1,224,0,60,0,7,128,0,240,0,0,1,254,0,31,254,0,255,252,7,192,248,62,1,240,240,3,199,128,7,158,0,30,120,0,121,224,1,231,128,7,158,0,30,120,0,120,240,3,195,224,31,7,192,248,15,255,224,31,255,0,31,248,0,3,240,0,7,224,0,15,128,0,14,0,63,254,3,255,248,63,255,195,192,62,60,1,227,192,30,60,1,227,192,30,60,3,195,255,252,63,255,131,255,240,60,15,3,192,248,60,7,131,192,124,60,3,195,192,62,60,1,224,3,248,3,255,129,255,240,248,62,60,7,143,1,227,224,0,127,128,31,252,3,255,128,31,240,0,254,0,15,158,1,231,128,121,240,62,63,255,7,255,128,127,128,127,255,207,255,249,255,255,0,240,0,30,0,3,192,0,120,0,15,0,1,224,0,60,0,7,128,0,240,0,30,0,3,192,0,120,0,15,0,1,224,0,60,0,7,128,0,60,1,225,224,15,15,0,120,120,3,195,192,30,30,0,240,240,7,135,128,60,60,1,225,224,15,15,0,120,120,3,195,192,30,30,0,240,248,15,131,224,248,15,255,128,63,248,0,127,0,120,1,231,128,62,124,3,195,192,60,60,7,131,224,120,30,7,129,224,240,31,15,0,240,240,15,30,0,121,224,7,158,0,123,192,3,252,0,63,128,3,248,0,31,128,1,240,0,248,31,1,239,3,224,121,224,126,15,62,31,193,231,195,248,60,120,127,15,15,14,241,225,243,222,60,30,123,199,3,206,57,224,121,199,188,15,184,247,128,255,30,224,31,225,252,3,248,63,128,127,7,240,7,224,252,0,252,15,128,31,1,240,0,124,3,227,224,124,31,15,129,240,240,15,159,0,127,224,7,252,0,63,192,1,248,0,31,128,3,252,0,127,192,7,190,0,249,240,31,31,1,224,248,62,7,199,192,124,120,3,224,124,3,227,192,60,62,7,193,240,120,31,15,0,249,240,7,158,0,127,192,3,252,0,31,128,1,248,0,15,0,0,240,0,15,0,0,240,0,15,0,0,240,0,15,0,0,240,0,127,255,159,255,231,255,240,0,252,0,62,0,31,0,15,128,3,224,1,240,0,248,0,62,0,31,0,15,128,3,224,1,240,0,248,0,63,255,159,255,231,255,248,31,135,225,248,112,28,7,1,192,112,28,7,1,192,112,28,7,1,192,112,28,7,1,192,112,28,7,1,248,126,31,128,224,112,24,14,7,3,128,192,112,56,28,6,3,129,192,96,48,28,14,3,1,128,224,112,24,126,31,135,224,56,14,3,128,224,56,14,3,128,224,56,14,3,128,224,56,14,3,128,224,56,14,3,135,225,248,126,0,0,192,0,120,0,30,0,15,192,3,240,1,206,0,115,128,56,112,14,28,3,135,1,192,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,240,240,240,240,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,224,15,252,15,255,7,135,128,3,192,63,224,127,240,127,248,126,60,60,30,30,15,15,143,135,255,241,254,248,124,60,60,0,30,0,15,0,7,128,3,192,1,231,192,255,240,127,252,62,30,31,7,143,3,199,129,227,192,241,224,120,240,60,120,30,62,30,31,255,14,255,7,31,0,7,224,31,248,63,252,60,62,124,30,120,30,120,0,120,0,120,0,120,30,124,30,60,62,63,252,31,248,7,224,0,30,0,15,0,7,128,3,192,1,224,124,240,127,248,127,252,60,62,60,31,30,7,143,3,199,129,227,192,241,224,120,240,124,60,62,31,255,7,251,129,241,192,7,224,31,248,63,252,60,60,120,30,127,254,127,254,127,254,120,0,120,30,120,30,60,62,63,252,31,248,7,224,7,193,248,127,15,1,224,255,159,243,254,30,3,192,120,15,1,224,60,7,128,240,30,3,192,120,15,0,0,3,0,1,224,0,120,15,252,15,254,7,255,131,225,224,240,60,60,15,15,135,193,255,224,63,240,31,248,15,0,3,192,0,255,248,31,255,15,255,231,128,121,224,30,127,255,143,255,193,255,192,60,0,30,0,15,0,7,128,3,192,1,231,192,247,240,127,252,63,62,31,15,15,7,135,131,195,193,225,224,240,240,120,120,60,60,30,30,15,15,7,135,131,192,60,60,60,0,0,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,30,15,7,128,0,0,240,120,60,30,15,7,131,193,224,240,120,60,30,15,7,131,193,224,243,249,248,120,0,60,0,60,0,60,0,60,0,60,0,60,30,60,60,60,124,60,248,61,240,61,224,63,224,63,240,63,240,62,120,60,120,60,60,60,60,60,30,60,31,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,56,248,124,15,255,127,131,255,255,224,248,252,124,60,30,15,15,7,131,195,193,224,240,240,120,60,60,30,15,15,7,131,195,193,224,240,240,120,60,60,30,15,15,7,131,195,193,224,240,56,248,31,254,15,255,135,199,195,193,225,224,240,240,120,120,60,60,30,30,15,15,7,135,131,195,193,225,224,240,240,120,7,240,15,254,15,255,135,131,199,193,243,192,121,224,60,240,30,120,15,60,7,159,7,199,131,195,255,224,255,224,31,192,56,248,29,254,15,255,135,195,195,224,241,224,120,240,60,120,30,60,15,30,7,143,131,199,195,195,255,225,255,224,243,224,120,0,60,0,30,0,15,0,7,128,0,15,142,15,255,15,255,135,135,199,131,227,192,241,224,120,240,60,120,30,60,15,30,15,135,135,195,255,224,255,240,62,120,0,60,0,30,0,15,0,7,128,3,192,57,231,124,255,159,243,224,124,15,1,224,60,7,128,240,30,3,192,120,15,0,15,224,127,240,255,243,227,231,131,207,192,15,240,15,252,3,252,0,249,224,243,227,227,255,135,255,3,248,0,14,3,129,224,120,127,159,231,248,120,30,7,129,224,120,30,7,129,224,120,31,131,224,120,60,30,30,15,15,7,135,131,195,193,225,224,240,240,120,120,60,60,30,30,15,15,7,135,199,195,255,224,255,112,62,56,240,30,120,30,120,60,120,60,60,60,60,120,60,120,30,112,30,240,14,240,15,224,15,224,7,224,7,192,7,192,240,120,60,240,248,121,225,240,243,199,225,199,143,199,135,159,207,15,59,156,30,247,56,29,206,240,59,159,192,127,31,128,126,63,0,248,124,1,240,248,1,224,240,0,124,30,60,60,62,60,30,120,15,240,15,240,7,224,7,224,7,224,15,240,30,248,30,120,60,60,124,62,120,30,120,15,120,14,60,30,60,30,30,28,30,60,30,60,15,56,15,120,7,112,7,240,7,240,3,224,3,224,3,224,3,192,7,192,63,128,63,0,62,0,63,252,127,248,255,224,7,192,31,0,60,0,248,3,224,7,128,31,0,124,1,240,3,255,207,255,159,255,0,3,192,248,60,7,0,224,28,3,128,112,14,1,192,120,62,7,128,120,7,128,112,14,1,192,56,7,0,224,28,3,192,62,3,192,56,112,225,195,135,14,28,56,112,225,195,135,14,28,56,112,225,195,135,14,28,56,112,56,15,128,120,7,0,224,28,3,128,112,14,1,192,60,3,224,60,15,3,192,112,14,1,192,56,7,0,224,28,7,131,224,56,0,0,0,1,224,16,127,134,31,255,195,15,240,64,60,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +static const Glyph f0_28_glyphs[]={{0,5,0,0,19,5,32},{0,8,19,0,0,8,33},{19,14,19,0,0,14,34},{53,17,19,0,0,17,35},{94,15,22,0,-1,15,36},{136,27,19,0,0,27,37},{201,22,19,0,0,22,38},{254,8,19,0,0,8,39},{273,11,25,0,-1,11,40},{308,11,25,0,-1,11,41},{343,11,19,0,0,11,42},{370,18,15,0,4,18,43},{404,8,9,0,15,8,44},{413,9,9,0,10,9,45},{424,8,4,0,15,8,46},{428,9,22,0,-1,8,47},{453,16,19,0,0,16,48},{491,17,19,0,0,17,49},{532,16,19,0,0,16,50},{570,16,19,0,0,16,51},{608,17,19,0,0,17,52},{649,16,19,0,0,16,53},{687,16,19,0,0,16,54},{725,16,19,0,0,16,55},{763,16,19,0,0,16,56},{801,16,19,0,0,16,57},{839,10,15,0,4,10,58},{858,10,20,0,4,10,59},{883,18,15,0,4,18,60},{917,18,12,0,7,18,61},{944,17,15,0,4,17,62},{976,18,19,0,0,18,63},{1019,27,24,0,0,27,64},{1100,21,19,0,0,20,65},{1150,20,19,0,0,20,66},{1198,20,19,0,0,20,67},{1246,20,19,0,0,20,68},{1294,19,19,0,0,19,69},{1340,17,19,0,0,17,70},{1381,22,19,0,0,22,71},{1434,21,19,0,0,21,72},{1484,8,19,0,0,8,73},{1503,17,19,0,0,17,74},{1544,20,19,0,0,20,75},{1592,17,19,0,0,17,76},{1633,24,19,0,0,24,77},{1690,21,19,0,0,21,78},{1740,22,19,0,0,22,79},{1793,19,19,0,0,19,80},{1839,22,23,0,0,22,81},{1903,20,19,0,0,20,82},{1951,18,19,0,0,18,83},{1994,19,19,0,0,19,84},{2040,21,19,0,0,21,85},{2090,20,19,0,0,19,86},{2138,27,19,0,0,27,87},{2203,20,19,0,0,20,88},{2251,20,19,0,0,20,89},{2299,18,19,0,0,18,90},{2342,10,25,0,-1,10,91},{2374,9,22,0,-1,8,92},{2399,10,25,0,-1,10,93},{2431,18,19,0,0,18,94},{2474,15,4,0,19,15,95},{2482,7,20,0,-1,6,96},{2500,17,15,0,4,17,97},{2532,17,20,0,-1,17,98},{2575,16,15,0,4,16,99},{2605,17,20,0,-1,17,100},{2648,16,15,0,4,16,101},{2678,11,20,0,-1,11,102},{2706,18,23,0,1,18,103},{2758,17,20,0,-1,17,104},{2801,8,20,0,-1,8,105},{2821,9,25,-1,-1,8,106},{2850,16,20,0,-1,16,107},{2890,8,20,0,-1,8,108},{2910,26,15,0,4,26,109},{2959,17,15,0,4,17,110},{2991,17,15,0,4,17,111},{3023,17,20,0,4,17,112},{3066,17,20,0,4,17,113},{3109,11,15,0,4,11,114},{3130,15,15,0,4,15,115},{3159,10,19,0,0,10,116},{3183,17,15,0,4,17,117},{3215,16,15,0,4,15,118},{3245,23,15,0,4,22,119},{3289,16,15,0,4,16,120},{3319,16,20,-1,4,15,121},{3359,15,15,0,4,15,122},{3388,11,25,0,-1,11,123},{3423,7,26,0,-1,7,124},{3446,11,25,0,-1,11,125},{3481,19,12,0,7,19,126}}; +static const uint8_t f0_30_bits[]={60,30,15,7,131,193,224,240,120,60,30,15,7,131,193,192,0,0,60,30,15,7,128,60,240,243,195,207,15,60,60,240,243,195,207,6,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,199,0,56,224,14,28,1,199,0,56,224,63,255,135,255,240,255,254,7,28,0,227,128,28,112,7,142,7,255,240,255,254,31,255,192,113,192,28,56,3,135,0,113,192,14,56,0,1,128,1,128,1,128,7,224,31,248,63,252,125,190,121,158,125,128,127,128,63,240,31,252,7,254,1,254,121,158,121,158,127,190,63,252,31,252,7,240,1,128,1,128,1,128,0,128,15,192,28,1,254,1,192,60,240,56,3,135,7,0,56,112,224,3,135,14,0,60,241,192,1,254,56,0,15,195,128,0,0,115,240,0,14,127,128,1,199,56,0,28,225,192,3,142,28,0,112,225,192,7,14,28,0,224,225,192,28,7,56,3,128,127,128,56,3,240,1,254,0,15,255,0,63,255,0,252,63,1,224,30,3,192,60,7,128,0,7,128,0,7,241,252,15,227,248,63,199,240,248,15,3,224,30,7,128,60,15,0,120,31,1,240,31,7,224,63,255,128,31,254,0,15,224,0,60,60,60,60,60,60,60,24,0,0,0,0,0,0,0,0,0,0,0,0,1,192,56,3,128,112,15,0,240,30,1,224,30,3,224,60,3,192,60,3,192,60,3,192,60,3,224,30,1,224,30,0,240,15,0,112,3,128,56,1,192,112,7,0,240,14,1,224,28,3,192,120,15,128,240,30,3,192,120,15,1,224,60,7,128,240,62,7,128,240,28,7,128,224,60,7,1,192,0,6,0,96,38,103,238,63,192,240,63,199,238,38,64,96,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,0,28,0,3,128,0,112,0,14,0,1,192,0,56,1,255,252,63,255,135,255,240,3,128,0,112,0,14,0,1,192,0,56,0,7,0,60,30,15,7,128,192,96,112,112,48,0,0,0,31,231,249,254,0,0,0,0,0,0,0,0,60,30,15,7,128,3,129,193,192,224,112,56,56,28,14,7,7,3,129,192,192,224,112,56,24,28,14,7,3,3,128,128,7,240,7,252,7,255,7,199,195,193,227,224,249,224,60,240,30,120,15,60,7,158,3,207,1,231,128,243,192,121,240,124,120,60,62,62,15,254,3,254,0,254,0,0,112,0,124,1,255,0,127,192,31,240,0,60,0,15,0,3,192,0,240,0,60,0,15,0,3,192,0,240,0,60,0,15,0,3,192,0,240,7,255,225,255,248,127,254,3,248,1,255,192,127,252,15,143,131,224,248,120,15,15,1,224,0,124,0,31,0,7,224,1,248,0,126,0,63,128,15,192,3,240,0,252,0,31,0,7,255,240,255,254,31,255,192,7,240,7,255,3,255,225,248,252,124,31,30,3,192,1,240,0,248,1,252,0,126,0,31,224,0,124,0,15,0,3,199,128,241,240,60,62,63,15,255,129,255,192,31,192,3,192,0,240,0,60,192,30,48,7,156,3,199,0,243,192,56,240,30,60,15,15,3,195,193,224,240,127,255,159,255,231,255,248,0,240,0,60,0,15,0,3,192,0,240,31,254,7,255,131,255,224,240,0,60,0,15,0,3,207,128,255,240,63,254,15,143,193,193,240,0,60,0,15,30,3,199,128,241,240,124,62,62,7,255,128,255,192,15,192,3,240,7,252,7,255,7,199,195,193,225,224,241,224,0,243,240,127,252,63,255,31,135,207,129,231,128,243,192,121,224,60,120,62,62,62,15,255,3,255,0,126,0,127,255,63,255,159,255,192,7,192,3,192,3,192,3,224,1,224,1,224,0,240,0,240,0,120,0,120,0,60,0,30,0,15,0,15,0,7,128,3,192,1,224,0,3,240,3,255,1,255,224,248,252,60,31,15,3,195,193,240,248,248,31,252,3,254,1,255,224,248,124,124,15,30,3,199,128,241,240,124,62,63,15,255,129,255,192,31,192,7,240,7,254,3,255,192,248,248,124,15,30,3,199,128,241,240,60,126,31,15,255,193,255,240,63,60,0,15,15,3,195,192,240,240,120,62,62,7,255,0,255,128,15,192,30,7,129,224,120,0,0,0,0,0,0,0,0,0,0,30,7,129,224,120,30,7,129,224,120,0,0,0,0,0,0,0,0,0,0,30,7,129,224,120,6,1,128,224,112,24,0,0,0,0,0,0,112,0,62,0,63,128,31,224,31,240,15,240,1,240,0,60,0,7,240,0,127,128,3,254,0,15,240,0,127,0,1,224,0,8,0,0,7,255,240,255,254,31,255,192,0,0,0,0,0,0,1,255,252,63,255,135,255,240,0,0,0,0,0,0,0,0,0,1,128,0,31,0,1,252,0,15,248,0,31,224,0,63,128,0,252,0,7,192,1,252,0,255,128,63,192,31,240,1,248,0,30,0,1,0,0,3,248,3,255,129,255,224,252,124,62,15,15,3,195,192,240,0,124,0,62,0,31,0,15,128,7,192,1,224,0,120,0,0,0,0,0,1,224,0,120,0,30,0,7,128,0,63,224,0,15,255,224,0,248,15,192,31,0,15,1,224,252,28,14,31,248,112,224,255,195,135,15,31,12,112,112,120,115,128,3,195,156,0,30,28,224,31,240,231,3,255,135,56,60,60,57,195,193,225,206,30,15,28,48,248,249,225,199,255,254,14,31,239,224,56,124,62,0,240,0,0,3,224,0,0,15,255,192,0,15,252,0,0,0,0,0,0,252,0,7,240,0,31,224,0,127,128,3,222,0,15,124,0,60,240,1,227,224,7,143,128,62,30,0,240,124,3,193,240,31,3,224,127,255,131,255,254,15,255,252,60,1,241,240,3,231,128,15,190,0,30,63,255,1,255,254,15,255,248,120,7,195,192,30,30,0,240,240,7,135,128,124,63,255,193,255,252,15,255,240,120,7,195,192,31,30,0,120,240,3,199,128,62,60,3,241,255,255,15,255,240,127,254,0,1,254,0,63,252,3,255,240,63,15,195,224,31,30,0,121,240,3,207,0,30,120,0,3,192,0,30,0,0,240,0,7,128,15,62,0,120,240,3,199,192,62,31,135,224,127,254,1,255,224,3,252,0,63,252,0,255,252,3,255,252,15,3,240,60,3,224,240,7,195,192,31,15,0,60,60,0,240,240,3,195,192,15,15,0,60,60,0,240,240,3,195,192,31,15,0,248,60,15,192,255,255,3,255,240,15,255,0,63,255,227,255,254,63,255,227,192,0,60,0,3,192,0,60,0,3,192,0,63,255,131,255,248,63,255,131,192,0,60,0,3,192,0,60,0,3,192,0,60,0,3,255,254,63,255,227,255,254,63,255,143,255,227,255,248,240,0,60,0,15,0,3,192,0,240,0,60,0,15,255,195,255,240,255,252,60,0,15,0,3,192,0,240,0,60,0,15,0,3,192,0,240,0,0,127,128,3,255,224,7,255,240,15,193,248,31,0,124,30,0,60,62,0,60,62,0,0,60,0,0,60,15,252,60,15,252,60,15,252,60,0,60,62,0,60,30,0,124,31,0,252,15,195,252,7,255,252,3,255,220,0,255,28,60,0,240,240,3,195,192,15,15,0,60,60,0,240,240,3,195,192,15,15,0,60,63,255,240,255,255,195,255,255,15,0,60,60,0,240,240,3,195,192,15,15,0,60,60,0,240,240,3,195,192,15,15,0,60,60,30,15,7,131,193,224,240,120,60,30,15,7,131,193,224,240,120,60,30,15,7,128,0,15,0,3,192,0,240,0,60,0,15,0,3,192,0,240,0,60,0,15,0,3,192,0,240,0,60,0,15,30,3,199,128,241,240,124,126,62,15,255,129,255,192,31,192,60,1,241,224,31,15,1,240,120,31,3,193,240,30,31,0,241,240,7,159,0,61,248,1,255,224,15,255,0,127,124,3,243,240,31,15,128,240,62,7,129,248,60,7,193,224,31,15,0,252,120,3,224,60,0,15,0,3,192,0,240,0,60,0,15,0,3,192,0,240,0,60,0,15,0,3,192,0,240,0,60,0,15,0,3,192,0,240,0,60,0,15,255,227,255,248,255,254,63,128,126,15,224,63,131,252,15,224,255,3,248,63,193,254,15,240,119,131,222,29,224,247,135,120,61,227,222,15,60,231,131,207,57,224,243,206,120,60,119,158,15,31,199,131,199,241,224,241,252,120,60,62,30,15,15,135,131,195,225,224,240,120,120,62,0,240,248,3,195,240,15,15,224,60,63,192,240,255,3,195,254,15,15,252,60,61,248,240,243,227,195,199,207,15,15,188,60,63,240,240,127,195,192,255,15,1,252,60,7,240,240,15,195,192,31,15,0,124,0,254,0,15,255,128,63,255,128,252,31,131,224,15,135,128,15,31,0,31,60,0,62,120,0,60,240,0,121,224,0,243,192,1,231,128,7,207,128,15,143,0,30,31,0,124,31,131,240,31,255,192,31,255,0,7,240,0,63,254,3,255,248,63,255,195,192,126,60,1,227,192,30,60,1,227,192,30,60,3,227,192,126,63,255,195,255,248,63,254,3,192,0,60,0,3,192,0,60,0,3,192,0,60,0,3,192,0,0,254,0,15,255,128,63,255,128,252,31,131,224,15,135,128,15,31,0,31,60,0,62,120,0,60,240,0,121,224,0,243,192,1,231,128,7,207,128,15,143,0,30,31,0,124,31,131,240,31,255,192,31,255,0,7,252,0,0,124,0,0,124,0,0,124,0,0,120,63,255,0,255,255,3,255,254,15,0,248,60,1,240,240,3,195,192,15,15,0,124,60,3,224,255,255,131,255,252,15,255,224,60,15,128,240,31,3,192,124,15,0,248,60,3,224,240,7,195,192,31,15,0,60,3,252,0,255,240,31,255,131,240,252,60,3,195,192,60,62,0,3,252,0,31,248,0,255,240,7,255,128,7,252,0,15,192,0,60,120,3,199,192,60,62,15,195,255,248,15,255,0,63,192,127,255,231,255,254,127,255,224,15,0,0,240,0,15,0,0,240,0,15,0,0,240,0,15,0,0,240,0,15,0,0,240,0,15,0,0,240,0,15,0,0,240,0,15,0,0,240,0,15,0,60,0,240,240,3,195,192,15,15,0,60,60,0,240,240,3,195,192,15,15,0,60,60,0,240,240,3,195,192,15,15,0,60,60,0,240,240,3,195,224,31,7,128,120,31,135,224,63,255,0,127,248,0,127,128,120,0,243,224,15,159,0,124,120,3,195,224,62,31,1,224,120,15,3,224,248,15,7,128,120,60,3,227,224,15,30,0,124,240,3,239,0,15,120,0,127,192,3,252,0,15,224,0,127,0,1,240,0,248,15,128,251,192,126,7,159,7,240,60,248,63,131,231,193,252,30,30,15,240,240,248,247,135,135,199,188,124,30,61,227,192,241,199,158,7,222,60,240,62,241,239,128,247,143,120,7,188,59,192,63,193,254,1,254,15,224,7,240,127,0,63,129,248,1,248,15,192,7,192,124,0,126,1,241,240,31,7,193,240,63,15,128,248,248,3,239,128,15,252,0,127,192,1,252,0,7,224,0,127,0,7,252,0,63,240,3,239,128,62,62,3,225,248,31,7,193,240,31,31,0,252,248,3,240,252,1,243,224,31,15,128,248,124,15,129,240,120,7,199,192,62,124,0,251,192,3,254,0,31,224,0,126,0,1,240,0,15,0,0,120,0,3,192,0,30,0,0,240,0,7,128,0,60,0,1,224,0,63,255,227,255,254,63,255,192,0,252,0,31,128,3,240,0,63,0,7,224,0,252,0,31,128,1,248,0,63,0,7,224,0,124,0,15,192,1,248,0,63,128,3,255,254,127,255,231,255,254,31,199,241,252,112,28,7,1,192,112,28,7,1,192,112,28,7,1,192,112,28,7,1,192,112,28,7,1,192,112,31,199,241,252,224,112,24,14,7,3,128,224,112,56,28,7,3,129,192,96,56,28,14,3,1,192,224,112,24,14,2,126,31,135,224,56,14,3,128,224,56,14,3,128,224,56,14,3,128,224,56,14,3,128,224,56,14,3,128,224,56,126,31,135,224,0,224,0,62,0,7,192,0,252,0,63,128,7,112,1,231,0,56,224,7,30,1,193,192,56,60,15,3,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,248,240,240,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,240,7,255,3,255,224,248,248,60,30,0,7,128,63,224,127,248,63,254,31,135,135,129,225,224,248,124,127,31,255,243,254,252,126,30,60,0,15,0,3,192,0,240,0,60,0,15,0,3,199,128,247,248,63,255,15,199,195,224,248,248,30,60,7,143,1,227,192,120,240,30,62,7,143,131,227,241,240,255,252,61,254,15,62,0,3,240,7,254,7,255,135,199,195,193,243,224,121,224,0,240,0,120,0,60,0,31,3,199,131,227,227,224,255,240,63,240,7,224,0,15,0,3,192,0,240,0,60,0,15,0,3,192,124,240,127,252,63,255,15,143,199,193,241,240,60,120,15,30,3,199,128,241,224,60,124,15,31,7,195,227,240,255,252,31,247,1,241,192,3,240,7,254,7,255,135,199,199,192,243,224,121,255,252,255,254,127,255,60,0,30,3,199,129,227,227,240,255,240,63,240,7,224,7,193,248,127,15,129,224,60,31,243,254,127,195,192,120,15,1,224,60,7,128,240,30,3,192,120,15,1,224,60,0,0,3,192,0,120,0,31,1,255,192,255,240,63,252,15,199,193,224,60,60,7,135,193,240,127,252,7,255,129,255,192,124,0,15,0,1,240,0,63,255,131,255,248,255,255,62,1,231,192,124,255,255,143,255,224,255,240,60,0,15,0,3,192,0,240,0,60,0,15,0,3,199,192,247,248,63,255,15,239,195,224,240,248,60,60,15,15,3,195,192,240,240,60,60,15,15,3,195,192,240,240,60,60,15,15,3,192,60,60,60,0,0,0,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,30,15,7,128,0,0,0,120,60,30,15,7,131,193,224,240,120,60,30,15,7,131,193,224,240,120,124,254,126,62,0,60,0,30,0,15,0,7,128,3,192,1,224,0,240,124,120,60,60,62,30,62,15,62,7,190,3,254,1,255,128,255,224,127,240,62,124,30,30,15,15,135,131,195,193,241,224,124,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,248,62,3,223,239,240,63,255,255,131,241,252,248,62,31,135,195,192,240,124,60,15,7,195,192,240,124,60,15,7,195,192,240,124,60,15,7,195,192,240,124,60,15,7,195,192,240,124,60,15,7,195,192,240,124,60,124,15,127,131,255,240,252,124,62,15,15,3,195,192,240,240,60,60,15,15,3,195,192,240,240,60,60,15,15,3,195,192,240,240,60,3,240,3,255,1,255,224,248,124,60,15,31,3,231,128,121,224,30,120,7,158,1,231,192,248,240,60,62,31,7,255,128,255,192,15,192,60,248,15,127,131,255,240,252,124,62,15,143,129,227,192,120,240,30,60,7,143,1,227,224,120,248,62,63,31,15,255,195,255,224,243,224,60,0,15,0,3,192,0,240,0,60,0,0,7,199,7,253,195,255,240,248,252,124,31,31,3,199,128,241,224,60,120,15,30,3,199,192,241,240,124,62,63,15,255,193,254,240,31,60,0,15,0,3,192,0,240,0,60,0,15,0,60,227,222,63,227,254,63,3,224,60,3,192,60,3,192,60,3,192,60,3,192,60,3,192,7,240,31,248,63,252,124,62,120,30,124,0,63,192,63,248,15,252,0,254,0,30,120,30,126,62,63,254,31,252,7,240,14,1,192,120,15,7,252,255,159,240,240,30,3,192,120,15,1,224,60,7,128,240,31,3,248,63,3,224,60,15,15,3,195,192,240,240,60,60,15,15,3,195,192,240,240,60,60,15,15,3,195,192,240,248,124,62,63,7,255,193,254,112,31,28,248,15,60,7,158,7,143,131,195,193,225,225,224,120,240,60,120,30,120,7,188,3,220,1,254,0,127,0,63,0,31,128,7,192,248,60,15,120,62,30,120,126,30,120,126,30,124,126,28,60,127,60,60,255,60,60,231,60,30,231,56,30,231,184,31,231,248,15,195,240,15,195,240,15,195,240,7,195,224,7,129,224,124,15,31,15,143,143,131,231,128,255,128,127,192,31,192,7,192,7,240,3,248,3,254,3,239,129,227,225,241,241,240,124,240,31,120,7,190,7,143,3,199,129,225,225,224,240,240,124,120,30,120,15,60,3,220,1,254,0,255,0,63,0,31,128,7,192,3,192,3,224,3,224,15,240,7,240,3,224,0,63,254,63,254,63,252,0,124,0,248,1,240,1,240,3,224,7,192,15,192,15,128,31,0,63,0,63,254,127,254,127,254,3,192,126,15,0,224,14,0,224,14,0,224,14,0,224,14,1,224,124,7,0,124,1,224,14,0,224,14,0,224,14,0,224,14,0,240,7,224,62,0,0,56,112,225,195,135,14,28,56,112,225,195,135,14,28,56,112,225,195,135,14,28,56,112,224,60,3,224,15,0,112,7,0,112,7,0,112,7,0,112,7,0,120,3,224,14,3,224,120,7,0,112,7,0,112,7,0,112,7,0,240,62,3,192,0,0,0,0,0,248,4,31,241,195,255,252,56,127,130,1,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +static const Glyph f0_30_glyphs[]={{0,6,0,0,20,6,32},{0,9,20,0,0,9,33},{23,14,20,0,0,14,34},{58,19,20,0,0,19,35},{106,16,24,0,-2,16,36},{154,28,20,0,0,28,37},{224,23,20,0,0,23,38},{282,8,20,0,0,8,39},{302,12,27,0,-2,12,40},{343,11,27,0,-2,11,41},{381,12,20,0,0,12,42},{411,19,16,0,4,19,43},{449,9,10,0,16,9,44},{461,10,10,0,10,10,45},{474,9,4,0,16,9,46},{479,9,24,0,-2,9,47},{506,17,20,0,0,17,48},{549,18,20,0,0,18,49},{594,19,20,0,0,19,50},{642,18,20,0,0,18,51},{687,18,20,0,0,18,52},{732,18,20,0,0,18,53},{777,17,20,0,0,17,54},{820,17,20,0,0,17,55},{863,18,20,0,0,18,56},{908,18,20,0,0,18,57},{953,10,16,0,4,10,58},{973,10,22,0,4,10,59},{1001,19,16,0,4,19,60},{1039,19,13,0,7,19,61},{1070,20,16,0,4,20,62},{1110,18,20,0,0,18,63},{1155,29,25,0,0,29,64},{1246,22,20,0,0,22,65},{1301,21,20,0,0,21,66},{1354,21,20,0,0,21,67},{1407,22,20,0,0,22,68},{1462,20,20,0,0,20,69},{1512,18,20,0,0,18,70},{1557,24,20,0,0,24,71},{1617,22,20,0,0,22,72},{1672,9,20,0,0,9,73},{1695,18,20,0,0,18,74},{1740,21,20,0,0,21,75},{1793,18,20,0,0,18,76},{1838,26,20,0,0,26,77},{1903,22,20,0,0,22,78},{1958,23,20,0,0,23,79},{2016,20,20,0,0,20,80},{2066,23,24,0,0,23,81},{2135,22,20,0,0,22,82},{2190,20,20,0,0,20,83},{2240,20,20,0,0,20,84},{2290,22,20,0,0,22,85},{2345,21,20,0,0,21,86},{2398,29,20,0,0,29,87},{2471,21,20,0,0,21,88},{2524,21,20,0,0,20,89},{2577,20,20,0,0,20,90},{2627,10,27,0,-2,10,91},{2661,9,24,0,-2,9,92},{2688,10,27,0,-2,10,93},{2722,19,21,0,-1,19,94},{2772,16,5,0,20,16,95},{2782,7,22,0,-2,7,96},{2802,18,16,0,4,18,97},{2838,18,22,0,-2,18,98},{2888,17,16,0,4,17,99},{2922,18,22,0,-2,18,100},{2972,17,16,0,4,17,101},{3006,11,22,0,-2,11,102},{3037,19,24,0,1,19,103},{3094,18,22,0,-2,18,104},{3144,8,22,0,-2,8,105},{3166,9,27,-1,-2,8,106},{3197,17,22,0,-2,17,107},{3244,8,22,0,-2,8,108},{3266,28,16,0,4,28,109},{3322,18,16,0,4,18,110},{3358,18,16,0,4,18,111},{3394,18,21,0,4,18,112},{3442,18,21,0,4,18,113},{3490,12,16,0,4,12,114},{3514,16,16,0,4,16,115},{3546,11,20,0,0,11,116},{3574,18,16,0,4,18,117},{3610,17,16,0,4,16,118},{3644,24,16,0,4,24,119},{3692,17,16,0,4,17,120},{3726,17,21,-1,4,16,121},{3771,16,16,0,4,16,122},{3803,12,27,0,-2,12,123},{3844,7,27,0,-2,7,124},{3868,12,27,0,-2,12,125},{3909,20,12,0,8,20,126}}; +static const uint8_t f0_34_bits[]={31,3,224,124,15,129,240,62,7,192,248,30,3,192,56,7,0,224,28,3,128,112,0,0,0,124,15,129,240,62,7,192,62,124,62,124,62,124,62,124,62,124,62,124,60,120,28,56,28,56,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,225,192,15,30,0,112,240,3,135,0,28,56,1,225,192,127,255,195,255,254,31,255,240,255,255,129,225,192,14,30,0,112,224,63,255,225,255,255,15,255,248,127,255,192,112,240,3,135,0,60,56,1,227,192,14,30,0,112,224,0,0,192,0,48,0,12,0,3,128,15,252,7,255,195,255,241,251,126,124,207,159,176,7,252,0,255,224,31,254,1,255,192,15,248,3,126,124,207,159,51,227,255,248,255,252,31,254,1,254,0,12,0,3,0,0,192,0,48,0,3,224,1,192,7,252,1,224,3,142,0,224,3,131,128,224,1,193,192,224,0,224,224,240,0,112,112,112,0,56,56,112,0,30,60,120,0,7,252,56,0,1,254,56,0,0,124,56,60,0,0,60,127,128,0,28,63,192,0,28,56,112,0,30,28,56,0,30,14,28,0,14,7,14,0,14,3,135,0,15,1,195,128,15,0,115,128,7,0,63,192,7,128,7,128,0,255,0,1,255,248,0,255,255,0,127,255,224,63,0,252,15,128,31,3,224,7,128,248,0,0,31,0,0,3,255,31,224,63,199,248,31,241,254,31,248,127,135,192,31,3,224,7,192,248,1,240,62,0,124,15,128,31,3,240,15,128,127,255,224,15,255,240,1,255,248,0,15,240,0,62,31,15,135,195,225,240,240,56,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,15,0,240,7,128,120,7,192,60,3,224,31,0,240,7,128,124,3,224,31,0,248,7,192,62,1,240,15,128,60,1,224,15,128,124,1,224,15,128,60,0,240,7,128,30,0,112,56,1,224,7,128,60,0,240,7,128,62,0,240,7,192,62,0,240,7,128,62,1,240,15,128,124,3,224,31,0,248,7,128,124,3,224,30,0,240,15,0,120,7,128,60,3,192,28,0,3,0,12,2,51,30,220,63,240,63,0,252,15,252,123,112,140,192,48,0,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,120,0,1,224,0,7,128,0,30,0,0,120,0,1,224,0,7,128,7,255,248,31,255,224,127,255,129,255,254,0,30,0,0,120,0,1,224,0,7,128,0,30,0,0,120,0,1,224,0,31,3,224,124,15,129,240,14,1,192,48,14,3,128,64,0,127,207,249,255,63,224,0,0,0,0,0,0,0,0,0,0,31,3,224,124,15,129,240,1,192,56,15,1,192,56,15,1,192,56,7,1,224,56,7,0,224,60,7,0,224,28,7,128,224,28,7,128,224,28,3,128,240,28,0,1,252,0,63,248,3,255,224,31,255,1,240,124,15,131,224,248,15,135,192,124,62,3,225,240,31,15,128,248,124,7,195,224,62,31,1,240,248,15,135,192,124,62,3,224,248,62,7,193,240,31,255,0,255,248,3,255,128,7,240,0,0,56,0,15,128,7,248,1,255,128,31,248,1,255,128,0,248,0,15,128,0,248,0,15,128,0,248,0,15,128,0,248,0,15,128,0,248,0,15,128,0,248,0,15,128,0,248,1,255,254,31,255,225,255,254,31,255,224,1,252,0,63,248,3,255,224,63,255,131,240,124,31,1,240,248,15,135,192,124,0,7,224,0,62,0,3,240,0,63,0,7,240,0,127,0,7,240,0,127,0,15,224,0,126,0,7,224,0,127,255,131,255,254,31,255,240,255,255,128,3,248,0,255,224,31,255,131,255,248,126,15,199,192,124,124,7,192,0,120,0,15,128,31,240,1,252,0,31,240,0,255,128,0,248,0,7,192,0,124,124,7,199,192,124,126,15,195,255,248,31,255,0,255,224,3,248,0,1,240,0,30,0,3,224,0,62,48,3,199,0,124,112,7,143,0,120,240,15,31,0,241,240,30,31,1,225,240,60,31,3,193,240,127,255,231,255,254,127,255,231,255,254,0,31,0,1,240,0,31,0,1,240,0,31,0,31,255,129,255,248,31,255,129,255,240,30,0,3,224,0,62,0,3,227,0,63,254,3,255,240,63,255,131,241,248,28,7,192,0,124,0,7,199,192,124,124,7,199,192,124,62,15,131,255,248,31,255,0,255,224,3,248,0,0,252,0,63,240,15,255,128,255,252,31,7,193,224,62,30,3,227,224,0,62,126,3,255,240,63,255,131,255,252,63,7,227,224,62,62,3,227,224,62,62,3,225,224,62,31,7,192,255,252,15,255,128,127,240,1,252,0,63,255,227,255,254,63,255,227,255,252,0,7,128,0,248,0,31,0,1,224,0,62,0,3,192,0,124,0,7,128,0,248,0,15,0,0,240,0,31,0,1,224,0,30,0,3,224,0,62,0,3,224,0,62,0,3,224,0,3,248,0,255,224,31,255,3,255,248,126,15,199,192,124,124,7,199,192,124,62,15,129,255,240,15,254,1,255,240,63,255,131,224,248,124,7,199,192,124,124,7,199,192,124,126,15,195,255,248,31,255,0,255,224,3,248,0,3,248,0,255,224,31,255,3,255,240,62,15,135,192,120,124,7,199,192,124,124,7,199,192,124,62,15,195,255,252,31,255,192,255,124,1,135,192,0,124,62,7,131,224,120,63,15,129,255,240,31,255,0,127,224,3,248,0,31,3,224,124,15,129,240,0,0,0,0,0,0,0,0,0,0,0,62,7,192,248,31,3,224,31,3,224,124,15,129,240,0,0,0,0,0,0,0,0,0,0,0,62,7,192,248,31,3,224,28,3,128,96,28,7,0,128,0,0,0,0,3,128,0,62,0,7,248,0,127,192,15,252,0,255,128,15,248,0,63,0,0,240,0,3,248,0,15,252,0,7,252,0,7,254,0,3,252,0,3,248,0,1,224,0,1,0,31,255,224,127,255,129,255,254,7,255,248,0,0,0,0,0,0,0,0,7,255,248,31,255,224,127,255,129,255,254,0,0,0,0,0,0,0,0,0,0,0,0,96,0,1,240,0,7,240,0,31,248,0,15,248,0,15,252,0,7,248,0,3,240,0,7,192,0,127,0,15,248,1,255,128,31,240,1,255,0,7,224,0,30,0,0,64,0,0,1,254,0,63,252,3,255,240,63,255,131,240,126,31,1,240,248,15,135,192,124,0,7,192,0,126,0,15,224,0,254,0,15,192,0,124,0,3,224,0,31,0,0,0,0,0,0,0,62,0,1,240,0,15,128,0,124,0,3,224,0,0,7,254,0,0,7,255,248,0,3,255,255,192,1,252,0,252,0,124,0,3,192,15,7,240,60,3,193,255,131,192,240,127,240,56,28,31,255,7,131,131,193,224,112,224,120,60,14,28,0,7,129,195,128,15,240,56,112,15,254,7,14,3,255,192,225,192,248,120,28,56,30,15,7,7,3,193,224,224,240,120,124,56,14,15,255,255,1,192,255,127,128,60,15,135,192,3,192,0,0,0,60,0,0,0,3,224,6,0,0,63,255,192,0,1,255,248,0,0,7,252,0,0,0,127,0,0,63,128,0,63,192,0,31,240,0,31,248,0,15,190,0,7,159,0,7,207,128,3,227,224,3,225,240,1,240,252,0,248,62,0,248,31,0,124,15,192,63,255,224,63,255,240,31,255,252,31,255,254,15,128,31,135,192,7,199,192,3,227,224,1,251,240,0,124,31,255,224,15,255,252,7,255,255,3,255,255,129,240,7,224,248,1,240,124,0,248,62,0,124,31,0,124,15,255,252,7,255,252,3,255,254,1,255,255,128,248,3,224,124,0,248,62,0,124,31,0,62,15,128,31,7,192,31,131,255,255,129,255,255,192,255,255,192,127,255,128,0,127,128,0,255,248,1,255,254,1,255,255,128,252,7,224,248,1,240,124,0,124,124,0,62,62,0,31,31,0,0,15,128,0,7,192,0,3,224,0,1,240,0,0,248,0,120,124,0,62,31,0,31,15,128,31,3,240,31,129,255,255,128,127,255,128,15,255,128,1,254,0,31,255,0,15,255,240,7,255,252,3,255,255,1,240,15,192,248,3,240,124,0,248,62,0,124,31,0,31,15,128,15,135,192,7,195,224,3,225,240,1,240,248,0,248,124,0,124,62,0,124,31,0,62,15,128,63,7,192,63,3,255,255,1,255,255,0,255,255,0,127,254,0,31,255,252,31,255,252,31,255,252,31,255,248,31,0,0,31,0,0,31,0,0,31,0,0,31,0,0,31,255,240,31,255,240,31,255,240,31,255,240,31,0,0,31,0,0,31,0,0,31,0,0,31,0,0,31,0,0,31,255,248,31,255,252,31,255,252,31,255,252,31,255,240,255,255,135,255,252,63,255,225,240,0,15,128,0,124,0,3,224,0,31,0,0,248,0,7,255,248,63,255,193,255,254,15,255,240,124,0,3,224,0,31,0,0,248,0,7,192,0,62,0,1,240,0,15,128,0,124,0,0,0,63,192,0,63,255,0,15,255,240,3,255,255,0,252,3,240,63,0,63,7,192,3,225,240,0,124,62,0,0,7,192,0,0,248,31,254,31,3,255,195,224,127,248,124,15,255,15,128,3,225,240,0,124,31,0,15,131,240,3,240,63,0,254,7,255,255,192,127,255,248,3,255,231,0,31,240,224,31,0,62,7,192,15,129,240,3,224,124,0,248,31,0,62,7,192,15,129,240,3,224,124,0,248,31,0,62,7,255,255,129,255,255,224,127,255,248,31,255,254,7,192,15,129,240,3,224,124,0,248,31,0,62,7,192,15,129,240,3,224,124,0,248,31,0,62,7,192,15,129,240,3,224,31,7,193,240,124,31,7,193,240,124,31,7,193,240,124,31,7,193,240,124,31,7,193,240,124,31,7,193,240,0,7,192,0,124,0,7,192,0,124,0,7,192,0,124,0,7,192,0,124,0,7,192,0,124,0,7,192,0,124,0,7,192,0,124,0,7,199,192,124,124,7,199,192,124,126,15,131,255,248,63,255,0,255,224,3,248,0,31,0,127,15,128,127,7,192,127,3,224,127,1,240,127,0,248,63,0,124,63,0,62,63,0,31,63,0,15,191,0,7,255,192,3,255,240,1,255,248,0,255,126,0,127,31,128,63,15,192,31,3,240,15,128,252,7,192,126,3,224,31,129,240,7,224,248,3,240,124,0,252,31,0,0,248,0,7,192,0,62,0,1,240,0,15,128,0,124,0,3,224,0,31,0,0,248,0,7,192,0,62,0,1,240,0,15,128,0,124,0,3,224,0,31,0,0,248,0,7,192,0,63,255,225,255,255,15,255,248,127,255,192,31,224,15,240,127,128,63,193,255,0,255,7,252,7,252,31,240,31,240,127,224,127,193,255,131,223,7,222,15,124,31,120,61,240,125,240,247,193,247,199,159,7,207,30,124,31,62,121,240,124,251,199,193,241,239,31,7,199,188,124,31,31,241,240,124,63,135,193,240,254,31,7,195,248,124,31,15,193,240,124,31,7,193,240,124,31,0,31,0,31,7,224,7,193,252,1,240,127,0,124,31,224,31,7,252,7,193,255,129,240,127,240,124,31,252,31,7,223,135,193,247,241,240,124,254,124,31,31,159,7,195,247,193,240,127,240,124,31,252,31,3,255,7,192,127,193,240,15,240,124,3,252,31,0,127,7,192,15,193,240,1,240,0,63,192,0,63,255,0,15,255,240,3,255,255,0,252,3,240,63,0,63,7,192,3,225,240,0,124,62,0,7,199,192,0,248,248,0,31,31,0,3,227,224,0,124,124,0,15,143,128,1,241,240,0,124,31,0,15,131,240,3,240,63,0,252,3,255,255,0,63,255,192,3,255,240,0,15,240,0,31,255,192,31,255,240,31,255,248,31,255,252,31,0,124,31,0,62,31,0,62,31,0,62,31,0,62,31,0,126,31,0,252,31,255,252,31,255,248,31,255,224,31,255,0,31,0,0,31,0,0,31,0,0,31,0,0,31,0,0,31,0,0,31,0,0,31,0,0,0,63,192,0,63,255,0,15,255,240,3,255,255,0,252,3,240,63,0,63,7,192,3,225,240,0,124,62,0,7,199,192,0,248,248,0,31,31,0,3,227,224,0,124,124,0,15,143,128,1,241,240,0,124,31,0,15,131,240,3,240,63,0,252,3,255,255,0,63,255,224,3,255,240,0,15,254,0,0,15,224,0,0,254,0,0,15,224,0,0,254,0,31,255,192,15,255,248,7,255,254,3,255,255,129,240,7,192,248,1,240,124,0,248,62,0,124,31,0,62,15,128,63,7,192,31,3,255,255,129,255,255,128,255,255,128,127,255,128,62,3,224,31,1,240,15,128,124,7,192,62,3,224,15,129,240,7,192,248,1,240,124,0,248,0,255,128,7,255,192,31,255,192,127,255,193,248,15,131,224,15,135,192,31,15,192,0,31,240,0,31,252,0,31,255,0,31,255,128,15,255,128,1,255,0,0,127,0,0,62,62,0,124,124,0,248,252,3,240,255,255,192,255,255,0,255,252,0,63,192,0,127,255,249,255,255,231,255,255,159,255,254,0,124,0,1,240,0,7,192,0,31,0,0,124,0,1,240,0,7,192,0,31,0,0,124,0,1,240,0,7,192,0,31,0,0,124,0,1,240,0,7,192,0,31,0,0,124,0,1,240,0,7,192,0,62,0,124,62,0,124,62,0,124,62,0,124,62,0,124,62,0,124,62,0,124,62,0,124,62,0,124,62,0,124,62,0,124,62,0,124,62,0,124,62,0,124,62,0,124,62,0,124,62,0,124,31,0,248,31,129,248,15,255,240,15,255,224,3,255,192,0,255,0,124,0,62,124,0,126,126,0,124,62,0,124,62,0,252,63,0,248,31,0,248,31,1,240,15,129,240,15,129,240,15,131,224,7,195,224,7,195,224,7,199,192,3,231,192,3,231,192,3,255,128,1,255,128,1,255,0,0,255,0,0,255,0,0,254,0,0,126,0,252,7,240,31,62,3,248,15,159,1,252,7,207,192,254,3,227,224,255,129,225,240,127,193,240,248,63,224,248,126,30,240,120,31,15,124,60,15,143,190,62,7,199,143,31,3,243,199,143,0,249,227,231,128,125,241,247,192,62,240,251,224,31,120,61,224,7,252,31,240,3,254,15,248,1,254,7,252,0,255,1,252,0,63,128,254,0,31,192,127,0,15,192,63,0,127,0,126,63,0,252,31,129,248,15,193,240,15,195,240,7,231,224,3,247,192,3,255,192,1,255,128,0,255,0,0,255,0,0,255,0,0,255,0,1,255,128,3,255,192,3,231,192,7,199,224,15,195,240,31,129,248,31,1,248,63,0,252,126,0,126,124,0,126,126,0,62,63,0,126,31,0,124,31,128,252,15,192,248,7,193,240,7,227,240,3,227,224,1,247,192,1,255,192,0,255,128,0,127,0,0,127,0,0,62,0,0,62,0,0,62,0,0,62,0,0,62,0,0,62,0,0,62,0,0,62,0,0,62,0,0,62,0,63,255,248,255,255,227,255,255,143,255,252,0,7,224,0,63,0,1,252,0,7,224,0,63,0,1,252,0,15,224,0,63,0,1,252,0,15,224,0,63,0,1,252,0,15,224,0,63,0,1,248,0,15,255,254,127,255,249,255,255,231,255,255,128,31,227,252,127,143,225,224,60,7,128,240,30,3,192,120,15,1,224,60,7,128,240,30,3,192,120,15,1,224,60,7,128,240,30,3,192,120,15,225,254,63,199,248,224,30,1,192,56,7,0,240,14,1,192,60,3,128,112,14,1,224,28,3,128,112,15,0,224,28,3,128,120,7,0,224,30,1,192,56,255,31,227,252,63,128,240,30,3,192,120,15,1,224,60,7,128,240,30,3,192,120,15,1,224,60,7,128,240,30,3,192,120,15,1,224,60,63,143,241,254,63,192,0,120,0,3,224,0,15,192,0,127,0,1,254,0,7,56,0,60,240,0,227,192,7,135,0,30,30,0,112,120,3,192,240,15,3,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,255,255,255,255,192,120,60,30,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,248,0,255,224,31,255,3,255,248,62,15,131,192,248,0,15,128,63,248,15,255,131,255,248,63,15,135,192,248,124,15,135,193,248,127,255,227,255,255,63,231,240,252,62,62,0,3,224,0,62,0,3,224,0,62,0,3,224,0,62,126,3,239,240,63,255,131,255,252,63,7,195,224,62,62,3,227,224,62,62,3,227,224,62,62,3,227,224,62,62,3,227,240,124,63,255,195,255,248,60,255,3,199,224,3,248,1,255,192,127,252,31,255,195,224,248,248,15,159,1,243,224,0,124,0,15,128,1,240,0,62,3,231,192,124,124,31,15,255,224,255,248,15,254,0,127,0,0,7,192,0,124,0,7,192,0,124,0,7,192,0,124,7,231,192,255,124,31,255,195,255,252,62,15,199,192,124,124,7,199,192,124,124,7,199,192,124,124,7,199,192,124,124,7,195,224,252,63,255,193,255,252,15,251,192,126,60,3,248,1,255,192,127,252,31,255,195,224,248,248,15,159,1,243,255,254,127,255,207,255,249,240,0,62,3,231,192,124,124,31,143,255,224,255,252,15,254,0,127,0,3,240,127,131,252,63,1,240,15,129,255,207,254,127,243,255,135,192,62,1,240,15,128,124,3,224,31,0,248,7,192,62,1,240,15,128,124,3,224,0,0,248,0,7,192,0,124,3,255,224,127,252,15,255,192,127,255,7,224,248,62,3,225,240,31,7,193,248,63,255,128,255,252,3,255,192,127,248,7,192,0,62,0,1,255,252,15,255,248,63,255,227,255,255,190,0,125,240,3,239,255,254,127,255,241,255,255,7,255,224,62,0,3,224,0,62,0,3,224,0,62,0,3,224,0,62,62,3,239,240,63,255,131,255,252,63,15,195,224,124,62,7,195,224,124,62,7,195,224,124,62,7,195,224,124,62,7,195,224,124,62,7,195,224,124,62,7,195,224,124,62,31,15,135,128,0,0,248,124,62,31,15,135,195,225,240,248,124,62,31,15,135,195,225,240,248,124,15,129,240,62,7,128,0,0,3,224,124,15,129,240,62,7,192,248,31,3,224,124,15,129,240,62,7,192,248,31,3,224,124,15,129,241,254,127,143,240,248,0,62,0,7,192,0,248,0,31,0,3,224,0,124,0,15,129,241,240,124,62,31,7,199,224,249,248,31,126,3,239,128,127,240,15,254,1,255,224,63,254,7,231,192,248,124,31,15,131,224,248,124,31,15,129,241,240,63,62,31,15,135,195,225,240,248,124,62,31,15,135,195,225,240,248,124,62,31,15,135,195,225,240,248,124,60,126,15,192,121,254,127,192,255,255,255,193,255,255,255,131,240,254,31,135,192,248,31,15,129,240,62,31,3,224,124,62,7,192,248,124,15,129,240,248,31,3,225,240,62,7,195,224,124,15,135,192,248,31,15,129,240,62,31,3,224,124,62,7,192,248,124,15,129,240,60,126,3,207,240,63,255,131,255,252,63,15,195,224,124,62,7,195,224,124,62,7,195,224,124,62,7,195,224,124,62,7,195,224,124,62,7,195,224,124,62,7,195,224,124,1,248,0,255,240,31,255,131,255,252,62,7,199,192,62,124,3,231,192,62,124,3,231,192,62,124,3,231,192,62,124,3,227,224,124,63,255,193,255,248,15,255,0,31,128,60,126,3,223,240,63,255,131,255,252,63,7,195,224,62,62,3,227,224,62,62,3,227,224,62,62,3,227,224,62,62,3,227,240,124,63,255,195,255,248,62,255,3,231,224,62,0,3,224,0,62,0,3,224,0,62,0,3,224,0,7,227,192,255,60,31,255,195,255,252,62,15,199,192,124,124,7,199,192,124,124,7,199,192,124,124,7,199,192,124,124,7,195,224,252,63,255,193,255,252,15,247,192,126,124,0,7,192,0,124,0,7,192,0,124,0,7,192,0,124,60,121,231,207,126,127,243,255,159,192,252,7,192,62,1,240,15,128,124,3,224,31,0,248,7,192,62,1,240,0,7,248,7,255,131,255,240,255,254,124,15,159,1,231,240,0,255,128,31,252,1,255,192,15,248,0,126,124,15,159,3,231,255,248,255,252,31,254,1,254,0,15,0,240,15,0,240,31,7,254,127,231,254,127,225,240,31,1,240,31,1,240,31,1,240,31,1,240,31,1,254,15,224,254,7,224,62,7,195,224,124,62,7,195,224,124,62,7,195,224,124,62,7,195,224,124,62,7,195,224,124,62,7,195,224,124,62,7,195,224,252,63,255,193,255,252,15,243,192,124,60,248,3,207,128,249,240,31,62,7,195,224,248,124,30,7,135,192,248,248,31,30,1,227,192,62,248,7,222,0,123,192,15,240,1,254,0,31,192,3,240,0,62,0,248,31,3,231,129,248,62,124,31,131,199,195,248,60,124,63,135,195,195,252,120,62,59,199,131,231,188,120,30,123,207,1,231,158,240,31,113,239,0,247,30,224,15,241,254,0,255,15,224,7,224,254,0,126,15,192,7,224,252,0,62,7,192,126,7,195,224,124,31,15,129,249,240,15,159,0,127,224,7,252,0,63,192,1,248,0,63,128,3,252,0,127,224,15,190,1,241,240,31,31,131,224,248,126,7,199,192,126,124,3,239,128,120,240,31,31,3,227,224,120,62,31,7,195,224,120,120,15,159,1,243,192,30,120,3,239,0,127,192,7,248,0,255,0,15,192,1,248,0,63,0,7,192,1,248,3,254,0,127,128,15,224,1,248,0,63,255,143,255,227,255,248,255,252,0,126,0,31,0,15,192,7,224,1,240,0,252,0,126,0,63,0,15,192,7,224,3,255,249,255,254,127,255,159,255,224,1,224,63,3,248,31,0,240,7,128,60,1,224,15,0,120,3,192,30,0,240,15,129,248,15,0,126,0,248,3,192,30,0,240,7,128,60,1,224,15,0,120,3,224,31,192,126,0,240,28,14,7,3,129,192,224,112,56,28,14,7,3,129,192,224,112,56,28,14,7,3,129,192,224,112,56,28,14,7,3,129,192,224,112,60,3,248,15,224,31,0,120,3,192,30,0,240,7,128,60,1,224,15,0,120,3,224,15,192,30,3,240,62,1,224,15,0,120,3,192,30,0,240,7,128,60,3,224,127,7,240,30,0,3,0,16,63,128,195,255,255,15,255,252,56,63,224,128,62,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +static const Glyph f0_34_glyphs[]={{0,7,0,0,23,7,32},{0,11,23,0,0,11,33},{32,16,23,0,0,16,34},{78,21,23,0,0,21,35},{139,18,26,0,-1,18,36},{198,33,23,0,0,33,37},{293,26,23,0,0,26,38},{368,9,23,0,0,9,39},{394,13,30,0,-1,13,40},{443,13,30,0,-1,13,41},{492,14,23,0,0,14,42},{533,22,18,0,5,22,43},{583,11,11,0,18,11,44},{599,11,11,0,12,11,45},{615,11,5,0,18,11,46},{622,11,26,0,-1,10,47},{658,21,23,0,0,21,48},{719,20,23,0,0,20,49},{777,21,23,0,0,21,50},{838,20,23,0,0,20,51},{896,20,23,0,0,20,52},{954,20,23,0,0,20,53},{1012,20,23,0,0,20,54},{1070,20,23,0,0,20,55},{1128,20,23,0,0,20,56},{1186,20,23,0,0,20,57},{1244,11,18,0,5,11,58},{1269,11,24,0,5,11,59},{1302,22,18,0,5,22,60},{1352,22,14,0,9,22,61},{1391,22,18,0,5,22,62},{1441,21,23,0,0,21,63},{1502,35,28,0,0,35,64},{1625,25,23,0,0,25,65},{1697,25,23,0,0,25,66},{1769,25,23,0,0,25,67},{1841,25,23,0,0,25,68},{1913,24,23,0,0,24,69},{1982,21,23,0,0,21,70},{2043,27,23,0,0,27,71},{2121,26,23,0,0,26,72},{2196,10,23,0,0,10,73},{2225,20,23,0,0,20,74},{2283,25,23,0,0,25,75},{2355,21,23,0,0,21,76},{2416,30,23,0,0,30,77},{2503,26,23,0,0,26,78},{2578,27,23,0,0,27,79},{2656,24,23,0,0,24,80},{2725,27,27,0,0,27,81},{2817,25,23,0,0,25,82},{2889,23,23,0,0,23,83},{2956,22,23,0,0,22,84},{3020,24,23,0,0,24,85},{3089,24,23,0,0,24,86},{3158,33,23,0,0,33,87},{3253,24,23,0,0,24,88},{3322,24,23,-1,0,23,89},{3391,22,23,0,0,22,90},{3455,11,31,0,-1,11,91},{3498,11,26,0,-1,10,92},{3534,11,31,0,-1,11,93},{3577,22,23,0,0,22,94},{3641,18,5,0,23,18,95},{3653,8,24,0,-1,8,96},{3677,20,18,0,5,20,97},{3722,20,24,0,-1,20,98},{3782,19,18,0,5,19,99},{3825,20,24,0,-1,20,100},{3885,19,18,0,5,19,101},{3928,13,24,0,-1,13,102},{3967,21,27,0,2,21,103},{4038,20,24,0,-1,20,104},{4098,9,24,0,-1,9,105},{4125,11,30,-2,-1,9,106},{4167,19,24,0,-1,19,107},{4224,9,24,0,-1,9,108},{4251,31,18,0,5,31,109},{4321,20,18,0,5,20,110},{4366,20,18,0,5,20,111},{4411,20,24,0,5,20,112},{4471,20,24,0,5,20,113},{4531,13,18,0,5,13,114},{4561,18,18,0,5,18,115},{4602,12,23,0,0,12,116},{4637,20,18,0,5,20,117},{4682,19,18,0,5,19,118},{4725,28,18,0,5,27,119},{4788,20,18,0,5,19,120},{4833,19,24,0,5,19,121},{4890,18,18,0,5,18,122},{4931,13,30,0,-1,13,123},{4980,9,31,0,-1,9,124},{5015,13,30,0,-1,13,125},{5064,22,13,0,10,22,126}}; +static const uint8_t f0_36_bits[]={31,3,224,124,15,129,240,62,7,192,248,31,3,224,124,7,128,224,28,3,128,112,14,1,192,0,0,0,0,62,7,192,248,31,3,224,62,124,62,124,62,124,62,124,62,124,62,124,62,124,62,124,28,56,28,56,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,224,3,135,128,30,30,0,120,120,1,225,192,7,15,0,28,60,7,255,254,31,255,248,127,255,225,255,255,128,225,224,7,135,128,30,30,0,120,112,1,193,192,127,255,225,255,255,135,255,254,31,255,248,15,14,0,56,120,1,225,224,7,135,128,30,28,0,112,112,0,0,96,0,12,0,1,128,0,48,0,63,128,31,254,15,255,225,255,252,126,111,207,140,249,241,128,63,176,3,255,0,127,252,7,255,192,31,252,0,127,192,13,249,241,159,62,51,231,230,252,127,255,7,255,224,127,248,3,248,0,12,0,1,128,0,48,0,6,0,3,240,0,224,0,255,0,28,0,63,240,7,0,15,159,1,192,1,225,224,56,0,60,60,14,0,7,135,131,128,0,240,240,112,0,30,30,28,0,3,231,199,0,0,63,240,224,0,3,252,56,0,0,63,14,15,0,0,1,199,248,0,0,113,255,128,0,28,60,240,0,3,143,15,0,0,225,225,224,0,56,60,60,0,7,7,135,128,1,192,240,240,0,112,30,30,0,14,3,231,192,3,128,63,240,0,224,3,252,0,28,0,63,0,0,127,128,0,127,254,0,31,255,224,7,255,254,1,252,15,192,63,0,252,7,192,15,128,248,1,240,31,0,0,3,240,0,0,63,0,0,3,255,31,224,63,227,252,15,252,127,131,255,15,240,252,1,240,63,0,62,7,192,7,192,248,0,248,31,0,31,3,240,7,224,63,1,248,7,255,255,0,127,255,192,3,255,224,0,31,240,0,62,31,15,135,195,225,240,248,124,28,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,240,15,0,120,7,128,124,3,192,62,1,240,31,0,248,7,192,62,3,224,31,0,248,7,192,62,1,240,15,128,124,3,224,15,128,124,3,224,31,0,124,3,224,15,0,124,1,224,7,128,60,0,240,120,1,224,7,0,60,1,240,7,128,62,0,240,7,192,62,1,240,15,192,62,1,240,15,128,124,3,224,31,0,248,7,192,62,1,240,31,0,248,7,192,60,3,224,30,1,240,15,0,112,7,128,120,0,3,128,7,0,142,65,217,199,255,131,252,1,240,15,240,127,248,118,112,142,64,28,0,56,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,60,0,0,120,0,0,240,0,1,224,0,3,192,0,7,128,0,15,0,0,30,0,31,255,240,63,255,224,127,255,192,255,255,128,3,192,0,7,128,0,15,0,0,30,0,0,60,0,0,120,0,0,240,0,1,224,0,31,3,224,124,15,129,240,6,1,192,56,14,3,128,96,0,0,127,207,249,255,63,224,0,0,0,0,0,0,0,0,0,0,0,31,3,224,124,15,129,240,1,224,56,7,1,224,60,7,0,224,60,7,128,224,28,7,128,240,28,3,128,240,30,3,128,112,30,3,192,112,14,3,192,120,14,1,192,120,15,0,0,252,0,15,252,0,127,248,3,255,240,31,135,224,124,15,129,240,62,15,128,124,62,1,240,248,7,195,224,31,15,128,124,62,1,240,248,7,195,224,31,15,128,124,62,1,240,248,7,195,224,31,7,192,248,31,3,224,126,31,128,255,252,1,255,224,3,255,0,3,240,0,0,14,0,1,240,0,127,128,31,252,0,255,224,7,255,0,63,248,0,7,192,0,62,0,1,240,0,15,128,0,124,0,3,224,0,31,0,0,248,0,7,192,0,62,0,1,240,0,15,128,0,124,0,3,224,0,31,0,63,255,193,255,254,15,255,240,127,255,128,0,254,0,31,254,0,255,252,7,255,248,31,135,224,252,15,195,224,31,15,128,124,62,1,240,0,15,192,0,63,0,1,248,0,15,224,0,127,0,7,248,0,63,192,1,254,0,15,240,0,127,0,3,248,0,31,192,0,126,0,3,255,255,15,255,252,63,255,240,255,255,192,0,254,0,31,254,0,255,252,7,255,248,63,135,240,252,15,195,224,31,15,128,124,0,1,240,0,15,128,0,126,0,31,240,0,127,0,1,254,0,7,252,0,1,248,0,3,240,0,7,195,224,31,15,128,124,63,3,240,126,31,193,255,254,3,255,240,7,255,128,3,248,0,1,248,0,15,128,0,124,0,3,224,0,62,24,1,241,192,15,142,0,248,240,7,199,128,124,124,3,227,224,62,31,1,224,248,31,7,192,240,62,15,129,240,127,255,243,255,255,159,255,252,255,255,224,0,248,0,7,192,0,62,0,1,240,0,15,128,0,124,0,15,255,224,63,255,128,255,254,3,255,248,15,0,0,60,0,0,240,0,7,192,0,31,0,0,124,248,1,255,248,7,255,240,31,255,224,126,31,128,240,63,0,0,124,0,1,240,0,7,195,224,31,15,128,124,63,3,240,126,31,129,255,254,3,255,240,3,255,128,3,248,0,0,252,0,15,252,0,127,248,3,255,240,31,135,192,124,31,129,240,62,7,128,248,62,0,0,248,248,3,239,248,15,255,240,63,255,224,254,31,131,240,63,15,192,124,62,1,240,248,7,195,224,31,7,192,124,31,3,240,126,31,128,255,254,1,255,240,3,255,128,3,248,0,63,255,241,255,255,143,255,252,127,255,227,255,254,0,1,224,0,31,0,1,240,0,15,0,0,248,0,15,128,0,124,0,7,192,0,62,0,3,224,0,31,0,0,240,0,15,128,0,124,0,3,224,0,62,0,1,240,0,15,128,0,124,0,3,224,0,31,0,0,0,254,0,15,254,0,127,252,3,255,248,31,199,224,126,15,193,240,31,7,192,124,31,1,240,126,15,192,252,126,3,255,240,7,255,128,31,254,0,255,254,7,225,248,63,3,240,248,7,195,224,31,15,128,124,63,3,240,126,31,193,255,254,3,255,240,7,255,128,7,248,0,1,248,0,127,240,7,255,192,127,255,3,240,252,63,3,225,240,15,15,128,124,124,3,227,224,31,31,129,248,126,31,195,255,254,15,255,240,63,239,128,124,124,0,3,224,0,31,15,128,248,124,15,131,240,124,15,135,224,127,254,1,255,224,7,254,0,15,192,0,31,1,240,31,1,240,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,240,31,1,240,31,1,240,31,3,224,124,15,129,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,31,3,224,124,15,128,48,14,1,192,112,28,3,0,0,0,0,0,0,0,0,224,0,7,192,0,63,128,3,254,0,31,248,0,255,192,15,252,0,63,224,0,126,0,0,248,0,1,252,0,3,254,0,1,255,128,0,255,192,0,63,240,0,31,240,0,7,224,0,3,192,0,1,0,31,255,240,63,255,224,127,255,192,255,255,128,0,0,0,0,0,0,0,0,0,0,0,31,255,240,63,255,224,127,255,192,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,48,0,0,120,0,0,254,0,1,255,0,1,255,192,0,127,224,0,63,240,0,15,248,0,7,240,0,3,224,0,31,192,1,255,0,15,252,0,255,192,15,254,0,31,224,0,63,0,0,120,0,0,128,0,0,1,252,0,31,252,0,255,252,7,255,240,31,135,224,252,15,131,224,62,15,128,248,62,3,224,0,31,128,0,252,0,7,240,0,63,128,1,252,0,15,224,0,63,0,0,248,0,3,224,0,15,128,0,0,0,0,0,0,3,224,0,15,128,0,62,0,0,248,0,3,224,0,0,3,255,0,0,0,255,255,0,0,31,255,254,0,3,248,1,252,0,62,0,1,240,3,224,0,7,192,60,3,240,14,1,192,127,224,120,30,7,255,129,192,224,124,62,15,7,3,192,240,56,120,0,7,129,195,128,0,60,14,28,0,63,224,112,224,15,255,3,135,1,255,248,28,56,15,131,192,225,192,248,30,14,14,7,128,240,112,112,60,15,135,131,193,240,252,120,14,7,255,255,128,112,63,231,248,3,192,126,31,0,15,0,0,0,0,60,0,0,0,1,240,0,0,0,7,240,7,0,0,15,255,248,0,0,31,255,192,0,0,31,240,0,0,0,63,128,0,31,224,0,7,248,0,3,255,0,0,255,192,0,63,240,0,31,126,0,7,207,128,1,243,224,0,252,252,0,62,31,0,15,135,224,7,225,248,1,240,62,0,252,15,192,63,3,240,15,128,124,7,255,255,129,255,255,224,127,255,248,63,255,255,15,128,15,199,224,1,249,248,0,126,124,0,15,191,0,3,240,31,255,240,7,255,255,1,255,255,224,127,255,248,31,0,63,7,192,15,193,240,1,240,124,0,124,31,0,31,7,192,15,129,240,7,224,127,255,240,31,255,240,7,255,254,1,255,255,224,124,0,248,31,0,31,7,192,7,193,240,1,240,124,0,124,31,0,31,7,192,15,193,255,255,224,127,255,248,31,255,252,7,255,252,0,0,63,192,0,31,255,0,15,255,240,3,255,255,0,254,7,240,31,128,126,7,224,7,224,248,0,124,31,0,15,135,192,1,240,248,0,0,31,0,0,3,224,0,0,124,0,0,15,128,0,1,240,0,0,62,0,15,135,224,1,240,124,0,62,15,192,15,193,248,3,240,31,192,254,1,255,255,128,31,255,224,1,255,248,0,7,248,0,31,255,128,3,255,254,0,127,255,224,15,255,254,1,240,15,224,62,0,252,7,192,15,192,248,0,248,31,0,31,131,224,1,240,124,0,62,15,128,7,193,240,0,248,62,0,31,7,192,3,224,248,0,124,31,0,15,131,224,3,240,124,0,124,15,128,31,129,240,3,224,62,1,252,7,255,255,0,255,255,192,31,255,240,3,255,240,0,31,255,254,15,255,255,7,255,255,131,255,255,129,240,0,0,248,0,0,124,0,0,62,0,0,31,0,0,15,128,0,7,192,0,3,255,255,1,255,255,128,255,255,192,127,255,224,62,0,0,31,0,0,15,128,0,7,192,0,3,224,0,1,240,0,0,248,0,0,127,255,240,63,255,252,31,255,254,15,255,255,0,31,255,248,127,255,225,255,255,135,255,254,31,0,0,124,0,1,240,0,7,192,0,31,0,0,124,0,1,240,0,7,192,0,31,255,240,127,255,193,255,255,7,255,252,31,0,0,124,0,1,240,0,7,192,0,31,0,0,124,0,1,240,0,7,192,0,31,0,0,124,0,0,0,63,224,0,7,255,192,0,255,255,128,15,255,254,0,254,3,248,7,224,15,192,126,0,63,3,224,0,248,63,0,7,193,240,0,0,15,128,0,0,124,0,0,3,224,63,252,31,1,255,224,248,15,255,7,192,127,248,62,0,7,193,248,0,62,7,192,3,240,63,0,31,128,252,1,252,7,240,63,224,31,255,255,0,127,255,248,1,255,249,192,1,254,14,0,31,0,31,3,224,3,224,124,0,124,15,128,15,129,240,1,240,62,0,62,7,192,7,192,248,0,248,31,0,31,3,224,3,224,127,255,252,15,255,255,129,255,255,240,63,255,254,7,255,255,192,248,0,248,31,0,31,3,224,3,224,124,0,124,15,128,15,129,240,1,240,62,0,62,7,192,7,192,248,0,248,31,0,31,3,224,3,224,31,3,224,124,15,129,240,62,7,192,248,31,3,224,124,15,129,240,62,7,192,248,31,3,224,124,15,129,240,62,7,192,248,31,3,224,0,3,224,0,15,128,0,62,0,0,248,0,3,224,0,15,128,0,62,0,0,248,0,3,224,0,15,128,0,62,0,0,248,0,3,224,0,15,128,0,62,0,0,248,0,3,225,240,15,135,192,62,31,0,248,126,7,192,252,63,3,255,252,7,255,224,15,255,0,15,224,0,31,0,63,135,192,31,193,240,15,224,124,3,240,31,1,248,7,192,252,1,240,126,0,124,63,128,31,31,192,7,207,224,1,247,240,0,127,252,0,31,255,128,7,255,240,1,255,252,0,127,159,128,31,199,240,7,224,252,1,240,31,128,124,7,224,31,0,252,7,192,63,129,240,7,224,124,0,252,31,0,63,135,192,7,224,31,0,0,124,0,1,240,0,7,192,0,31,0,0,124,0,1,240,0,7,192,0,31,0,0,124,0,1,240,0,7,192,0,31,0,0,124,0,1,240,0,7,192,0,31,0,0,124,0,1,240,0,7,192,0,31,0,0,127,255,225,255,255,135,255,254,31,255,248,127,255,224,31,224,7,248,31,240,7,248,31,240,15,248,31,240,15,248,31,248,15,248,31,248,15,248,31,248,31,248,31,120,30,248,31,124,30,248,31,124,30,248,31,124,60,248,31,60,60,248,31,62,60,248,31,62,124,248,31,30,120,248,31,30,120,248,31,31,120,248,31,31,120,248,31,15,240,248,31,15,240,248,31,15,240,248,31,15,240,248,31,7,224,248,31,7,224,248,31,7,224,248,31,3,192,248,31,0,15,129,248,0,248,31,192,15,129,254,0,248,31,224,15,129,255,0,248,31,248,15,129,255,128,248,31,252,15,129,255,224,248,31,127,15,129,247,240,248,31,63,143,129,241,252,248,31,15,207,129,240,254,248,31,7,255,129,240,63,248,31,1,255,129,240,31,248,31,0,255,129,240,7,248,31,0,127,129,240,3,248,31,0,31,129,240,0,248,0,63,224,0,7,255,192,0,255,255,128,15,255,254,0,254,3,248,7,224,15,192,126,0,63,3,224,0,248,63,0,7,225,240,0,31,15,128,0,248,124,0,7,195,224,0,62,31,0,1,240,248,0,15,135,192,0,124,62,0,3,224,248,0,63,7,192,1,240,63,0,31,128,252,1,248,7,240,31,192,31,255,252,0,127,255,192,0,255,248,0,1,255,0,0,31,255,224,15,255,252,7,255,255,3,255,255,193,240,7,224,248,1,248,124,0,124,62,0,62,31,0,31,15,128,15,135,192,7,195,224,7,225,240,7,224,255,255,240,127,255,240,63,255,240,31,255,224,15,128,0,7,192,0,3,224,0,1,240,0,0,248,0,0,124,0,0,62,0,0,31,0,0,15,128,0,0,0,63,224,0,7,255,192,0,255,255,128,15,255,254,0,254,3,248,7,224,15,192,126,0,63,3,224,0,248,63,0,7,225,240,0,31,15,128,0,248,124,0,7,195,224,0,62,31,0,1,240,248,0,15,135,192,0,124,62,0,3,224,248,0,63,7,192,1,240,63,0,31,128,252,1,252,7,240,31,192,31,255,252,0,127,255,192,0,255,252,0,1,255,224,0,0,63,128,0,0,254,0,0,3,240,0,0,15,192,0,0,62,0,31,255,224,7,255,254,1,255,255,192,127,255,248,31,0,126,7,192,15,193,240,1,240,124,0,124,31,0,31,7,192,7,193,240,1,240,124,0,252,31,0,126,7,255,255,129,255,255,192,127,255,224,31,255,240,7,192,126,1,240,15,128,124,3,240,31,0,124,7,192,31,129,240,3,224,124,0,252,31,0,31,7,192,7,224,0,127,128,1,255,248,1,255,254,1,255,255,129,252,15,192,248,3,240,124,0,248,62,0,124,31,128,0,15,248,0,3,255,192,1,255,248,0,127,255,0,15,255,192,1,255,240,0,15,252,0,0,254,0,0,63,15,128,15,135,192,7,195,240,3,224,252,7,224,127,255,240,31,255,240,3,255,224,0,127,192,0,127,255,252,255,255,249,255,255,243,255,255,231,255,255,192,15,128,0,31,0,0,62,0,0,124,0,0,248,0,1,240,0,3,224,0,7,192,0,15,128,0,31,0,0,62,0,0,124,0,0,248,0,1,240,0,3,224,0,7,192,0,15,128,0,31,0,0,62,0,0,124,0,0,248,0,31,0,15,129,240,0,248,31,0,15,129,240,0,248,31,0,15,129,240,0,248,31,0,15,129,240,0,248,31,0,15,129,240,0,248,31,0,15,129,240,0,248,31,0,15,129,240,0,248,31,0,15,129,240,0,248,31,0,15,129,240,0,248,31,0,15,129,248,1,248,15,192,63,0,254,7,240,7,255,254,0,63,255,192,1,255,248,0,3,252,0,124,0,31,63,0,15,159,128,15,199,192,7,195,240,3,225,248,3,240,124,1,240,63,0,248,31,128,252,7,192,124,3,240,62,1,248,63,0,124,31,0,63,15,128,31,143,192,7,199,192,3,243,224,1,251,240,0,125,240,0,63,248,0,31,252,0,7,252,0,3,254,0,1,254,0,0,127,0,0,63,128,0,252,3,248,7,207,128,127,0,249,248,15,224,31,63,1,254,7,199,224,127,192,248,124,15,248,31,15,193,255,3,225,248,61,224,120,63,15,190,31,3,225,247,195,224,124,62,248,124,15,199,159,15,1,248,241,227,224,31,62,62,124,3,231,199,207,128,126,240,249,224,15,222,15,124,0,251,193,255,128,31,248,63,240,3,255,7,252,0,127,192,255,128,7,248,15,240,0,255,1,254,0,31,224,63,128,3,248,7,240,0,63,0,126,0,63,0,63,15,224,31,129,248,7,224,127,3,240,15,225,248,1,248,126,0,127,63,0,15,223,128,1,255,224,0,127,240,0,15,248,0,1,254,0,0,127,0,0,63,224,0,15,252,0,7,255,0,3,255,224,0,253,252,0,126,63,0,31,135,224,15,193,252,7,224,63,1,248,15,224,252,1,248,126,0,63,31,128,15,224,126,0,63,63,0,31,143,192,31,131,240,15,129,248,15,192,126,7,192,63,7,224,15,199,224,3,243,224,1,251,240,0,127,240,0,31,248,0,15,248,0,3,248,0,1,252,0,0,124,0,0,62,0,0,31,0,0,15,128,0,7,192,0,3,224,0,1,240,0,0,248,0,0,124,0,0,62,0,0,31,0,0,63,255,252,127,255,248,255,255,241,255,255,192,0,63,0,0,254,0,3,248,0,7,240,0,31,192,0,127,0,0,254,0,3,248,0,15,224,0,31,192,0,127,0,1,252,0,3,248,0,15,224,0,31,128,0,127,0,1,252,0,3,248,0,15,255,255,63,255,254,127,255,252,255,255,248,15,240,127,131,252,30,0,240,7,128,60,1,224,15,0,120,3,192,30,0,240,7,128,60,1,224,15,0,120,3,192,30,0,240,7,128,60,1,224,15,0,120,3,192,30,0,240,7,128,63,193,254,15,240,240,30,1,192,56,7,128,240,14,1,192,60,7,128,112,14,1,224,60,3,128,112,15,1,224,28,3,128,120,15,0,224,28,3,192,120,7,0,224,30,127,131,252,31,224,15,0,120,3,192,30,0,240,7,128,60,1,224,15,0,120,3,192,30,0,240,7,128,60,1,224,15,0,120,3,192,30,0,240,7,128,60,1,224,15,0,120,3,193,254,15,240,127,128,0,124,0,0,248,0,3,248,0,7,240,0,15,224,0,61,224,0,123,192,1,227,192,3,199,128,7,7,128,30,15,0,60,30,0,240,30,1,224,60,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,255,255,255,255,255,192,252,62,15,131,192,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,252,0,127,248,7,255,224,127,255,131,240,252,31,3,224,0,31,0,0,248,1,255,192,127,254,7,255,240,127,15,135,224,124,62,3,225,240,63,15,195,248,127,255,241,255,255,199,252,254,31,131,224,62,0,1,240,0,15,128,0,124,0,3,224,0,31,0,0,248,0,7,199,192,62,255,129,255,254,15,255,248,127,15,195,240,63,31,128,248,248,7,199,192,62,62,1,241,240,15,143,128,124,124,3,227,240,31,31,129,248,254,31,135,255,252,63,255,193,231,252,15,15,128,1,252,0,127,240,31,255,129,255,252,63,15,195,224,126,126,3,231,192,62,124,0,7,192,0,124,0,7,192,0,124,3,231,224,62,62,7,227,240,252,31,255,193,255,248,7,255,0,31,192,0,3,224,0,31,0,0,248,0,7,192,0,62,0,1,240,0,15,128,124,124,15,251,224,255,255,15,255,248,126,31,195,224,126,63,1,241,240,15,143,128,124,124,3,227,224,31,31,0,248,248,7,199,192,62,31,3,240,252,63,135,255,252,31,255,224,127,207,0,248,120,1,252,0,127,240,31,255,129,255,252,63,15,195,224,126,126,3,231,192,62,127,255,231,255,254,127,255,231,192,0,124,0,7,192,62,62,7,227,240,254,31,255,193,255,248,7,255,0,31,192,3,240,63,131,252,31,224,248,7,192,62,15,254,127,243,255,159,252,31,0,248,7,192,62,1,240,15,128,124,3,224,31,0,248,7,192,62,1,240,15,128,124,3,224,0,0,124,0,1,248,0,3,224,7,255,128,63,254,1,255,248,3,241,248,15,193,248,31,1,240,62,3,224,124,7,192,248,31,128,248,126,1,255,248,1,255,224,7,255,0,31,0,0,124,0,0,252,0,1,255,255,1,255,255,129,255,255,7,255,255,31,0,126,124,0,124,252,1,249,255,255,227,255,255,195,255,255,1,255,248,0,62,0,1,240,0,15,128,0,124,0,3,224,0,31,0,0,248,0,7,199,224,62,127,129,255,254,15,255,248,127,159,195,240,126,31,129,240,248,15,135,192,124,62,3,225,240,31,15,128,248,124,7,195,224,62,31,1,240,248,15,135,192,124,62,3,225,240,31,15,128,248,62,31,15,135,192,0,0,0,124,62,31,15,135,195,225,240,248,124,62,31,15,135,195,225,240,248,124,62,31,15,128,15,129,240,62,7,192,0,0,0,0,124,15,129,240,62,7,192,248,31,3,224,124,15,129,240,62,7,192,248,31,3,224,124,15,129,240,62,7,192,248,63,63,231,248,255,15,128,62,0,3,224,0,62,0,3,224,0,62,0,3,224,0,62,0,3,224,126,62,7,195,224,252,62,31,131,227,240,62,126,3,231,192,62,248,3,255,128,63,252,3,255,224,63,254,3,251,240,63,31,3,225,248,62,15,131,224,252,62,7,227,224,126,62,3,240,62,31,15,135,195,225,240,248,124,62,31,15,135,195,225,240,248,124,62,31,15,135,195,225,240,248,124,62,31,15,128,60,62,7,192,121,254,63,192,255,254,255,193,255,255,255,195,248,255,31,135,224,252,31,15,129,240,62,31,3,224,124,62,7,192,248,124,15,129,240,248,31,3,225,240,62,7,195,224,124,15,135,192,248,31,15,129,240,62,31,3,224,124,62,7,192,248,124,15,129,240,248,31,3,225,240,62,7,192,60,63,1,231,252,15,255,240,127,255,195,248,126,31,129,240,248,15,135,192,124,62,3,225,240,31,15,128,248,124,7,195,224,62,31,1,240,248,15,135,192,124,62,3,225,240,31,15,128,248,124,7,192,1,252,0,63,248,7,255,240,63,255,131,240,126,31,1,241,248,7,207,128,62,124,1,243,224,15,159,0,124,248,3,231,192,31,63,0,248,248,15,135,224,252,31,255,192,255,254,1,255,192,3,248,0,60,62,1,231,252,15,255,240,127,255,195,248,126,31,129,248,252,7,199,192,62,62,1,241,240,15,143,128,124,124,3,227,224,31,31,128,248,252,15,199,240,252,63,255,225,255,254,15,191,224,124,124,3,224,0,31,0,0,248,0,7,192,0,62,0,1,240,0,15,128,0,3,225,224,127,207,7,255,248,127,255,195,240,254,31,3,241,248,15,143,128,124,124,3,227,224,31,31,0,248,248,7,199,192,62,63,1,240,248,31,135,225,252,63,255,224,255,255,3,254,248,7,199,192,0,62,0,1,240,0,15,128,0,124,0,3,224,0,31,0,0,248,60,120,243,227,255,143,254,63,248,254,3,240,15,192,62,0,248,3,224,15,128,62,0,248,3,224,15,128,62,0,248,3,224,15,128,3,248,3,255,224,255,254,31,255,231,224,252,248,15,159,128,3,252,0,63,248,3,255,192,31,254,0,127,224,0,252,248,15,159,1,243,240,126,63,255,199,255,240,63,252,1,254,0,15,0,120,3,192,30,1,240,15,129,255,207,254,127,243,255,135,192,62,1,240,15,128,124,3,224,31,0,248,7,192,62,1,240,15,192,127,193,254,15,240,31,128,62,3,225,240,31,15,128,248,124,7,195,224,62,31,1,240,248,15,135,192,124,62,3,225,240,31,15,128,248,124,7,195,224,62,31,1,240,252,31,135,225,252,31,255,224,255,255,3,254,120,15,195,192,252,3,231,192,62,124,3,231,224,124,62,7,195,224,124,63,15,129,240,248,31,15,129,249,240,15,159,0,249,224,7,158,0,127,224,7,252,0,63,192,3,252,0,63,128,1,248,0,31,128,248,15,128,251,224,126,15,159,7,240,124,248,63,131,231,193,252,30,31,15,224,240,248,255,143,135,199,188,120,30,61,227,192,249,239,30,7,206,125,240,62,241,239,0,247,143,120,7,252,127,192,63,195,252,0,254,15,224,7,240,127,0,63,131,240,0,252,31,128,7,192,252,0,126,3,241,248,63,15,193,240,63,31,128,252,248,7,239,128,31,252,0,127,192,3,252,0,15,224,0,127,0,7,252,0,127,224,3,255,128,62,126,3,241,240,31,15,193,240,63,31,129,248,248,7,224,124,1,247,192,62,126,3,227,224,62,62,3,195,240,124,31,7,193,240,120,15,143,128,248,248,15,143,0,125,240,7,223,0,125,224,3,254,0,63,224,1,252,0,31,192,1,252,0,15,128,0,248,0,31,0,3,240,3,254,0,63,224,3,252,0,63,0,0,63,255,199,255,248,255,255,31,255,192,3,240,0,126,0,31,128,7,224,0,252,0,63,0,15,192,3,248,0,126,0,31,128,7,240,0,252,0,63,255,207,255,249,255,255,63,255,224,0,240,15,224,63,1,240,7,128,30,0,120,1,224,7,128,30,0,120,1,224,7,128,30,0,248,31,192,124,1,252,0,248,1,224,7,128,30,0,120,1,224,7,128,30,0,120,1,224,7,128,31,0,63,0,254,0,240,30,7,129,224,120,30,7,129,224,120,30,7,129,224,120,30,7,129,224,120,30,7,129,224,120,30,7,129,224,120,30,7,129,224,120,30,7,129,224,120,30,7,128,60,1,252,3,240,3,224,7,128,30,0,120,1,224,7,128,30,0,120,1,224,7,128,30,0,124,0,254,0,248,15,224,124,1,224,7,128,30,0,120,1,224,7,128,30,0,120,1,224,7,128,62,3,240,31,192,60,0,0,0,0,15,128,16,127,224,225,255,255,195,255,255,135,7,254,8,1,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +static const Glyph f0_36_glyphs[]={{0,7,0,0,26,7,32},{0,11,26,0,0,11,33},{36,16,26,0,0,16,34},{88,22,26,0,0,22,35},{160,19,29,0,-1,19,36},{229,35,26,0,0,35,37},{343,27,26,0,0,27,38},{431,9,26,0,0,9,39},{461,13,33,0,-1,13,40},{515,13,33,0,-1,13,41},{569,15,26,0,0,15,42},{618,23,20,0,6,23,43},{676,11,12,0,21,11,44},{693,11,12,0,14,11,45},{710,11,5,0,21,11,46},{717,11,29,0,-1,11,47},{757,22,26,0,0,22,48},{829,21,26,0,0,21,49},{898,22,26,0,0,22,50},{970,22,26,0,0,22,51},{1042,21,26,0,0,21,52},{1111,22,26,0,0,22,53},{1183,22,26,0,0,22,54},{1255,21,26,0,0,21,55},{1324,22,26,0,0,22,56},{1396,21,26,0,0,21,57},{1465,12,20,0,6,12,58},{1495,11,27,0,6,11,59},{1533,23,20,0,6,23,60},{1591,23,16,0,10,23,61},{1637,23,20,0,6,23,62},{1695,22,26,0,0,22,63},{1767,37,31,0,0,37,64},{1911,26,26,0,0,26,65},{1996,26,26,0,0,26,66},{2081,27,26,0,0,27,67},{2169,27,26,0,0,27,68},{2257,25,26,0,0,25,69},{2339,22,26,0,0,22,70},{2411,29,26,0,0,29,71},{2506,27,26,0,0,27,72},{2594,11,26,0,0,11,73},{2630,22,26,0,0,22,74},{2702,26,26,0,0,26,75},{2787,22,26,0,0,22,76},{2859,32,26,0,0,32,77},{2963,28,26,0,0,28,78},{3054,29,26,0,0,29,79},{3149,25,26,0,0,25,80},{3231,29,31,0,0,29,81},{3344,26,26,0,0,26,82},{3429,25,26,0,0,25,83},{3511,23,26,0,0,23,84},{3586,28,26,0,0,28,85},{3677,25,26,0,0,25,86},{3759,35,26,0,0,35,87},{3873,26,26,0,0,25,88},{3958,25,26,0,0,25,89},{4040,23,26,0,0,23,90},{4115,13,33,0,-1,13,91},{4169,11,29,0,-1,11,92},{4209,13,33,0,-1,13,93},{4263,23,25,0,1,23,94},{4335,19,6,0,26,19,95},{4350,9,27,0,-1,8,96},{4381,21,20,0,6,21,97},{4434,21,27,0,-1,21,98},{4505,20,20,0,6,20,99},{4555,21,27,0,-1,21,100},{4626,20,20,0,6,20,101},{4676,13,27,0,-1,13,102},{4720,23,30,0,3,23,103},{4807,21,27,0,-1,21,104},{4878,9,27,0,-1,9,105},{4909,11,34,-2,-1,9,106},{4956,20,27,0,-1,20,107},{5024,9,27,0,-1,9,108},{5055,31,20,0,6,31,109},{5133,21,20,0,6,21,110},{5186,21,20,0,6,21,111},{5239,21,27,0,6,21,112},{5310,21,27,0,6,21,113},{5381,14,20,0,6,14,114},{5416,19,20,0,6,19,115},{5464,13,26,0,0,13,116},{5507,21,20,0,6,21,117},{5560,20,20,0,6,20,118},{5610,29,20,0,6,29,119},{5683,21,20,0,6,21,120},{5736,20,27,0,6,20,121},{5804,19,20,0,6,19,122},{5852,14,33,0,-1,14,123},{5910,10,34,0,-1,10,124},{5953,14,33,0,-1,14,125},{6011,23,15,0,11,23,126}}; +static const uint8_t f0_40_bits[]={31,129,248,31,129,248,31,129,248,31,129,240,31,1,240,31,1,240,15,0,240,15,0,240,15,0,240,15,0,240,0,0,0,31,129,248,31,129,248,31,129,248,63,63,15,207,195,243,240,252,252,63,63,15,207,195,243,240,248,248,62,62,7,135,129,225,224,120,120,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,120,0,240,120,0,240,120,0,240,240,1,240,240,1,224,240,1,224,240,31,255,254,31,255,254,31,255,254,31,255,254,3,195,224,3,193,224,3,195,192,7,131,192,7,131,192,7,135,192,127,255,248,127,255,248,127,255,248,127,255,248,15,7,128,15,7,128,15,15,0,30,15,0,30,15,0,30,15,0,30,31,0,0,24,0,0,96,0,1,128,0,6,0,0,24,0,7,254,0,127,252,3,255,248,31,255,240,126,111,227,241,159,143,198,124,63,152,0,127,224,1,255,240,3,255,240,3,255,240,1,255,192,1,255,128,6,126,63,25,248,252,103,227,249,191,135,255,252,15,255,240,31,255,128,63,248,0,6,0,0,24,0,0,96,0,1,128,0,6,0,1,240,0,60,0,31,240,0,224,0,255,224,7,128,3,199,128,60,0,30,15,0,224,0,120,60,7,128,1,224,240,60,0,7,131,192,224,0,30,15,7,128,0,120,60,60,0,0,241,224,224,0,3,255,135,128,0,7,252,60,0,0,7,225,224,248,0,0,7,143,248,0,0,60,127,240,0,1,225,227,192,0,7,15,7,128,0,60,60,30,0,1,224,240,120,0,7,3,193,224,0,60,15,7,128,1,224,60,30,0,7,0,240,120,0,60,1,227,192,1,224,7,255,0,7,0,15,248,0,60,0,15,128,0,63,192,0,7,255,224,0,127,255,224,3,255,255,192,15,248,255,0,127,0,254,1,248,1,248,7,224,7,224,31,128,0,0,126,0,0,0,252,0,0,1,255,199,254,1,255,31,248,15,252,127,224,255,241,255,135,248,7,224,31,128,31,128,252,0,126,3,240,1,248,15,192,7,224,63,0,31,128,252,0,126,3,248,3,240,7,252,127,192,15,255,254,0,31,255,240,0,63,255,128,0,31,240,0,63,15,195,240,252,63,15,195,240,248,62,7,129,224,120,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,240,3,192,30,0,248,3,192,31,0,120,3,224,15,128,124,1,240,7,192,31,0,248,3,224,15,128,62,0,248,3,224,15,128,62,0,248,3,224,15,128,31,0,124,1,240,7,192,15,128,62,0,120,1,240,3,192,15,128,30,0,60,0,240,60,0,240,1,224,7,192,15,0,62,0,120,1,240,7,192,15,0,62,0,248,3,224,7,192,31,0,124,1,240,7,192,31,0,124,1,240,7,192,31,0,124,3,224,15,128,62,0,248,7,192,31,0,120,3,224,15,0,124,1,224,15,0,60,0,1,192,1,192,1,192,49,140,61,190,63,252,15,240,7,224,15,240,63,252,61,158,49,140,1,192,1,192,1,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,30,0,0,15,0,0,7,128,0,3,192,0,1,224,0,0,240,0,0,120,0,0,60,0,0,30,0,15,255,254,7,255,255,3,255,255,129,255,255,192,0,240,0,0,120,0,0,60,0,0,30,0,0,15,0,0,7,128,0,3,192,0,1,224,0,0,240,0,31,129,248,31,129,248,31,129,248,3,128,48,7,0,112,30,1,192,16,0,0,0,255,195,255,15,252,63,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,129,248,31,129,248,31,129,248,0,240,15,0,224,30,1,224,30,1,192,60,3,192,60,3,128,120,7,128,120,15,0,240,15,0,240,30,1,224,30,1,192,60,3,192,60,3,128,120,7,128,120,7,0,240,15,0,0,255,0,3,255,192,7,255,224,15,255,240,15,231,240,31,129,248,31,129,248,31,0,248,63,0,252,63,0,252,63,0,252,63,0,252,63,0,252,63,0,252,63,0,252,63,0,252,63,0,252,63,0,252,63,0,252,63,0,252,31,0,248,31,129,248,31,129,248,15,231,240,15,255,240,7,255,224,3,255,192,0,255,0,0,7,0,0,31,0,0,255,0,15,255,0,15,255,0,15,255,0,15,255,0,0,63,0,0,63,0,0,63,0,0,63,0,0,63,0,0,63,0,0,63,0,0,63,0,0,63,0,0,63,0,0,63,0,0,63,0,0,63,0,0,63,0,0,63,0,0,63,0,0,63,0,15,255,252,15,255,252,15,255,252,15,255,252,0,127,0,3,255,192,7,255,224,15,255,240,31,231,248,63,129,248,63,0,252,63,0,252,63,0,252,63,0,252,0,1,248,0,1,248,0,3,240,0,7,240,0,31,224,0,63,192,0,127,128,0,255,0,3,252,0,7,248,0,15,240,0,15,192,0,31,128,0,31,255,252,63,255,252,63,255,252,63,255,252,63,255,252,0,255,0,3,255,192,15,255,240,31,255,248,31,231,248,31,129,248,63,0,252,63,0,252,0,0,248,0,1,248,0,3,240,0,63,224,0,63,192,0,63,192,0,63,240,0,7,248,0,1,248,0,0,252,0,0,252,63,0,252,63,0,252,63,0,252,63,129,252,31,231,248,15,255,248,15,255,240,3,255,192,0,255,0,0,252,0,1,248,0,3,224,0,15,199,0,31,142,0,62,28,0,252,120,1,240,240,3,227,224,15,199,192,31,31,128,126,63,0,248,126,3,224,252,7,193,248,31,3,240,60,7,224,248,15,193,255,255,243,255,255,231,255,255,207,255,255,128,1,248,0,3,240,0,7,224,0,15,192,0,31,128,0,63,0,15,255,240,15,255,240,15,255,240,15,255,240,15,255,240,15,128,0,15,128,0,15,128,0,15,128,0,31,159,128,31,255,224,31,255,240,31,255,248,31,231,248,31,129,248,1,0,252,0,0,252,0,0,252,0,0,252,63,0,252,63,0,252,63,0,252,31,129,248,31,231,248,15,255,240,7,255,224,3,255,192,0,255,0,0,127,0,1,255,192,7,255,240,7,255,240,15,231,248,31,129,248,31,128,248,31,0,252,31,0,0,63,0,0,63,63,192,63,255,224,63,255,240,63,255,248,63,195,248,63,129,252,63,0,252,63,0,252,63,0,252,63,0,252,31,0,252,31,128,252,31,129,248,15,231,248,15,255,240,7,255,224,3,255,192,0,127,0,63,255,252,63,255,252,63,255,252,63,255,252,63,255,248,0,1,240,0,3,240,0,7,224,0,7,192,0,15,192,0,15,128,0,31,0,0,63,0,0,62,0,0,62,0,0,124,0,0,124,0,0,252,0,0,248,0,0,248,0,1,248,0,1,240,0,1,240,0,1,240,0,1,240,0,3,240,0,3,240,0,3,240,0,0,255,0,3,255,192,15,255,240,31,255,240,31,231,248,31,129,248,63,0,252,63,0,252,63,0,252,31,129,248,31,195,248,15,255,240,7,255,224,3,255,224,15,255,240,31,227,248,31,128,252,63,0,124,63,0,126,63,0,126,63,0,126,63,0,126,63,128,252,31,227,252,31,255,248,15,255,240,3,255,224,0,255,128,0,254,0,3,255,192,7,255,224,15,255,240,31,231,240,31,129,248,63,0,248,63,0,248,63,0,252,63,0,252,63,0,252,63,129,252,31,195,252,31,255,252,15,255,252,7,255,252,3,252,252,0,0,252,0,0,252,31,128,248,31,128,248,31,129,248,15,193,248,15,231,240,7,255,240,7,255,224,3,255,192,0,254,0,15,192,63,0,252,3,240,15,192,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,192,63,0,252,3,240,15,192,63,0,15,192,126,3,240,31,128,252,7,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,192,126,3,240,31,128,252,7,224,7,0,48,3,128,28,3,192,28,0,128,0,0,0,0,0,0,1,128,0,3,224,0,3,248,0,7,254,0,7,255,0,7,255,0,15,254,0,15,254,0,7,252,0,1,252,0,0,124,0,0,31,192,0,7,254,0,0,255,224,0,7,255,0,0,127,248,0,3,255,0,0,63,224,0,3,248,0,0,30,0,0,1,0,31,255,252,15,255,254,7,255,255,3,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,254,7,255,255,3,255,255,129,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,240,0,0,63,128,0,15,248,0,3,255,192,0,31,252,0,1,255,192,0,15,254,0,0,255,192,0,7,240,0,0,252,0,0,255,0,0,255,128,1,255,192,3,255,192,3,255,128,3,255,128,0,255,0,0,63,0,0,15,0,0,2,0,0,0,0,255,0,3,255,192,15,255,224,31,255,240,31,231,240,63,129,248,63,1,248,63,1,248,63,1,248,0,1,248,0,3,240,0,7,240,0,15,224,0,31,192,0,63,128,0,126,0,0,126,0,0,124,0,0,124,0,0,0,0,0,0,0,0,0,0,0,126,0,0,126,0,0,126,0,0,126,0,0,126,0,0,126,0,0,0,255,192,0,0,7,255,254,0,0,15,255,255,192,0,31,224,7,248,0,31,128,0,126,0,31,0,0,15,128,31,0,254,3,224,15,1,255,192,240,15,3,255,240,124,15,129,255,248,30,7,129,248,126,15,3,192,248,31,3,195,224,124,15,129,225,224,0,7,192,240,240,0,3,224,120,120,0,127,240,60,60,1,255,248,30,30,1,255,252,15,15,1,248,62,7,135,129,248,31,7,131,192,248,15,131,193,224,124,15,195,224,120,63,15,227,224,60,31,255,255,240,30,7,255,127,240,15,129,255,63,224,3,192,126,7,192,0,240,0,0,0,0,124,0,0,0,0,31,128,0,0,0,7,248,3,128,0,0,255,255,192,0,0,31,255,224,0,0,1,255,192,0,0,0,31,192,0,1,255,0,0,15,248,0,0,255,192,0,7,255,0,0,63,248,0,3,247,224,0,31,191,0,0,249,248,0,15,199,224,0,126,63,0,7,225,248,0,63,7,224,1,248,63,0,31,129,252,0,252,7,224,7,224,63,0,126,1,252,3,255,255,224,63,255,255,129,255,255,252,15,255,255,224,254,0,63,135,224,0,252,63,0,7,227,240,0,63,159,128,0,253,252,0,7,240,31,255,248,1,255,255,224,31,255,255,1,255,255,248,31,128,63,193,248,0,252,31,128,15,193,248,0,252,31,128,15,193,248,0,252,31,128,31,129,248,3,248,31,255,255,1,255,255,192,31,255,255,1,255,255,248,31,128,31,193,248,0,252,31,128,7,193,248,0,126,31,128,7,225,248,0,126,31,128,15,193,248,3,252,31,255,255,193,255,255,248,31,255,255,1,255,255,192,0,31,240,0,7,255,224,0,127,255,192,7,255,255,0,127,199,252,7,240,15,240,127,0,31,131,240,0,252,31,128,3,241,248,0,31,143,192,0,248,126,0,0,3,240,0,0,31,128,0,0,252,0,0,7,224,0,0,63,0,0,1,248,0,0,15,192,0,252,63,0,7,225,248,0,126,15,224,3,240,63,128,127,128,255,143,248,7,255,255,128,15,255,248,0,63,255,128,0,63,224,0,31,255,224,0,255,255,192,7,255,255,128,63,255,254,1,255,255,248,15,192,63,224,126,0,127,3,240,1,252,31,128,7,224,252,0,63,7,224,0,248,63,0,7,225,248,0,63,15,192,1,248,126,0,15,195,240,0,126,31,128,3,240,252,0,31,7,224,1,248,63,0,15,193,248,0,254,15,192,15,224,126,1,255,3,255,255,240,31,255,255,0,255,255,240,7,255,254,0,63,255,192,0,31,255,255,131,255,255,240,127,255,254,15,255,255,193,255,255,248,63,0,0,7,224,0,0,252,0,0,31,128,0,3,240,0,0,126,0,0,15,192,0,1,255,255,224,63,255,252,7,255,255,128,255,255,240,31,128,0,3,240,0,0,126,0,0,15,192,0,1,248,0,0,63,0,0,7,224,0,0,255,255,252,31,255,255,131,255,255,240,127,255,254,15,255,255,192,31,255,254,15,255,255,7,255,255,131,255,255,193,255,255,224,252,0,0,126,0,0,63,0,0,31,128,0,15,192,0,7,224,0,3,240,0,1,255,255,192,255,255,224,127,255,240,63,255,248,31,128,0,15,192,0,7,224,0,3,240,0,1,248,0,0,252,0,0,126,0,0,63,0,0,31,128,0,15,192,0,7,224,0,3,240,0,0,0,15,248,0,0,127,255,0,1,255,255,128,3,255,255,224,7,254,63,224,15,224,7,240,31,192,3,248,31,128,1,248,31,128,1,248,63,0,0,0,63,0,0,0,63,0,0,0,63,0,0,0,63,1,255,248,63,1,255,248,63,1,255,248,63,1,255,248,63,0,1,248,63,0,1,248,31,128,1,248,31,128,3,248,31,192,3,248,15,224,15,248,7,252,127,248,7,255,255,248,1,255,255,120,0,255,254,120,0,31,240,120,31,128,7,224,126,0,31,129,248,0,126,7,224,1,248,31,128,7,224,126,0,31,129,248,0,126,7,224,1,248,31,128,7,224,126,0,31,129,248,0,126,7,255,255,248,31,255,255,224,127,255,255,129,255,255,254,7,255,255,248,31,128,7,224,126,0,31,129,248,0,126,7,224,1,248,31,128,7,224,126,0,31,129,248,0,126,7,224,1,248,31,128,7,224,126,0,31,129,248,0,126,7,224,1,248,31,129,248,31,129,248,31,129,248,31,129,248,31,129,248,31,129,248,31,129,248,31,129,248,31,129,248,31,129,248,31,129,248,31,129,248,31,129,248,31,129,248,0,1,248,0,1,248,0,1,248,0,1,248,0,1,248,0,1,248,0,1,248,0,1,248,0,1,248,0,1,248,0,1,248,0,1,248,0,1,248,0,1,248,0,1,248,0,1,248,0,1,248,0,1,248,0,1,248,126,1,248,126,1,240,126,3,240,127,3,240,63,207,240,63,255,224,31,255,192,15,255,128,1,254,0,31,128,15,224,252,0,254,7,224,15,224,63,0,255,1,248,15,240,15,192,255,0,126,15,240,3,240,255,0,31,135,240,0,252,127,0,7,231,240,0,63,127,0,1,255,252,0,15,255,240,0,127,255,128,3,255,254,0,31,243,248,0,255,31,192,7,240,127,0,63,1,248,1,248,15,224,15,192,63,128,126,1,252,3,240,7,240,31,128,31,192,252,0,254,7,224,3,248,63,0,15,224,31,128,0,31,128,0,31,128,0,31,128,0,31,128,0,31,128,0,31,128,0,31,128,0,31,128,0,31,128,0,31,128,0,31,128,0,31,128,0,31,128,0,31,128,0,31,128,0,31,128,0,31,128,0,31,128,0,31,128,0,31,128,0,31,128,0,31,128,0,31,255,254,31,255,254,31,255,254,31,255,254,31,255,254,31,240,1,255,3,255,0,127,224,127,224,15,252,15,252,1,255,129,255,128,63,240,63,248,15,254,7,255,1,255,192,255,224,61,248,31,254,7,191,3,247,193,247,224,126,248,62,252,15,223,7,159,129,251,240,243,240,63,62,62,126,7,231,199,207,192,252,248,241,248,31,143,190,63,3,241,247,199,224,126,62,240,252,15,199,222,31,129,248,127,195,240,63,15,248,126,7,225,254,15,192,252,63,193,248,31,131,248,63,3,240,127,7,224,126,15,192,252,15,192,248,31,128,31,128,7,224,127,0,31,129,252,0,126,7,248,1,248,31,240,7,224,127,192,31,129,255,128,126,7,255,1,248,31,254,7,224,127,248,31,129,255,240,126,7,239,225,248,31,191,199,224,126,127,31,129,248,254,126,7,227,253,248,31,135,247,224,126,15,255,129,248,31,254,7,224,127,248,31,128,255,224,126,1,255,129,248,3,254,7,224,15,248,31,128,31,224,126,0,63,129,248,0,254,7,224,1,248,0,31,248,0,0,127,254,0,1,255,255,128,3,255,255,192,7,252,63,224,15,224,7,240,15,192,3,240,31,128,1,248,31,128,1,248,63,0,0,252,63,0,0,252,63,0,0,252,63,0,0,252,63,0,0,252,63,0,0,252,63,0,0,252,63,0,0,252,63,0,0,252,63,0,0,252,31,128,1,248,31,128,1,248,15,192,3,240,15,224,7,240,7,252,63,224,3,255,255,192,1,255,255,128,0,127,254,0,0,31,248,0,31,255,248,3,255,255,192,127,255,252,15,255,255,129,255,255,248,63,0,127,7,224,3,240,252,0,126,31,128,15,195,240,1,248,126,0,63,15,192,7,225,248,1,248,63,0,255,7,255,255,192,255,255,248,31,255,252,3,255,254,0,126,0,0,15,192,0,1,248,0,0,63,0,0,7,224,0,0,252,0,0,31,128,0,3,240,0,0,126,0,0,15,192,0,0,0,31,248,0,0,127,254,0,1,255,255,128,3,255,255,192,7,252,63,224,15,224,7,240,15,192,3,240,31,128,1,248,31,128,1,248,63,0,0,252,63,0,0,252,63,0,0,252,63,0,0,252,63,0,0,252,63,0,0,252,63,0,0,252,63,0,0,252,63,0,0,252,63,0,0,252,31,128,1,248,31,128,1,248,15,192,3,248,15,224,7,240,7,252,63,224,3,255,255,224,1,255,255,128,0,127,255,0,0,31,255,0,0,0,63,128,0,0,31,192,0,0,31,224,0,0,15,240,0,0,7,248,31,255,252,0,255,255,248,7,255,255,224,63,255,255,129,248,3,252,15,192,7,224,126,0,31,131,240,0,252,31,128,7,224,252,0,63,7,224,1,248,63,0,31,193,248,1,252,15,255,255,224,127,255,254,3,255,255,224,31,255,254,0,252,3,240,7,224,31,192,63,0,126,1,248,3,240,15,192,15,192,126,0,126,3,240,1,248,31,128,15,192,252,0,63,7,224,1,248,63,0,15,224,0,63,192,0,63,255,0,31,255,248,7,255,255,128,255,31,240,63,128,127,7,224,7,224,252,0,252,31,128,0,3,252,0,0,127,240,0,7,255,224,0,127,255,128,7,255,248,0,63,255,192,1,255,252,0,3,255,128,0,15,240,0,0,127,31,128,7,227,240,0,252,126,0,31,7,224,7,224,255,135,252,15,255,255,0,255,255,192,15,255,224,0,63,224,0,127,255,255,159,255,255,231,255,255,249,255,255,254,127,255,255,128,15,192,0,3,240,0,0,252,0,0,63,0,0,15,192,0,3,240,0,0,252,0,0,63,0,0,15,192,0,3,240,0,0,252,0,0,63,0,0,15,192,0,3,240,0,0,252,0,0,63,0,0,15,192,0,3,240,0,0,252,0,0,63,0,0,15,192,0,3,240,0,0,252,0,31,128,7,224,126,0,31,129,248,0,126,7,224,1,248,31,128,7,224,126,0,31,129,248,0,126,7,224,1,248,31,128,7,224,126,0,31,129,248,0,126,7,224,1,248,31,128,7,224,126,0,31,129,248,0,126,7,224,1,248,31,128,7,224,126,0,31,129,248,0,126,7,224,1,248,31,128,7,224,63,0,63,0,254,1,252,1,255,63,224,7,255,255,128,15,255,252,0,15,255,192,0,7,248,0,126,0,7,231,224,0,126,127,0,15,195,240,0,252,63,0,15,195,248,1,248,31,128,31,129,248,1,248,31,192,63,0,252,3,240,15,192,63,0,126,7,224,7,224,126,0,126,7,224,3,240,252,0,63,15,192,3,240,248,0,31,159,128,1,249,248,0,31,159,0,0,255,240,0,15,255,0,0,255,224,0,7,254,0,0,127,224,0,3,252,0,0,63,192,0,3,252,0,254,0,254,0,252,252,1,252,1,249,248,7,252,3,243,248,15,248,7,195,240,31,240,31,135,224,63,224,63,15,192,255,224,126,31,129,255,192,248,31,131,239,131,240,63,7,223,7,224,126,15,190,15,128,252,62,126,31,0,252,124,124,62,1,248,248,248,252,3,241,241,241,240,7,231,195,243,224,7,239,135,231,192,15,223,7,223,128,31,190,15,190,0,63,124,31,124,0,63,240,63,248,0,127,224,63,240,0,255,192,127,192,1,255,128,255,128,1,254,1,255,0,3,252,1,254,0,7,248,3,248,0,15,240,7,240,0,63,128,15,227,252,1,252,31,192,31,128,254,3,248,15,224,127,0,127,7,224,3,248,254,0,63,159,192,1,253,248,0,15,255,128,0,255,240,0,7,254,0,0,63,224,0,3,252,0,0,127,224,0,7,254,0,0,255,240,0,31,255,128,1,249,252,0,63,159,192,7,240,254,0,126,15,240,15,224,127,1,252,3,248,31,128,63,195,248,1,252,127,0,15,231,240,0,255,127,0,15,231,240,0,252,63,128,31,193,248,1,248,31,192,63,0,254,3,240,15,224,126,0,127,15,224,3,240,252,0,63,159,128,1,249,248,0,15,255,0,0,255,224,0,7,254,0,0,63,192,0,3,248,0,0,31,128,0,1,248,0,0,31,128,0,1,248,0,0,31,128,0,1,248,0,0,31,128,0,1,248,0,0,31,128,0,1,248,0,0,31,128,0,1,248,0,63,255,255,15,255,255,195,255,255,240,255,255,248,63,255,254,0,0,127,0,0,63,128,0,31,224,0,7,240,0,3,248,0,1,254,0,0,127,0,0,63,128,0,31,192,0,7,240,0,3,248,0,1,252,0,0,255,0,0,63,128,0,31,192,0,15,240,0,3,248,0,1,252,0,0,255,255,254,63,255,255,159,255,255,231,255,255,249,255,255,254,15,248,63,224,255,131,254,15,128,62,0,248,3,224,15,128,62,0,248,3,224,15,128,62,0,248,3,224,15,128,62,0,248,3,224,15,128,62,0,248,3,224,15,128,62,0,248,3,224,15,128,62,0,248,3,224,15,128,63,224,255,131,254,15,248,240,15,0,112,7,128,120,7,128,56,3,192,60,3,192,28,1,224,30,1,224,15,0,240,15,0,240,7,128,120,7,128,56,3,192,60,3,192,28,1,224,30,1,224,14,0,240,15,127,193,255,7,252,31,240,7,192,31,0,124,1,240,7,192,31,0,124,1,240,7,192,31,0,124,1,240,7,192,31,0,124,1,240,7,192,31,0,124,1,240,7,192,31,0,124,1,240,7,192,31,0,124,1,240,7,193,255,7,252,31,240,127,192,0,30,0,0,15,192,0,3,240,0,1,252,0,0,127,128,0,63,224,0,15,124,0,3,207,0,1,243,192,0,120,120,0,62,30,0,15,7,192,3,192,240,1,240,60,0,120,15,128,62,1,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,252,252,31,131,224,124,15,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,254,0,15,255,0,127,255,0,255,255,3,252,255,7,224,126,7,128,252,0,1,248,0,255,240,15,255,224,127,255,193,255,255,131,252,63,15,224,126,31,128,252,63,1,248,126,7,240,255,63,241,255,255,249,255,239,241,255,143,224,252,15,128,31,128,0,15,192,0,7,224,0,3,240,0,1,248,0,0,252,0,0,126,0,0,63,0,0,31,143,192,15,223,248,7,255,254,3,255,255,1,255,63,192,254,7,224,127,1,248,63,0,252,31,128,126,15,192,63,7,224,31,131,240,15,193,248,7,224,252,3,240,126,1,248,63,0,252,31,192,252,15,249,254,7,255,254,3,239,255,1,243,255,0,248,126,0,0,127,0,3,255,192,7,255,240,15,255,248,31,231,248,31,129,252,31,128,252,63,0,252,63,0,124,63,0,0,63,0,0,63,0,0,63,0,0,63,0,124,63,0,252,31,0,252,31,129,252,31,231,248,15,255,248,7,255,240,3,255,192,0,255,0,0,0,252,0,0,126,0,0,63,0,0,31,128,0,15,192,0,7,224,0,3,240,0,1,248,1,248,252,1,255,126,3,255,255,1,255,255,129,254,127,192,252,15,224,252,3,240,126,1,248,63,0,252,31,128,126,15,192,63,7,224,31,131,240,15,193,248,7,224,252,3,240,126,1,248,31,129,252,15,243,254,3,255,255,1,255,239,128,127,231,192,15,195,224,0,255,0,3,255,192,7,255,224,15,255,240,31,231,248,31,129,248,31,0,252,63,0,252,63,255,252,63,255,252,63,255,252,63,255,252,63,0,0,63,0,0,63,0,252,31,0,252,31,129,252,31,231,248,15,255,248,7,255,240,3,255,192,0,255,0,1,248,31,224,127,131,254,15,192,63,0,252,3,240,127,249,255,231,255,159,254,15,192,63,0,252,3,240,15,192,63,0,252,3,240,15,192,63,0,252,3,240,15,192,63,0,252,3,240,15,192,63,0,0,0,63,0,0,31,128,0,15,192,0,15,192,7,255,224,31,255,192,31,255,192,31,255,224,31,193,248,15,192,126,7,224,63,3,240,31,129,248,15,192,126,15,224,63,255,224,15,255,224,7,255,224,7,255,192,7,224,0,7,224,0,3,240,0,1,252,0,0,127,255,240,63,255,252,15,255,254,15,255,255,143,128,15,207,192,7,231,240,7,243,255,255,240,255,255,248,127,255,240,15,255,224,0,31,128,0,31,128,0,31,128,0,31,128,0,31,128,0,31,128,0,31,128,0,31,128,0,31,143,192,31,191,240,31,255,248,31,255,248,31,255,252,31,225,252,31,192,252,31,128,252,31,128,252,31,128,252,31,128,252,31,128,252,31,128,252,31,128,252,31,128,252,31,128,252,31,128,252,31,128,252,31,128,252,31,128,252,31,128,252,31,128,252,31,131,240,126,15,192,0,0,0,0,0,31,131,240,126,15,193,248,63,7,224,252,31,131,240,126,15,193,248,63,7,224,252,31,131,240,126,15,193,248,63,0,15,192,252,15,192,252,0,0,0,0,0,0,15,192,252,15,192,252,15,192,252,15,192,252,15,192,252,15,192,252,15,192,252,15,192,252,15,192,252,15,192,252,15,192,252,15,192,252,31,207,248,255,143,240,254,0,31,128,0,63,0,0,126,0,0,252,0,1,248,0,3,240,0,7,224,0,15,192,0,31,128,252,63,3,240,126,15,192,252,63,129,248,254,3,241,248,7,231,224,15,223,128,31,255,0,63,254,0,127,252,0,255,252,1,255,252,3,253,248,7,241,248,15,195,240,31,131,240,63,7,224,126,7,224,252,15,225,248,15,195,240,31,192,31,131,240,126,15,193,248,63,7,224,252,31,131,240,126,15,193,248,63,7,224,252,31,131,240,126,15,193,248,63,7,224,252,31,131,240,126,15,193,248,63,0,31,15,128,252,1,243,254,63,224,31,127,247,255,1,255,255,255,248,31,231,255,63,129,252,31,193,248,31,129,252,31,129,248,31,128,248,31,129,248,15,129,248,31,128,248,31,129,248,15,129,248,31,128,248,31,129,248,15,129,248,31,128,248,31,129,248,15,129,248,31,128,248,31,129,248,15,129,248,31,128,248,31,129,248,15,129,248,31,128,248,31,129,248,15,129,248,31,128,248,31,15,192,31,63,240,31,127,248,31,255,248,31,243,252,31,192,252,31,128,252,31,128,252,31,128,252,31,128,252,31,128,252,31,128,252,31,128,252,31,128,252,31,128,252,31,128,252,31,128,252,31,128,252,31,128,252,31,128,252,31,128,252,31,128,252,0,127,128,1,255,240,1,255,254,1,255,255,129,255,63,192,252,7,240,126,1,248,126,0,124,63,0,63,31,128,31,143,192,15,199,224,7,227,240,3,241,248,1,248,252,0,248,63,0,252,31,128,254,15,249,254,3,255,255,0,255,255,0,63,254,0,3,252,0,31,15,192,15,159,248,7,223,254,3,255,255,1,255,63,192,254,7,224,127,1,248,63,0,252,31,128,126,15,192,63,7,224,31,131,240,15,193,248,7,224,252,3,240,126,1,248,63,128,252,31,192,252,15,249,254,7,255,254,3,255,255,1,251,255,0,252,126,0,126,0,0,63,0,0,31,128,0,15,192,0,7,224,0,3,240,0,1,248,0,0,1,248,124,3,255,62,3,255,223,1,255,255,129,254,127,192,252,15,224,252,3,240,126,1,248,63,0,252,31,128,126,15,192,63,7,224,31,131,240,15,193,248,7,224,252,3,240,126,1,248,31,129,252,15,243,254,3,255,255,1,255,255,128,127,239,192,15,199,224,0,3,240,0,1,248,0,0,252,0,0,126,0,0,63,0,0,31,128,0,15,192,31,31,31,63,31,127,31,127,31,255,31,255,31,224,31,192,31,128,31,128,31,128,31,128,31,128,31,128,31,128,31,128,31,128,31,128,31,128,31,128,31,128,31,128,0,255,0,31,255,0,255,254,7,255,252,31,199,240,252,7,227,240,31,143,224,0,31,240,0,127,252,0,255,252,0,255,252,0,127,240,0,31,224,0,31,143,192,126,63,1,248,255,31,225,255,255,3,255,248,7,255,192,3,252,0,15,128,62,0,248,3,224,15,128,62,7,255,159,254,127,249,255,225,248,7,224,31,128,126,1,248,7,224,31,128,126,1,248,7,224,31,128,126,1,248,7,240,15,248,63,224,127,128,252,63,1,248,63,1,248,63,1,248,63,1,248,63,1,248,63,1,248,63,1,248,63,1,248,63,1,248,63,1,248,63,1,248,63,1,248,63,1,248,63,1,248,63,1,248,63,1,248,63,3,248,63,207,248,31,255,248,31,254,248,15,252,248,3,240,248,252,0,253,248,3,231,224,31,159,128,124,63,1,240,252,15,195,240,62,7,224,248,31,135,224,62,31,0,252,124,3,243,240,7,207,128,31,62,0,126,248,0,255,192,3,255,0,15,248,0,31,224,0,127,128,1,252,0,3,240,0,252,7,224,63,124,7,224,62,126,7,240,62,126,15,240,62,126,15,240,126,62,15,240,124,63,15,248,124,63,15,248,124,31,31,248,248,31,30,120,248,31,30,124,248,15,158,124,240,15,190,124,240,15,188,61,240,15,188,61,240,7,252,63,224,7,252,63,224,7,252,63,224,3,248,31,192,3,248,31,192,3,248,31,192,1,248,31,128,127,0,252,126,3,240,126,15,192,254,31,128,252,126,0,253,248,1,255,240,1,255,192,1,255,0,3,254,0,3,248,0,15,248,0,31,240,0,127,240,1,255,240,3,247,224,15,199,224,63,15,224,126,15,193,248,31,199,240,31,207,192,31,128,252,0,253,248,3,231,224,31,143,128,126,63,1,240,252,7,193,248,63,7,224,248,15,131,224,63,31,128,252,124,1,241,240,7,239,128,31,190,0,62,248,0,255,192,1,255,0,7,252,0,31,224,0,63,128,0,254,0,3,240,0,15,192,0,126,0,7,248,0,255,192,3,254,0,15,240,0,63,128,0,127,255,227,255,255,31,255,248,255,255,128,3,252,0,31,192,1,252,0,15,192,0,254,0,7,224,0,126,0,7,240,0,63,0,3,240,0,63,128,1,248,0,31,128,1,252,0,15,255,252,255,255,231,255,255,63,255,248,0,120,1,252,3,252,7,224,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,15,192,127,128,126,0,126,0,127,128,15,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,224,3,252,1,252,0,120,30,7,129,224,120,30,7,129,224,120,30,7,129,224,120,30,7,129,224,120,30,7,129,224,120,30,7,129,224,120,30,7,129,224,120,30,7,129,224,120,30,7,129,224,120,30,7,128,62,0,63,128,63,192,7,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,240,1,252,0,124,0,124,1,252,3,240,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,7,224,63,192,63,128,62,0,0,0,0,3,240,1,3,254,1,135,255,231,195,255,255,225,243,255,240,224,63,224,64,7,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +static const Glyph f0_40_glyphs[]={{0,8,0,0,28,8,32},{0,12,28,0,0,12,33},{42,18,28,0,0,18,34},{105,24,28,0,0,24,35},{189,22,32,0,-2,22,36},{277,38,28,0,0,38,37},{410,30,28,0,0,30,38},{515,10,28,0,0,10,39},{550,14,37,0,-3,14,40},{615,14,37,0,-3,14,41},{680,16,28,0,0,16,42},{736,25,22,0,6,25,43},{805,12,13,0,22,12,44},{825,14,14,0,14,14,45},{850,12,6,0,22,12,46},{859,12,32,0,-2,12,47},{907,24,28,0,0,24,48},{991,24,28,0,0,24,49},{1075,24,28,0,0,24,50},{1159,24,28,0,0,24,51},{1243,23,28,0,0,23,52},{1324,24,28,0,0,24,53},{1408,24,28,0,0,24,54},{1492,24,28,0,0,24,55},{1576,24,28,0,0,24,56},{1660,24,28,0,0,24,57},{1744,14,22,0,6,14,58},{1783,13,29,0,6,13,59},{1831,26,22,0,6,26,60},{1903,25,17,0,11,25,61},{1957,26,22,0,6,26,62},{2029,24,28,0,0,24,63},{2113,41,34,0,0,41,64},{2288,29,28,0,0,29,65},{2390,28,28,0,0,28,66},{2488,29,28,0,0,29,67},{2590,29,28,0,0,29,68},{2692,27,28,0,0,27,69},{2787,25,28,0,0,25,70},{2875,32,28,0,0,32,71},{2987,30,28,0,0,30,72},{3092,12,28,0,0,12,73},{3134,24,28,0,0,24,74},{3218,29,28,0,0,29,75},{3320,24,28,0,0,24,76},{3404,35,28,0,0,35,77},{3527,30,28,0,0,30,78},{3632,32,28,0,0,32,79},{3744,27,28,0,0,27,80},{3839,32,33,0,0,32,81},{3971,29,28,0,0,29,82},{4073,27,28,0,0,27,83},{4168,26,28,0,0,26,84},{4259,30,28,0,0,30,85},{4364,28,28,0,0,28,86},{4462,39,28,0,0,39,87},{4599,28,28,0,0,28,88},{4697,28,28,0,0,28,89},{4795,26,28,0,0,26,90},{4886,14,37,0,-2,14,91},{4951,12,32,0,-2,12,92},{4999,14,37,0,-2,14,93},{5064,26,28,0,0,26,94},{5155,21,6,0,28,21,95},{5171,10,29,0,-1,9,96},{5208,23,22,0,6,23,97},{5272,25,30,0,-2,25,98},{5366,24,22,0,6,24,99},{5432,25,30,0,-2,25,100},{5526,24,22,0,6,24,101},{5592,14,30,0,-2,14,102},{5645,25,33,0,2,25,103},{5749,24,30,0,-2,24,104},{5839,11,30,0,-2,11,105},{5881,12,37,-1,-2,11,106},{5937,23,30,0,-2,23,107},{6024,11,30,0,-2,11,108},{6066,36,22,0,6,36,109},{6165,24,22,0,6,24,110},{6231,25,22,0,6,25,111},{6300,25,29,0,6,25,112},{6391,25,29,0,6,25,113},{6482,16,22,0,6,16,114},{6526,22,22,0,6,22,115},{6587,14,28,0,0,14,116},{6636,24,22,0,6,24,117},{6702,22,22,0,6,22,118},{6763,32,22,0,6,32,119},{6851,23,22,0,6,23,120},{6915,22,29,0,6,22,121},{6995,21,22,0,6,21,122},{7053,16,36,0,-2,16,123},{7125,10,38,0,-2,10,124},{7173,16,36,0,-2,16,125},{7245,25,17,0,11,25,126}}; +static const uint8_t f0_46_bits[]={15,224,63,128,254,3,248,15,224,63,0,252,3,240,15,192,63,0,252,3,240,7,192,31,0,124,1,240,7,192,31,0,124,1,224,7,128,30,0,120,0,0,0,0,63,128,254,3,248,15,224,63,128,254,3,248,31,207,224,254,127,7,243,248,63,159,193,252,254,15,231,240,127,63,131,241,248,31,143,192,252,126,3,225,240,30,15,128,240,120,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,60,15,0,15,131,224,1,240,124,0,62,15,128,7,129,224,1,240,124,0,62,15,128,7,193,240,0,240,60,3,255,255,248,127,255,255,15,255,255,225,255,255,252,63,255,255,128,248,30,0,31,7,192,3,224,248,0,124,31,0,15,3,224,3,224,120,7,255,255,240,255,255,254,31,255,255,195,255,255,248,127,255,255,1,240,124,0,62,15,128,7,193,240,0,240,62,0,62,7,128,7,193,240,0,248,62,0,31,7,192,3,192,240,0,0,14,0,0,3,128,0,0,224,0,0,56,0,0,14,0,0,31,224,0,63,255,0,31,255,224,15,255,252,7,255,255,129,252,239,224,126,57,248,63,142,126,7,243,128,1,255,224,0,127,252,0,15,255,224,1,255,254,0,63,255,192,1,255,248,0,15,255,0,3,191,195,240,231,240,254,57,252,63,142,127,15,243,191,193,255,255,224,63,255,248,7,255,252,0,255,252,0,7,248,0,0,56,0,0,14,0,0,3,128,0,0,224,0,0,56,0,0,12,0,0,0,0,0,0,0,0,7,254,0,3,192,0,63,252,0,30,0,1,255,248,0,248,0,7,195,224,7,192,0,62,7,192,30,0,0,248,31,0,248,0,3,224,124,7,192,0,15,129,240,30,0,0,62,7,192,248,0,0,248,31,7,192,0,3,224,124,30,0,0,7,195,224,240,0,0,31,15,135,192,0,0,63,252,62,0,0,0,127,224,240,0,0,0,126,7,192,0,0,0,0,62,7,224,0,0,0,240,127,224,0,0,7,195,255,192,0,0,62,31,15,128,0,0,240,124,62,0,0,7,131,224,124,0,0,62,15,129,240,0,0,240,62,7,192,0,7,128,248,31,0,0,62,3,224,124,0,1,240,15,129,240,0,7,128,62,7,192,0,62,0,124,62,0,1,240,1,255,248,0,7,128,3,255,192,0,60,0,7,254,0,0,0,0,0,0,0,0,0,0,0,0,3,255,224,0,0,255,255,128,0,31,255,254,0,3,255,255,240,0,127,247,255,128,15,240,7,248,0,254,0,63,128,15,224,1,248,0,254,0,31,192,15,224,0,0,0,254,0,0,0,7,224,0,0,0,127,128,0,0,3,255,225,255,192,15,254,31,252,0,255,225,255,192,63,254,31,252,7,255,225,255,192,255,0,31,192,15,224,1,252,1,252,0,31,192,31,192,1,252,1,252,0,31,192,31,192,1,248,1,252,0,31,128,31,224,3,248,0,255,0,127,128,15,255,127,240,0,127,255,254,0,3,255,255,192,0,31,255,248,0,0,63,254,0,0,0,0,0,0,31,193,252,31,193,252,31,193,252,31,193,248,31,129,248,15,128,240,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,30,0,30,0,31,0,31,0,15,128,15,128,7,192,7,224,3,224,3,240,1,248,1,248,0,252,0,126,0,63,0,63,0,31,128,15,192,7,224,3,240,1,248,0,252,0,126,0,63,0,31,128,15,192,3,224,1,248,0,252,0,126,0,31,0,15,192,7,224,1,240,0,252,0,62,0,31,0,7,192,3,224,0,248,0,60,0,15,0,60,0,15,0,7,192,1,240,0,248,0,62,0,31,0,15,192,3,224,1,248,0,252,0,62,0,31,128,15,192,7,224,1,240,0,252,0,126,0,63,0,31,128,15,192,7,224,3,240,1,248,0,252,0,126,0,63,0,63,0,31,128,15,192,7,192,7,224,3,240,1,240,1,248,0,248,0,124,0,124,0,62,0,62,0,30,0,30,0,0,0,224,0,28,0,3,128,12,113,131,206,112,125,223,7,255,192,63,224,3,240,0,255,128,127,252,31,119,195,206,112,33,198,0,56,0,7,0,0,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,192,0,0,31,0,0,0,124,0,0,1,240,0,0,7,192,0,0,31,0,0,0,124,0,0,1,240,0,0,7,192,0,0,31,0,0,255,255,252,3,255,255,240,15,255,255,192,63,255,255,0,255,255,252,0,1,240,0,0,7,192,0,0,31,0,0,0,124,0,0,1,240,0,0,7,192,0,0,31,0,0,0,124,0,0,1,240,0,15,224,63,128,254,3,248,15,224,63,128,254,0,112,1,192,7,0,60,1,224,15,0,120,0,128,0,63,252,63,252,63,252,63,252,63,252,63,252,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,224,63,128,254,3,248,15,224,63,128,254,0,0,56,1,240,7,128,30,0,248,3,224,15,0,60,1,240,7,128,30,0,120,3,224,15,0,60,0,240,7,192,30,0,120,1,224,15,128,60,0,240,7,192,31,0,120,1,224,15,128,62,0,240,3,192,31,0,124,1,224,7,128,62,0,248,0,0,0,0,0,31,252,0,15,255,224,3,255,254,0,127,255,192,31,247,252,3,248,63,128,254,3,248,31,128,63,3,240,7,224,254,0,254,31,192,31,195,248,3,248,127,0,127,15,224,15,225,252,1,252,63,128,63,135,240,7,240,254,0,254,31,192,31,195,248,3,248,127,0,127,15,224,15,225,252,1,252,31,128,63,3,240,7,224,127,1,252,7,240,127,0,255,191,224,15,255,248,1,255,255,0,15,255,128,0,255,224,0,0,0,0,0,1,224,0,0,126,0,0,31,224,0,15,254,0,7,255,224,0,127,254,0,7,255,224,0,127,254,0,0,15,224,0,0,254,0,0,15,224,0,0,254,0,0,15,224,0,0,254,0,0,15,224,0,0,254,0,0,15,224,0,0,254,0,0,15,224,0,0,254,0,0,15,224,0,0,254,0,0,15,224,0,0,254,0,0,15,224,0,0,254,0,0,15,224,0,127,255,252,7,255,255,192,127,255,252,7,255,255,192,127,255,252,0,0,0,0,31,252,0,15,255,224,3,255,254,0,255,255,224,63,247,252,7,240,31,193,252,3,248,63,128,63,7,240,7,240,254,0,252,31,128,31,128,0,7,240,0,0,254,0,0,63,128,0,15,240,0,3,252,0,0,255,0,0,127,192,0,31,240,0,7,252,0,1,255,0,0,255,128,0,63,224,0,7,248,0,1,252,0,0,127,0,0,15,255,255,195,255,255,248,127,255,255,15,255,255,225,255,255,252,63,255,255,128,0,0,0,0,31,252,0,15,255,224,3,255,254,0,255,255,224,63,247,254,7,240,31,192,254,3,248,31,128,63,7,240,7,224,0,0,252,0,0,31,128,0,7,240,0,1,252,0,7,255,0,0,255,192,0,31,240,0,3,255,128,0,127,248,0,0,127,128,0,7,240,0,0,127,0,0,15,225,248,1,252,63,128,63,135,240,7,240,254,0,254,15,224,63,129,255,191,240,31,255,252,1,255,255,0,31,255,192,0,255,224,0,0,0,0,0,62,0,0,7,240,0,0,126,0,0,7,224,0,0,254,0,0,15,195,192,0,252,60,0,31,195,192,1,248,124,0,31,135,192,3,240,252,0,63,15,192,7,225,252,0,126,31,192,15,193,252,0,252,31,192,31,129,252,1,248,31,192,63,1,252,3,224,31,192,126,1,252,7,255,255,252,127,255,255,199,255,255,252,127,255,255,199,255,255,252,0,1,252,0,0,31,192,0,1,252,0,0,31,192,0,1,252,0,0,31,192,0,1,252,0,15,255,254,1,255,255,192,63,255,248,7,255,255,0,255,255,224,31,255,252,3,240,0,0,126,0,0,15,192,0,1,248,0,0,63,0,0,7,227,240,0,255,255,128,31,255,248,7,255,255,128,255,255,248,31,224,255,3,248,15,224,2,0,254,0,0,31,192,0,3,248,0,0,127,15,224,15,225,252,1,252,63,128,63,131,248,15,224,127,1,252,15,253,255,128,255,255,224,15,255,248,0,255,254,0,7,255,0,0,0,0,0,0,0,0,0,15,252,0,7,255,224,1,255,254,0,127,255,224,31,247,254,3,248,31,192,254,3,248,31,128,63,3,240,7,224,126,0,0,31,192,0,3,248,126,0,127,127,240,15,255,255,1,255,255,240,63,255,255,7,254,31,240,255,0,254,31,224,31,195,248,1,248,127,0,63,15,224,7,241,252,0,252,31,128,31,131,240,7,240,127,0,254,7,240,63,192,255,255,240,15,255,252,0,255,255,0,15,255,192,0,127,224,0,0,0,0,63,255,255,135,255,255,240,255,255,254,31,255,255,195,255,255,248,127,255,254,0,0,31,128,0,7,224,0,1,252,0,0,63,0,0,15,192,0,3,248,0,0,126,0,0,31,128,0,3,240,0,0,252,0,0,31,128,0,7,224,0,0,252,0,0,63,128,0,7,224,0,0,252,0,0,63,128,0,7,224,0,0,252,0,0,31,128,0,3,240,0,0,254,0,0,31,192,0,3,248,0,0,127,0,0,15,224,0,0,0,0,0,15,254,0,3,255,248,0,127,255,192,15,255,254,1,255,191,240,31,192,127,1,252,7,240,31,128,63,3,248,3,248,31,128,63,1,248,3,240,31,192,127,0,254,15,224,15,255,254,0,63,255,192,1,255,248,0,127,255,192,15,255,254,1,254,15,240,31,192,63,131,248,1,248,63,128,31,131,248,1,248,63,128,31,195,248,1,248,63,128,63,129,252,7,248,31,251,255,0,255,255,240,7,255,254,0,63,255,192,0,255,240,0,0,0,0,0,0,0,0,31,248,0,15,255,192,3,255,252,0,255,255,192,63,255,252,7,240,63,128,254,3,248,63,128,63,7,240,7,224,254,0,254,31,192,31,195,248,3,248,127,0,127,15,224,15,224,254,3,252,31,224,255,131,255,255,240,63,255,254,3,255,255,192,63,251,248,1,252,127,0,0,15,224,0,1,248,31,192,63,3,248,7,224,127,1,252,7,240,63,0,255,191,224,15,255,248,1,255,254,0,31,255,128,0,255,192,0,0,0,0,15,224,31,192,63,128,127,0,254,1,252,3,248,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,192,63,128,127,0,254,1,252,3,248,7,240,7,240,7,240,7,240,7,240,7,240,7,240,7,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,240,7,240,7,240,7,240,7,240,7,240,7,240,0,224,0,224,0,224,1,224,3,192,7,128,15,0,4,0,0,0,0,0,0,0,3,0,0,0,124,0,0,7,240,0,0,255,192,0,15,255,0,0,255,248,0,31,255,0,1,255,240,0,63,254,0,1,255,192,0,7,252,0,0,31,128,0,0,127,0,0,1,255,0,0,7,255,128,0,7,255,128,0,7,255,192,0,3,255,224,0,3,255,224,0,3,255,192,0,1,255,0,0,1,252,0,0,0,240,0,0,0,128,15,255,255,192,63,255,255,0,255,255,252,3,255,255,240,15,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,252,3,255,255,240,15,255,255,192,63,255,255,0,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,48,0,0,0,240,0,0,3,248,0,0,15,248,0,0,63,252,0,0,127,252,0,0,127,252,0,0,63,254,0,0,63,254,0,0,31,254,0,0,15,248,0,0,15,224,0,0,63,128,0,7,254,0,0,127,240,0,15,255,128,1,255,240,0,31,255,0,3,255,240,0,15,254,0,0,63,224,0,0,252,0,0,3,192,0,0,8,0,0,0,0,0,0,0,7,255,0,1,255,252,0,63,255,224,7,255,255,0,255,223,240,15,224,127,129,252,3,248,31,192,63,129,252,3,248,31,192,63,128,0,3,248,0,0,127,0,0,15,240,0,1,254,0,0,127,192,0,15,248,0,0,255,0,0,31,224,0,1,252,0,0,31,128,0,1,248,0,0,31,128,0,0,0,0,0,0,0,0,0,0,0,0,31,192,0,1,252,0,0,31,192,0,1,252,0,0,31,192,0,1,252,0,0,31,192,0,0,0,0,0,0,0,0,3,255,252,0,0,0,127,255,254,0,0,7,255,255,252,0,0,127,255,255,252,0,3,252,0,7,248,0,31,192,0,7,240,0,252,0,0,7,224,7,224,0,0,15,192,31,0,63,128,31,0,248,3,255,128,62,3,192,63,255,0,120,31,0,255,254,1,224,120,7,224,252,7,193,224,31,1,240,15,15,128,124,7,192,60,60,0,0,31,0,240,240,0,0,124,3,195,192,0,127,240,15,15,0,15,255,192,60,60,0,255,255,0,240,240,7,255,252,3,195,192,31,193,240,15,15,0,252,7,192,120,60,3,224,31,1,224,248,15,128,252,15,129,224,62,3,240,60,7,128,252,63,227,240,30,1,255,255,255,128,124,7,255,191,252,0,248,15,252,127,192,3,224,15,192,252,0,7,192,0,0,0,0,15,128,0,0,0,0,63,128,0,0,0,0,127,192,7,0,0,0,127,255,252,0,0,0,255,255,240,0,0,0,255,255,192,0,0,0,63,240,0,0,0,15,248,0,0,3,254,0,0,1,255,192,0,0,127,240,0,0,31,254,0,0,15,255,128,0,3,247,224,0,0,253,252,0,0,127,127,0,0,31,143,192,0,15,227,248,0,3,248,254,0,0,252,31,192,0,127,7,240,0,31,193,252,0,7,224,63,128,3,248,15,224,0,252,3,252,0,127,0,127,0,31,192,31,192,7,255,255,248,3,255,255,254,0,255,255,255,128,63,255,255,240,31,255,255,252,7,224,0,63,131,248,0,15,224,254,0,3,248,63,0,0,127,31,192,0,31,199,240,0,7,243,252,0,1,254,31,255,255,0,15,255,255,224,7,255,255,248,3,255,255,254,1,255,255,255,128,254,0,63,192,127,0,15,224,63,128,3,248,31,192,1,248,15,224,0,252,7,240,0,126,3,248,0,127,1,252,0,127,0,255,255,255,0,127,255,255,0,63,255,255,0,31,255,255,224,15,255,255,252,7,240,0,254,3,248,0,63,129,252,0,15,192,254,0,7,224,127,0,3,240,63,128,1,252,31,192,1,252,15,224,1,254,7,255,255,255,3,255,255,255,129,255,255,255,128,255,255,255,128,127,255,255,128,63,255,254,0,0,0,0,0,0,15,255,128,0,31,255,240,0,63,255,252,0,63,255,255,0,63,255,255,192,31,255,255,240,31,240,7,248,31,224,1,254,15,224,0,127,7,240,0,63,131,240,0,15,195,248,0,7,225,252,0,0,0,254,0,0,0,127,0,0,0,63,128,0,0,31,192,0,0,15,224,0,0,7,240,0,0,3,248,0,0,1,252,0,3,240,126,0,1,248,63,128,1,252,31,192,0,254,15,240,0,255,3,254,0,255,1,255,255,255,128,127,255,255,128,31,255,255,128,7,255,255,128,0,255,255,128,0,31,255,0,0,0,0,0,0,31,255,240,0,15,255,255,0,7,255,255,224,3,255,255,248,1,255,255,254,0,255,255,255,128,127,0,127,224,63,128,15,240,31,192,3,252,15,224,0,254,7,240,0,63,3,248,0,31,193,252,0,15,224,254,0,7,240,127,0,3,248,63,128,1,252,31,192,0,254,15,224,0,127,7,240,0,63,131,248,0,31,193,252,0,15,224,254,0,7,224,127,0,7,240,63,128,7,248,31,192,7,248,15,224,15,252,7,255,255,252,3,255,255,252,1,255,255,252,0,255,255,252,0,127,255,248,0,63,255,224,0,31,255,255,240,63,255,255,224,127,255,255,192,255,255,255,129,255,255,255,3,255,255,254,7,240,0,0,15,224,0,0,31,192,0,0,63,128,0,0,127,0,0,0,254,0,0,1,252,0,0,3,255,255,248,7,255,255,240,15,255,255,224,31,255,255,192,63,255,255,128,127,255,255,0,254,0,0,1,252,0,0,3,248,0,0,7,240,0,0,15,224,0,0,31,192,0,0,63,128,0,0,127,255,255,224,255,255,255,193,255,255,255,131,255,255,255,7,255,255,254,15,255,255,252,31,255,255,225,255,255,254,31,255,255,225,255,255,254,31,255,255,225,255,255,254,31,192,0,1,252,0,0,31,192,0,1,252,0,0,31,192,0,1,252,0,0,31,192,0,1,252,0,0,31,255,255,129,255,255,248,31,255,255,129,255,255,248,31,255,255,129,255,255,248,31,192,0,1,252,0,0,31,192,0,1,252,0,0,31,192,0,1,252,0,0,31,192,0,1,252,0,0,31,192,0,1,252,0,0,31,192,0,1,252,0,0,0,0,0,0,0,1,255,248,0,0,127,255,224,0,15,255,255,128,3,255,255,252,0,127,255,255,224,7,255,255,254,0,255,128,31,240,15,240,0,127,1,254,0,3,248,31,192,0,63,129,248,0,3,248,63,128,0,0,3,248,0,0,0,63,128,0,0,3,248,0,0,0,63,128,127,255,131,248,7,255,248,63,128,127,255,131,248,7,255,248,63,128,127,255,131,248,0,3,248,31,128,0,63,129,252,0,3,248,31,192,0,127,129,254,0,15,248,15,248,1,255,128,255,255,255,248,7,255,255,255,128,63,255,255,248,1,255,255,239,128,7,255,248,120,0,31,254,7,128,0,0,0,0,31,192,0,254,7,240,0,63,129,252,0,15,224,127,0,3,248,31,192,0,254,7,240,0,63,129,252,0,15,224,127,0,3,248,31,192,0,254,7,240,0,63,129,252,0,15,224,127,0,3,248,31,192,0,254,7,255,255,255,129,255,255,255,224,127,255,255,248,31,255,255,254,7,255,255,255,129,255,255,255,224,127,0,3,248,31,192,0,254,7,240,0,63,129,252,0,15,224,127,0,3,248,31,192,0,254,7,240,0,63,129,252,0,15,224,127,0,3,248,31,192,0,254,7,240,0,63,129,252,0,15,224,127,0,3,248,31,192,254,7,240,63,129,252,15,224,127,3,248,31,192,254,7,240,63,129,252,15,224,127,3,248,31,192,254,7,240,63,129,252,15,224,127,3,248,31,192,254,7,240,63,129,252,15,224,127,3,248,0,0,63,128,0,3,248,0,0,63,128,0,3,248,0,0,63,128,0,3,248,0,0,63,128,0,3,248,0,0,63,128,0,3,248,0,0,63,128,0,3,248,0,0,63,128,0,3,248,0,0,63,128,0,3,248,0,0,63,128,0,3,248,0,0,63,128,0,3,248,0,0,63,131,248,3,248,63,128,63,3,248,3,240,63,128,127,3,252,15,240,31,255,255,1,255,255,224,15,255,254,0,255,255,192,3,255,248,0,15,254,0,0,0,0,0,31,192,1,254,15,224,1,254,7,240,1,254,3,248,1,254,1,252,1,254,0,254,1,254,0,127,1,254,0,63,129,254,0,31,193,255,0,15,225,255,0,7,241,255,0,3,249,255,0,1,253,255,0,0,255,255,128,0,127,255,192,0,63,255,240,0,31,255,252,0,15,255,254,0,7,255,127,128,3,255,31,224,1,255,15,240,0,255,3,252,0,127,0,255,0,63,128,127,128,31,192,31,224,15,224,15,240,7,240,3,252,3,248,0,255,1,252,0,127,128,254,0,31,224,127,0,7,248,63,128,3,252,31,192,0,3,248,0,0,127,0,0,15,224,0,1,252,0,0,63,128,0,7,240,0,0,254,0,0,31,192,0,3,248,0,0,127,0,0,15,224,0,1,252,0,0,63,128,0,7,240,0,0,254,0,0,31,192,0,3,248,0,0,127,0,0,15,224,0,1,252,0,0,63,128,0,7,240,0,0,254,0,0,31,192,0,3,248,0,0,127,255,255,15,255,255,225,255,255,252,63,255,255,135,255,255,240,255,255,254,31,252,0,63,240,31,252,0,127,240,31,252,0,127,240,31,254,0,127,240,31,254,0,127,240,31,254,0,255,240,31,254,0,255,240,31,255,0,255,240,31,255,0,255,240,31,255,1,247,240,31,223,1,247,240,31,223,129,247,240,31,223,131,247,240,31,223,131,231,240,31,207,195,231,240,31,207,195,231,240,31,207,199,231,240,31,207,199,199,240,31,199,231,199,240,31,199,231,199,240,31,199,239,199,240,31,195,239,135,240,31,195,255,135,240,31,195,255,135,240,31,195,255,7,240,31,193,255,7,240,31,193,255,7,240,31,193,255,7,240,31,192,254,7,240,31,192,254,7,240,31,192,254,7,240,31,192,254,7,240,31,192,0,254,7,240,0,63,129,254,0,15,224,127,192,3,248,31,248,0,254,7,254,0,63,129,255,192,15,224,127,248,3,248,31,255,0,254,7,255,192,63,129,255,248,15,224,127,255,3,248,31,255,192,254,7,247,248,63,129,252,255,15,224,127,63,227,248,31,199,248,254,7,240,255,63,129,252,63,239,224,127,7,251,248,31,192,255,254,7,240,31,255,129,252,7,255,224,127,0,255,248,31,192,31,254,7,240,7,255,129,252,0,255,224,127,0,31,248,31,192,3,254,7,240,0,255,129,252,0,31,224,127,0,3,248,0,0,0,0,0,1,255,248,0,0,127,255,224,0,15,255,255,128,3,255,255,252,0,127,255,255,224,7,255,255,254,0,255,128,31,240,15,240,0,255,1,254,0,7,248,31,192,0,63,129,248,0,1,248,63,128,0,31,195,248,0,1,252,63,128,0,31,195,248,0,1,252,63,128,0,31,195,248,0,1,252,63,128,0,31,195,248,0,1,252,63,128,0,31,195,248,0,1,252,31,128,0,31,129,252,0,3,248,31,224,0,127,128,255,0,15,240,15,248,1,255,0,127,255,255,224,7,255,255,254,0,63,255,255,192,0,255,255,240,0,7,255,254,0,0,31,255,128,0,0,0,0,0,31,255,254,0,63,255,255,0,127,255,255,128,255,255,255,1,255,255,255,3,255,255,255,7,240,1,254,15,224,1,252,31,192,1,248,63,128,3,240,127,0,7,240,254,0,15,193,252,0,31,131,248,0,127,7,240,1,254,15,255,255,252,31,255,255,240,63,255,255,192,127,255,255,0,255,255,252,1,255,255,224,3,248,0,0,7,240,0,0,15,224,0,0,31,192,0,0,63,128,0,0,127,0,0,0,254,0,0,1,252,0,0,3,248,0,0,7,240,0,0,15,224,0,0,0,0,0,0,0,1,255,248,0,0,127,255,224,0,15,255,255,128,3,255,255,252,0,127,255,255,224,7,255,255,254,0,255,128,31,240,15,240,0,255,1,254,0,7,248,31,192,0,63,129,248,0,1,248,63,128,0,31,195,248,0,1,252,63,128,0,31,195,248,0,1,252,63,128,0,31,195,248,0,1,252,63,128,0,31,195,248,0,1,252,63,128,0,31,195,248,0,1,252,31,128,0,31,129,252,0,3,248,31,224,0,127,128,255,0,15,240,15,248,1,255,0,127,255,255,224,3,255,255,254,0,63,255,255,192,0,255,255,248,0,7,255,254,0,0,31,255,224,0,0,1,255,0,0,0,15,248,0,0,0,127,192,0,0,3,254,0,0,0,31,240,0,0,0,255,128,31,255,255,0,15,255,255,224,7,255,255,252,3,255,255,255,1,255,255,255,128,255,255,255,224,127,0,15,240,63,128,3,248,31,192,0,252,15,224,0,127,7,240,0,63,3,248,0,31,129,252,0,15,192,254,0,15,224,127,0,15,240,63,255,255,240,31,255,255,240,15,255,255,248,7,255,255,240,3,255,255,248,1,252,1,252,0,254,0,255,0,127,0,63,128,63,128,31,224,31,192,7,240,15,224,3,252,7,240,0,254,3,248,0,127,1,252,0,31,192,254,0,15,224,127,0,3,248,63,128,1,252,0,0,0,0,0,255,248,0,7,255,252,0,31,255,254,0,127,255,254,1,255,223,252,3,248,3,252,15,224,3,248,31,192,3,240,63,128,7,240,127,0,0,0,255,128,0,1,255,224,0,1,255,248,0,3,255,254,0,3,255,255,128,3,255,255,192,1,255,255,192,0,255,255,192,0,63,255,128,0,7,255,128,0,1,255,0,0,1,254,31,192,1,252,63,128,3,248,127,0,7,240,255,0,15,224,255,0,63,129,255,231,255,1,255,255,252,1,255,255,240,0,255,255,192,0,127,252,0,0,0,0,0,127,255,255,243,255,255,255,159,255,255,252,255,255,255,231,255,255,255,63,255,255,248,0,127,0,0,3,248,0,0,31,192,0,0,254,0,0,7,240,0,0,63,128,0,1,252,0,0,15,224,0,0,127,0,0,3,248,0,0,31,192,0,0,254,0,0,7,240,0,0,63,128,0,1,252,0,0,15,224,0,0,127,0,0,3,248,0,0,31,192,0,0,254,0,0,7,240,0,0,63,128,0,1,252,0,0,15,224,0,0,127,0,0,3,248,0,31,192,0,254,7,240,0,63,129,252,0,15,224,127,0,3,248,31,192,0,254,7,240,0,63,129,252,0,15,224,127,0,3,248,31,192,0,254,7,240,0,63,129,252,0,15,224,127,0,3,248,31,192,0,254,7,240,0,63,129,252,0,15,224,127,0,3,248,31,192,0,254,7,240,0,63,129,252,0,15,224,127,0,3,248,31,192,0,252,7,240,0,63,1,252,0,31,192,127,128,7,240,15,224,3,252,3,254,1,255,0,255,255,255,128,31,255,255,192,3,255,255,240,0,127,255,248,0,15,255,248,0,0,255,248,0,0,0,0,0,0,127,0,0,254,127,0,0,254,127,128,1,254,63,128,1,252,63,128,1,252,31,192,3,248,31,192,3,248,31,192,3,248,15,224,7,240,15,224,7,240,15,224,7,240,7,240,15,224,7,240,15,224,7,240,15,224,3,248,31,192,3,248,31,192,3,248,31,192,1,252,63,128,1,252,63,128,1,252,63,0,0,254,127,0,0,254,127,0,0,126,126,0,0,127,126,0,0,127,254,0,0,63,252,0,0,63,252,0,0,63,252,0,0,31,248,0,0,31,248,0,0,31,248,0,0,15,240,0,127,0,63,224,7,247,240,3,254,0,127,127,0,63,224,7,231,248,3,254,0,126,63,128,127,240,15,227,248,7,255,0,254,63,128,127,240,15,195,248,7,255,0,252,31,192,255,240,31,193,252,15,223,129,248,31,192,253,248,31,129,252,15,223,129,248,15,224,249,248,63,128,254,31,159,195,240,15,225,248,252,63,0,254,31,143,195,240,7,241,240,252,127,0,127,63,15,199,224,7,243,240,126,126,0,127,63,7,231,224,3,251,224,126,254,0,63,190,7,239,192,3,255,224,62,252,0,63,254,3,255,192,1,255,192,63,252,0,31,252,3,255,128,1,255,192,63,248,0,15,252,1,255,128,0,255,192,31,248,0,15,248,1,255,0,0,255,128,31,240,0,7,248,0,255,0,63,192,1,254,15,240,1,254,7,252,0,254,1,254,0,255,0,127,128,255,0,63,192,127,0,15,240,127,128,3,252,127,128,1,254,63,128,0,127,191,192,0,31,255,192,0,15,255,192,0,3,255,224,0,0,255,224,0,0,127,224,0,0,31,240,0,0,31,252,0,0,31,254,0,0,15,255,128,0,15,255,224,0,15,255,240,0,15,243,252,0,7,240,255,0,7,248,127,128,7,248,31,224,3,248,7,248,3,252,3,252,3,252,0,255,1,254,0,127,193,254,0,31,225,254,0,7,248,255,0,3,254,127,128,0,254,63,192,1,254,63,192,1,252,31,224,3,248,15,224,3,248,15,240,7,240,7,248,15,240,3,248,15,224,3,252,31,192,1,252,31,192,1,254,63,128,0,255,127,0,0,127,127,0,0,127,254,0,0,63,252,0,0,31,252,0,0,31,248,0,0,15,248,0,0,7,240,0,0,7,240,0,0,7,240,0,0,7,240,0,0,7,240,0,0,7,240,0,0,7,240,0,0,7,240,0,0,7,240,0,0,7,240,0,0,7,240,0,0,7,240,0,0,7,240,0,0,7,240,0,63,255,255,240,255,255,255,195,255,255,255,15,255,255,252,63,255,255,224,255,255,255,0,0,7,252,0,0,63,224,0,0,255,0,0,7,248,0,0,63,224,0,0,255,0,0,7,248,0,0,63,192,0,1,255,0,0,7,248,0,0,63,192,0,1,255,0,0,15,248,0,0,63,192,0,1,254,0,0,15,248,0,0,63,192,0,1,254,0,0,15,240,0,0,127,192,0,1,255,255,255,15,255,255,252,127,255,255,241,255,255,255,199,255,255,255,31,255,255,252,7,254,7,254,7,254,7,254,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,254,7,254,7,254,7,254,112,3,224,7,128,30,0,120,1,240,3,192,15,0,60,0,248,1,224,7,128,30,0,124,0,240,3,192,15,128,62,0,120,1,224,7,192,31,0,60,0,240,3,224,15,128,30,0,120,1,240,7,192,15,0,60,0,248,1,224,7,128,30,0,124,127,224,127,224,127,224,127,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,3,224,127,224,127,224,127,224,127,224,0,15,192,0,0,254,0,0,7,240,0,0,63,192,0,3,254,0,0,31,248,0,1,247,192,0,15,158,0,0,124,248,0,7,199,192,0,62,31,0,3,224,248,0,31,7,192,0,248,31,0,15,128,248,0,124,3,224,7,224,31,0,62,0,252,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,254,15,224,252,15,192,252,15,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,0,3,255,248,0,255,255,192,15,255,254,1,255,191,240,31,192,127,1,252,7,240,0,0,127,0,0,7,240,0,31,255,0,31,255,240,7,255,255,0,255,255,240,31,240,127,1,252,7,240,63,128,127,3,248,7,240,63,128,255,3,248,31,240,63,255,255,129,255,255,254,31,255,255,224,255,241,254,7,254,15,224,0,0,0,31,192,0,1,252,0,0,31,192,0,1,252,0,0,31,192,0,1,252,0,0,31,192,0,1,252,0,0,31,192,0,1,252,0,0,31,207,248,1,253,255,224,31,255,255,1,255,255,248,31,255,255,129,255,3,252,31,224,31,193,252,1,252,31,192,15,193,252,0,252,31,192,15,193,252,0,254,31,192,15,225,252,0,252,31,192,15,193,252,0,252,31,192,31,193,254,1,252,31,240,63,193,255,255,248,31,255,255,129,251,255,240,31,159,254,1,240,255,128,0,0,0,0,0,0,0,0,31,252,0,15,255,224,3,255,255,0,255,255,224,31,255,254,7,248,31,192,254,1,252,63,128,63,135,240,7,240,254,0,0,31,192,0,3,248,0,0,127,0,0,15,224,0,1,252,0,0,63,128,63,135,240,7,240,127,0,254,15,240,63,128,255,255,240,31,255,252,1,255,255,0,31,255,192,0,255,224,0,0,0,0,0,0,63,128,0,3,248,0,0,63,128,0,3,248,0,0,63,128,0,3,248,0,0,63,128,0,3,248,0,0,63,128,0,3,248,1,255,63,128,63,251,248,7,255,255,128,255,255,248,31,251,255,129,252,15,248,31,192,127,131,248,3,248,63,128,63,131,248,3,248,63,128,63,131,248,3,248,63,128,63,131,248,3,248,63,128,63,131,248,3,248,63,128,63,129,252,7,248,31,192,255,129,255,191,248,15,255,255,128,127,253,248,3,255,143,128,31,240,248,0,0,0,0,0,0,0,0,31,254,0,15,255,240,3,255,255,0,255,255,240,31,251,254,7,240,15,224,252,1,252,63,128,31,135,240,3,240,255,255,254,31,255,255,195,255,255,252,127,255,255,143,224,0,1,252,0,0,63,128,31,131,240,3,240,127,0,254,15,240,63,192,255,223,248,31,255,254,1,255,255,128,31,255,224,0,255,240,0,0,0,0,0,252,1,254,3,254,7,254,7,254,7,240,7,224,15,224,15,224,15,224,127,254,127,254,127,254,127,254,127,254,15,224,15,224,15,224,15,224,15,224,15,224,15,224,15,224,15,224,15,224,15,224,15,224,15,224,15,224,15,224,15,224,15,224,15,224,15,224,0,0,7,224,0,0,126,0,0,7,224,0,0,254,0,0,15,224,0,1,252,0,255,255,128,63,255,224,7,255,254,0,255,255,240,31,224,127,1,252,3,248,31,192,63,129,252,3,248,31,192,63,129,252,3,248,15,224,127,0,127,255,240,3,255,254,0,63,255,128,15,255,224,1,252,0,0,31,128,0,1,248,0,0,31,192,0,1,255,255,240,31,255,255,128,255,255,252,15,255,255,225,255,255,254,63,0,15,231,240,0,126,127,0,7,231,240,0,254,127,255,255,195,255,255,252,31,255,255,0,255,255,192,31,192,0,1,252,0,0,31,192,0,1,252,0,0,31,192,0,1,252,0,0,31,192,0,1,252,0,0,31,192,0,1,252,0,0,31,199,248,1,253,255,224,31,255,255,1,255,255,240,31,255,255,129,255,7,248,31,224,63,129,252,3,248,31,192,63,129,252,3,248,31,192,63,129,252,3,248,31,192,63,129,252,3,248,31,192,63,129,252,3,248,31,192,63,129,252,3,248,31,192,63,129,252,3,248,31,192,63,129,252,3,248,31,192,63,129,252,3,248,31,193,252,31,193,252,31,192,0,0,0,0,0,0,0,31,193,252,31,193,252,31,193,252,31,193,252,31,193,252,31,193,252,31,193,252,31,193,252,31,193,252,31,193,252,31,193,252,31,193,252,7,240,31,192,127,1,252,7,240,0,0,0,0,0,0,0,0,0,127,1,252,7,240,31,192,127,1,252,7,240,31,192,127,1,252,7,240,31,192,127,1,252,7,240,31,192,127,1,252,7,240,31,192,127,1,252,7,240,31,192,127,1,252,15,225,255,143,254,63,240,255,193,252,0,31,192,0,7,240,0,1,252,0,0,127,0,0,31,192,0,7,240,0,1,252,0,0,127,0,0,31,192,0,7,240,0,1,252,3,248,127,1,252,31,192,254,7,240,127,1,252,63,192,127,31,224,31,207,240,7,243,248,1,253,252,0,127,254,0,31,255,192,7,255,240,1,255,254,0,127,255,192,31,247,240,7,248,254,1,252,63,128,127,7,240,31,192,254,7,240,63,129,252,7,240,127,1,252,31,192,63,135,240,15,240,31,193,252,31,193,252,31,193,252,31,193,252,31,193,252,31,193,252,31,193,252,31,193,252,31,193,252,31,193,252,31,193,252,31,193,252,31,193,252,31,193,252,31,193,252,31,193,252,31,193,252,0,0,0,0,0,15,135,252,15,240,7,231,255,31,252,3,247,255,223,255,1,255,255,255,255,192,255,255,255,255,224,127,195,255,7,240,63,192,255,1,248,31,192,127,0,252,15,224,63,128,126,7,240,31,192,63,3,248,15,224,31,129,252,7,240,15,192,254,3,248,7,224,127,1,252,3,240,63,128,254,1,248,31,192,127,0,252,15,224,63,128,126,7,240,31,192,63,3,248,15,224,31,129,252,7,240,15,192,254,3,248,7,224,127,1,252,3,240,63,128,254,1,248,31,192,127,0,252,0,0,0,0,1,240,255,128,31,159,254,1,251,255,240,31,255,255,1,255,255,248,31,224,127,129,254,3,248,31,192,63,129,252,3,248,31,192,63,129,252,3,248,31,192,63,129,252,3,248,31,192,63,129,252,3,248,31,192,63,129,252,3,248,31,192,63,129,252,3,248,31,192,63,129,252,3,248,31,192,63,129,252,3,248,31,192,63,128,0,0,0,0,15,255,0,3,255,252,0,127,255,224,15,255,255,0,255,255,248,31,224,63,129,252,1,252,63,128,31,195,248,0,252,63,128,15,195,248,0,252,63,128,15,227,248,0,254,63,128,15,195,248,0,252,63,128,15,193,248,1,252,31,192,31,193,254,3,248,15,253,255,128,255,255,240,7,255,254,0,63,255,192,0,255,240,0,0,0,0,0,0,0,1,240,255,128,31,159,254,1,251,255,240,31,255,255,129,255,255,248,31,240,63,193,254,1,252,31,192,31,193,252,0,252,31,192,15,193,252,0,252,31,192,15,225,252,0,254,31,192,15,193,252,0,252,31,192,15,193,252,1,252,31,224,31,193,255,3,248,31,253,255,129,255,255,248,31,255,255,1,253,255,224,31,207,248,1,252,0,0,31,192,0,1,252,0,0,31,192,0,1,252,0,0,31,192,0,1,252,0,0,31,192,0,0,0,0,0,0,31,240,252,3,255,207,192,127,254,252,15,255,255,193,255,255,252,31,192,127,193,252,3,252,63,128,63,195,248,1,252,63,128,31,195,248,1,252,63,128,31,195,248,1,252,63,128,31,195,248,1,252,63,128,31,195,248,3,252,31,192,63,193,252,7,252,31,255,255,192,255,255,252,7,255,255,192,63,253,252,1,255,31,192,0,1,252,0,0,31,192,0,1,252,0,0,31,192,0,1,252,0,0,31,192,0,1,252,0,0,31,192,0,0,7,195,225,251,248,126,254,31,255,135,255,225,255,248,127,192,31,224,7,240,1,252,0,127,0,31,192,7,240,1,252,0,127,0,31,192,7,240,1,252,0,127,0,31,192,7,240,1,252,0,127,0,31,192,0,0,0,0,0,127,248,0,127,255,128,63,255,240,31,255,254,7,255,255,129,252,15,224,126,1,252,31,128,0,7,248,0,1,255,224,0,63,255,128,7,255,248,0,127,255,0,3,255,224,0,15,252,0,0,255,15,192,31,195,248,7,240,254,1,252,31,255,255,7,255,255,128,255,255,192,31,255,224,1,255,224,0,0,0,0,7,224,7,224,7,224,7,224,7,224,7,224,15,224,15,224,127,254,127,254,127,254,127,254,127,254,15,224,15,224,15,224,15,224,15,224,15,224,15,224,15,224,15,224,15,224,15,224,15,224,15,224,15,224,15,240,15,254,7,254,7,254,1,254,0,0,31,192,63,129,252,3,248,31,192,63,129,252,3,248,31,192,63,129,252,3,248,31,192,63,129,252,3,248,31,192,63,129,252,3,248,31,192,63,129,252,3,248,31,192,63,129,252,3,248,31,192,63,129,252,3,248,31,192,63,129,252,7,248,31,224,255,129,255,255,248,15,255,255,128,255,253,248,7,255,159,128,31,240,248,0,0,0,0,254,0,63,191,0,31,159,192,15,207,224,15,227,248,7,225,252,3,240,126,3,248,63,129,248,31,192,252,7,224,252,3,248,126,0,252,63,0,126,63,0,63,159,128,15,207,192,7,239,192,3,247,224,0,255,224,0,127,240,0,31,248,0,15,248,0,7,252,0,1,252,0,0,254,0,254,1,252,3,243,240,15,224,31,159,128,127,128,252,254,7,252,7,227,240,63,224,126,31,129,255,3,240,252,15,252,31,135,240,255,224,248,31,135,223,15,192,252,62,248,126,7,241,247,227,224,31,159,63,31,0,252,248,249,248,7,231,199,207,128,31,62,63,124,0,253,225,251,224,7,255,7,254,0,31,248,63,240,0,255,193,255,128,7,252,15,248,0,31,224,63,192,0,255,1,254,0,7,248,15,240,0,31,128,127,0,127,128,63,143,240,31,193,252,7,240,63,131,248,15,241,252,1,252,127,0,63,191,128,15,255,192,1,255,224,0,63,248,0,15,252,0,1,254,0,0,127,192,0,63,248,0,31,254,0,15,255,192,3,243,248,1,252,254,0,254,31,192,63,135,248,31,192,254,15,224,31,199,248,7,249,252,0,255,254,0,63,191,0,31,159,192,15,207,224,15,227,248,7,225,252,3,240,126,3,240,63,129,248,31,192,252,7,224,252,3,248,126,0,252,63,0,126,63,0,63,159,128,15,207,192,7,231,192,1,255,224,0,255,224,0,63,240,0,31,248,0,15,248,0,3,252,0,1,254,0,0,254,0,0,127,0,0,127,0,0,127,128,7,255,128,3,255,128,1,255,192,0,255,128,0,127,0,0,63,255,252,63,255,252,63,255,252,63,255,248,63,255,248,0,7,240,0,15,224,0,31,224,0,63,192,0,63,128,0,127,0,0,255,0,1,254,0,1,252,0,3,252,0,7,248,0,15,240,0,15,224,0,31,224,0,63,255,254,63,255,254,127,255,254,127,255,254,127,255,254,0,62,0,63,192,31,240,7,252,3,248,0,248,0,62,0,15,128,3,224,0,248,0,62,0,15,128,3,224,0,248,0,62,0,15,128,3,224,0,248,0,252,0,255,0,63,0,15,192,3,252,0,63,0,3,224,0,248,0,62,0,15,128,3,224,0,248,0,62,0,15,128,3,224,0,248,0,62,0,15,128,3,224,0,254,0,31,240,7,252,0,255,0,15,128,31,1,240,31,1,240,31,1,240,31,1,240,31,1,240,31,1,240,31,1,240,31,1,240,31,1,240,31,1,240,31,1,240,31,1,240,31,1,240,31,1,240,31,1,240,31,1,240,31,1,240,31,1,240,31,1,240,31,1,240,31,1,240,31,0,31,0,15,240,3,254,0,255,128,7,240,0,124,0,31,0,7,192,1,240,0,124,0,31,0,7,192,1,240,0,124,0,31,0,7,192,1,240,0,124,0,15,192,3,254,0,63,128,15,224,15,248,3,240,1,240,0,124,0,31,0,7,192,1,240,0,124,0,31,0,7,192,1,240,0,124,0,31,0,7,192,1,240,1,252,3,254,0,255,128,63,192,7,192,0,0,0,0,0,7,224,0,128,127,224,6,3,255,224,120,31,255,255,224,127,255,255,129,224,127,252,6,0,127,224,16,0,126,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +static const Glyph f0_46_glyphs[]={{0,9,0,0,33,9,32},{0,14,32,0,1,14,33},{56,21,32,0,1,21,34},{140,27,34,0,0,27,35},{255,26,37,0,-1,26,36},{376,46,34,0,0,46,37},{572,36,34,0,0,36,38},{725,12,32,0,1,12,39},{773,17,42,0,-2,17,40},{863,17,42,0,-2,17,41},{953,19,32,0,1,19,42},{1029,30,24,0,9,30,43},{1119,14,15,0,26,14,44},{1146,16,16,0,17,16,45},{1178,14,7,0,26,14,46},{1191,14,37,0,-2,14,47},{1256,27,34,0,0,27,48},{1371,28,32,0,1,28,49},{1483,27,33,0,0,27,50},{1595,27,34,0,0,27,51},{1710,28,33,0,0,28,52},{1826,27,33,0,1,27,53},{1938,27,34,0,0,27,54},{2053,27,32,0,1,27,55},{2161,28,34,0,0,28,56},{2280,27,34,0,0,27,57},{2395,15,24,0,9,15,58},{2440,16,32,0,9,16,59},{2504,30,25,0,8,30,60},{2598,30,19,0,14,30,61},{2670,30,25,0,8,30,62},{2764,28,33,0,0,28,63},{2880,46,40,0,0,46,64},{3110,34,32,0,1,33,65},{3246,33,32,0,1,33,66},{3378,33,34,0,0,33,67},{3519,33,32,0,1,33,68},{3651,31,32,0,1,31,69},{3775,28,32,0,1,28,70},{3887,36,34,0,0,36,71},{4040,34,32,0,1,34,72},{4176,13,32,0,1,13,73},{4228,28,33,0,1,28,74},{4344,33,32,0,1,33,75},{4476,27,32,0,1,27,76},{4584,40,32,0,1,40,77},{4744,34,32,0,1,34,78},{4880,36,34,0,0,36,79},{5033,31,32,0,1,31,80},{5157,36,39,0,0,36,81},{5333,33,32,0,1,33,82},{5465,31,34,0,0,31,83},{5597,29,32,0,1,29,84},{5713,34,33,0,1,34,85},{5854,32,32,0,1,32,86},{5982,44,32,0,1,44,87},{6158,33,32,0,1,32,88},{6290,32,32,0,1,32,89},{6418,30,32,0,1,30,90},{6538,16,42,0,-2,16,91},{6622,14,37,0,-2,14,92},{6687,16,42,0,-2,16,93},{6771,29,32,0,1,29,94},{6887,24,7,0,33,24,95},{6908,11,33,0,0,10,96},{6954,28,26,0,8,28,97},{7045,28,35,0,-1,28,98},{7168,27,26,0,8,27,99},{7256,28,35,0,-1,28,100},{7379,27,26,0,8,27,101},{7467,16,34,0,-1,16,102},{7535,28,38,0,3,28,103},{7668,28,34,0,-1,28,104},{7787,12,34,0,-1,12,105},{7838,14,42,-2,-1,12,106},{7912,26,34,0,-1,26,107},{8023,12,34,0,-1,12,108},{8074,41,25,0,8,41,109},{8203,28,25,0,8,28,110},{8291,28,26,0,8,28,111},{8382,28,33,0,8,28,112},{8498,28,33,0,8,28,113},{8614,18,25,0,8,18,114},{8671,26,26,0,8,26,115},{8756,16,33,0,1,16,116},{8822,28,25,0,9,28,117},{8910,25,24,0,9,25,118},{8985,37,24,0,9,37,119},{9096,26,24,0,9,26,120},{9174,25,32,0,9,25,121},{9274,24,24,0,9,24,122},{9346,18,42,0,-1,18,123},{9441,12,43,0,-1,12,124},{9506,18,42,0,-1,18,125},{9601,30,19,0,14,30,126}}; +static const uint8_t f0_50_bits[]={15,224,31,192,63,128,127,0,254,1,252,3,248,7,240,15,224,31,192,63,128,127,0,126,0,248,1,240,3,224,7,192,15,128,31,0,62,0,124,0,248,1,240,3,224,0,0,0,0,0,0,127,0,254,1,252,3,248,7,240,15,224,31,192,31,199,240,63,143,224,127,31,192,254,63,129,252,127,3,248,254,7,241,252,15,227,248,31,199,240,63,143,224,62,31,128,124,31,0,248,62,1,240,124,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,30,3,224,1,248,31,0,15,129,248,0,124,15,192,7,224,124,0,63,3,224,1,240,31,0,15,129,248,0,124,15,128,7,224,124,7,255,255,252,63,255,255,225,255,255,255,15,255,255,248,127,255,255,192,63,7,224,1,240,63,0,15,129,248,0,252,15,128,7,224,124,0,62,7,224,3,240,63,1,255,255,255,143,255,255,252,127,255,255,227,255,255,255,31,255,255,248,31,129,240,0,252,15,128,7,192,124,0,62,7,224,1,240,63,0,31,129,240,0,252,15,128,7,192,252,0,62,7,224,0,0,7,0,0,0,112,0,0,7,0,0,0,112,0,0,7,0,0,0,112,0,0,127,240,0,63,255,192,7,255,254,0,255,255,240,15,255,255,1,254,127,248,31,199,63,129,252,115,248,31,231,0,1,255,112,0,15,255,0,0,255,254,0,7,255,252,0,63,255,240,0,255,255,128,0,255,248,0,7,255,192,0,115,252,63,135,31,195,248,113,252,63,199,31,195,254,119,252,31,255,255,128,255,255,248,7,255,255,0,63,255,192,0,255,240,0,0,112,0,0,7,0,0,0,112,0,0,7,0,0,0,112,0,0,7,0,0,0,96,0,0,0,0,0,0,0,0,127,192,0,31,0,0,255,248,0,31,0,0,255,254,0,31,128,0,127,255,0,15,128,0,126,15,192,15,128,0,62,3,224,15,128,0,31,1,240,15,192,0,15,128,248,7,192,0,7,192,124,7,192,0,3,224,62,7,224,0,1,240,31,3,224,0,0,252,31,131,224,0,0,62,15,131,224,0,0,31,255,195,240,0,0,7,255,193,240,0,0,1,255,193,240,0,0,0,63,129,248,63,128,0,0,0,248,127,240,0,0,0,248,127,252,0,0,0,248,127,255,0,0,0,252,63,31,128,0,0,124,63,7,224,0,0,124,31,1,240,0,0,126,15,128,248,0,0,62,7,192,124,0,0,62,3,224,62,0,0,62,1,240,31,0,0,63,0,248,15,128,0,31,0,124,7,192,0,31,0,31,7,192,0,31,0,15,255,224,0,15,128,3,255,240,0,15,128,1,255,240,0,15,128,0,63,224,0,0,0,0,0,0,0,0,0,0,0,0,0,127,254,0,0,15,255,254,0,0,127,255,254,0,3,255,255,252,0,31,255,255,248,0,127,192,63,224,3,252,0,63,192,15,240,0,127,0,63,128,1,252,0,254,0,7,240,3,248,0,0,0,15,240,0,0,0,31,224,0,0,0,63,224,0,0,0,127,252,31,254,0,127,240,127,248,3,255,193,255,224,31,255,7,255,129,255,252,31,254,15,248,0,127,0,63,128,1,252,1,254,0,7,240,7,240,0,31,192,31,192,0,127,0,127,0,1,252,1,252,0,7,240,7,248,0,63,192,15,240,1,254,0,63,224,15,248,0,127,255,255,192,1,255,255,254,0,1,255,255,240,0,3,255,255,128,0,1,255,248,0,0,0,0,0,0,31,192,254,7,240,63,129,252,15,224,127,3,248,31,192,254,3,224,31,0,248,7,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,30,0,15,128,7,192,1,240,0,248,0,126,0,31,0,15,192,3,240,0,248,0,126,0,31,128,15,192,3,240,0,252,0,63,0,15,192,7,224,1,248,0,126,0,31,128,7,224,1,248,0,126,0,31,128,7,224,1,248,0,126,0,15,192,3,240,0,252,0,63,0,7,192,1,248,0,126,0,15,128,3,240,0,252,0,31,0,7,224,0,248,0,31,0,7,192,0,248,0,30,0,30,0,7,192,0,248,0,62,0,7,192,1,248,0,62,0,15,192,3,240,0,124,0,31,128,7,224,0,248,0,63,0,15,192,3,240,0,252,0,31,128,7,224,1,248,0,126,0,31,128,7,224,1,248,0,126,0,31,128,7,224,1,248,0,252,0,63,0,15,192,3,240,0,252,0,126,0,31,128,7,192,3,240,0,252,0,62,0,31,128,7,192,3,224,0,248,0,124,0,30,0,0,0,240,0,15,0,0,240,1,143,8,28,115,195,231,124,63,255,192,255,240,3,252,0,63,192,15,255,3,255,252,62,119,193,199,60,24,240,128,15,0,0,240,0,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,224,0,0,3,224,0,0,3,224,0,0,3,224,0,0,3,224,0,0,3,224,0,0,3,224,0,0,3,224,0,0,3,224,0,0,3,224,0,0,3,224,0,15,255,255,240,15,255,255,240,15,255,255,240,15,255,255,240,15,255,255,240,0,3,224,0,0,3,224,0,0,3,224,0,0,3,224,0,0,3,224,0,0,3,224,0,0,3,224,0,0,3,224,0,0,3,224,0,0,3,224,0,15,224,31,192,63,128,127,0,254,1,252,3,248,0,240,1,224,3,192,7,0,30,0,248,3,224,7,128,0,0,63,254,31,255,15,255,135,255,195,255,225,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,224,31,192,63,128,127,0,254,1,252,3,248,0,0,28,0,124,0,240,3,224,7,192,15,128,30,0,124,0,248,1,240,3,192,15,128,31,0,62,0,120,1,240,3,224,7,192,15,0,62,0,124,0,248,1,224,7,192,15,128,31,0,60,0,248,1,240,3,192,15,128,31,0,62,0,120,1,240,3,224,7,192,15,0,62,0,124,0,0,0,0,0,3,255,128,0,127,255,0,7,255,252,0,127,255,240,7,255,255,192,63,193,254,3,252,7,248,31,192,31,192,254,0,254,7,240,7,240,127,0,31,195,248,0,254,31,192,7,240,254,0,63,135,240,1,252,63,128,15,225,252,0,127,15,224,3,248,127,0,31,195,248,0,254,31,192,7,240,254,0,63,135,240,1,252,63,128,15,224,254,0,254,7,240,7,240,63,128,63,129,254,3,252,7,248,63,192,63,255,254,0,255,255,224,3,255,254,0,15,255,224,0,31,252,0,0,0,0,0,0,0,240,0,0,15,192,0,0,255,0,0,31,252,0,7,255,240,0,31,255,192,0,127,255,0,1,255,252,0,0,7,240,0,0,31,192,0,0,127,0,0,1,252,0,0,7,240,0,0,31,192,0,0,127,0,0,1,252,0,0,7,240,0,0,31,192,0,0,127,0,0,1,252,0,0,7,240,0,0,31,192,0,0,127,0,0,1,252,0,0,7,240,0,0,31,192,0,0,127,0,0,1,252,0,0,7,240,0,31,255,255,192,127,255,255,1,255,255,252,7,255,255,240,31,255,255,192,0,0,0,0,0,255,240,0,15,255,240,0,255,255,224,7,255,255,192,63,255,255,128,255,129,254,3,248,3,252,31,224,15,240,127,0,31,193,252,0,127,7,240,1,252,31,192,15,240,0,0,63,192,0,1,254,0,0,15,248,0,0,127,192,0,3,254,0,0,31,248,0,0,255,192,0,15,254,0,0,127,224,0,3,255,0,0,63,248,0,1,255,128,0,15,252,0,0,127,192,0,1,254,0,0,15,240,0,0,63,255,255,193,255,255,255,7,255,255,252,31,255,255,240,127,255,255,193,255,255,255,0,0,0,0,0,3,255,192,0,127,255,128,7,255,254,0,127,255,248,7,255,255,224,63,192,255,3,252,3,252,31,192,15,224,254,0,127,7,240,3,248,0,0,31,192,0,1,252,0,0,31,224,0,3,254,0,1,255,224,0,15,252,0,0,127,224,0,3,255,192,0,31,255,0,0,7,252,0,0,15,224,0,0,127,128,0,1,252,63,128,15,225,252,0,127,15,224,3,248,127,128,31,195,252,1,254,15,240,31,224,127,255,255,1,255,255,240,7,255,255,0,31,255,240,0,63,254,0,0,0,0,0,0,63,128,0,0,254,0,0,7,240,0,0,31,192,0,0,127,0,0,3,248,56,0,15,225,224,0,63,135,128,1,252,30,0,7,240,248,0,31,131,224,0,254,31,128,3,240,126,0,31,195,248,0,126,15,224,3,248,63,128,15,192,254,0,127,3,248,1,248,15,224,15,192,63,128,63,0,254,1,248,3,248,7,255,255,255,31,255,255,252,127,255,255,241,255,255,255,199,255,255,255,0,0,63,128,0,0,254,0,0,3,248,0,0,15,224,0,0,63,128,0,0,254,0,0,3,248,0,0,15,224,0,7,255,255,128,63,255,252,1,255,255,224,31,255,255,0,255,255,248,7,255,255,192,63,0,0,1,248,0,0,15,192,0,0,126,0,0,3,240,0,0,31,135,224,0,253,255,192,7,255,255,128,63,255,254,1,255,255,240,31,248,255,192,255,1,254,3,240,7,248,0,0,63,192,0,0,254,0,0,7,240,0,0,63,135,240,1,252,63,128,15,225,254,0,127,15,240,7,248,63,192,127,129,255,7,252,7,255,255,192,31,255,252,0,127,255,192,1,255,252,0,3,255,128,0,0,0,0,0,0,0,0,3,255,128,0,127,255,0,7,255,252,0,127,255,240,3,255,255,192,63,193,254,1,252,7,248,31,192,31,192,254,0,254,7,224,7,240,63,0,0,3,248,0,0,31,199,252,0,254,127,248,7,255,255,224,63,255,255,129,255,255,254,15,252,31,240,127,192,127,131,252,1,254,31,224,7,240,255,0,63,135,240,1,252,63,128,15,224,254,0,127,7,240,3,248,63,128,63,193,254,1,252,7,248,63,224,63,255,254,0,255,255,240,3,255,255,0,15,255,224,0,31,252,0,0,0,0,0,63,255,255,225,255,255,255,15,255,255,248,127,255,255,195,255,255,254,31,255,255,224,0,0,254,0,0,15,224,0,0,254,0,0,7,240,0,0,127,0,0,7,240,0,0,63,128,0,3,248,0,0,31,128,0,1,252,0,0,15,192,0,0,254,0,0,7,224,0,0,127,0,0,3,240,0,0,63,128,0,1,252,0,0,15,192,0,0,254,0,0,7,240,0,0,63,128,0,1,248,0,0,15,192,0,0,254,0,0,7,240,0,0,63,128,0,1,252,0,0,15,224,0,0,0,0,0,0,3,255,192,0,127,255,128,7,255,254,0,127,255,248,7,255,255,224,63,192,255,3,252,3,252,31,192,15,224,254,0,127,7,240,3,248,63,128,31,193,254,1,254,7,240,31,224,63,243,254,0,255,255,224,3,255,254,0,15,255,224,1,255,255,192,31,255,255,1,255,7,252,15,224,15,224,255,0,127,135,240,1,252,63,128,15,225,252,0,127,15,224,3,248,127,128,63,195,252,1,254,15,248,63,224,127,255,255,1,255,255,240,7,255,255,0,31,255,240,0,63,254,0,0,0,0,0,0,0,0,0,1,255,192,0,31,255,192,1,255,255,128,15,255,255,0,63,255,254,1,255,7,248,7,240,15,240,63,192,31,192,254,0,127,3,248,0,254,15,224,3,248,63,128,15,224,254,0,127,131,252,1,254,7,248,15,248,31,240,127,224,127,255,255,128,255,255,254,1,255,255,248,3,255,239,224,3,254,63,128,0,0,254,0,0,3,248,31,192,15,192,127,0,127,1,252,1,252,7,240,7,240,31,224,63,128,63,193,254,0,255,255,240,1,255,255,192,3,255,254,0,7,255,240,0,7,255,0,0,0,0,0,7,240,3,248,1,252,0,254,0,127,0,63,128,31,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,254,0,127,0,63,128,31,192,15,224,7,240,3,248,0,7,240,3,248,1,252,0,254,0,127,0,63,128,31,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,254,0,127,0,63,128,31,192,15,224,7,240,3,248,0,60,0,30,0,15,0,15,0,7,128,15,128,15,128,7,128,0,0,0,0,0,0,0,0,0,0,32,0,0,1,240,0,0,7,240,0,0,31,240,0,0,255,240,0,3,255,224,0,31,255,128,0,127,254,0,3,255,240,0,15,255,192,0,31,254,0,0,31,240,0,0,31,192,0,0,31,192,0,0,31,248,0,0,31,254,0,0,15,255,192,0,3,255,240,0,0,127,254,0,0,31,255,192,0,3,255,224,0,0,255,240,0,0,31,240,0,0,7,240,0,0,0,240,0,0,0,32,15,255,255,240,15,255,255,240,15,255,255,240,15,255,255,240,15,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,240,15,255,255,240,15,255,255,240,15,255,255,240,15,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,15,0,0,0,15,224,0,0,15,248,0,0,15,254,0,0,15,255,192,0,3,255,240,0,0,127,254,0,0,31,255,128,0,3,255,240,0,0,255,248,0,0,31,248,0,0,3,248,0,0,3,248,0,0,31,248,0,0,255,248,0,3,255,240,0,31,255,128,0,255,254,0,3,255,240,0,15,255,192,0,15,254,0,0,15,248,0,0,15,192,0,0,15,0,0,0,8,0,0,0,0,0,0,0,1,255,224,0,31,255,224,0,255,255,224,7,255,255,192,63,255,255,0,255,3,254,7,248,7,248,31,224,15,224,127,0,63,129,252,0,254,7,240,3,248,0,0,31,224,0,0,255,0,0,7,252,0,0,63,224,0,1,255,0,0,15,248,0,0,127,192,0,1,252,0,0,15,224,0,0,63,0,0,0,252,0,0,3,240,0,0,15,192,0,0,0,0,0,0,0,0,0,0,0,0,0,15,224,0,0,63,128,0,0,254,0,0,3,248,0,0,15,224,0,0,63,128,0,0,254,0,0,0,0,0,0,0,0,0,0,63,255,224,0,0,0,255,255,254,0,0,1,255,255,255,192,0,3,255,255,255,240,0,3,254,0,3,252,0,3,248,0,0,127,128,3,240,0,0,15,192,3,240,0,0,1,240,3,240,1,254,0,124,1,240,7,255,192,62,1,240,7,255,240,15,128,240,7,255,252,3,192,248,3,255,254,1,224,120,3,248,63,128,248,60,1,248,15,192,60,30,0,252,7,224,30,30,0,0,3,240,15,15,0,0,1,248,7,135,128,0,127,252,3,195,192,1,255,254,1,225,224,3,255,255,0,240,240,3,255,255,128,120,120,3,254,15,192,60,60,3,248,7,224,60,30,1,248,3,240,30,15,0,252,1,248,15,7,192,126,1,252,15,1,224,63,1,254,15,128,240,31,193,255,143,128,120,7,255,255,255,128,62,3,255,247,255,128,15,0,255,241,255,128,7,192,15,224,127,0,1,240,0,0,0,0,0,124,0,0,0,0,0,63,0,0,0,0,0,15,224,0,0,0,0,3,255,0,62,0,0,0,127,255,255,0,0,0,31,255,255,128,0,0,1,255,255,192,0,0,0,15,254,0,0,0,0,7,254,0,0,0,127,224,0,0,7,255,0,0,0,255,240,0,0,15,255,0,0,1,255,248,0,0,31,255,128,0,1,251,252,0,0,63,159,192,0,3,249,252,0,0,127,31,224,0,7,240,254,0,0,127,15,240,0,15,224,255,0,0,254,7,240,0,31,224,127,128,1,252,3,248,0,31,192,63,128,3,252,3,252,0,63,128,31,192,3,248,1,254,0,127,255,255,224,7,255,255,254,0,255,255,255,240,15,255,255,255,0,255,255,255,248,31,255,255,255,129,252,0,3,248,63,192,0,63,195,252,0,3,252,63,128,0,31,231,248,0,1,254,127,128,0,31,239,240,0,0,255,15,255,255,224,0,255,255,255,128,15,255,255,252,0,255,255,255,224,15,255,255,255,0,254,0,31,248,15,224,0,127,128,254,0,3,248,15,224,0,63,128,254,0,3,248,15,224,0,63,128,254,0,7,240,15,224,0,127,0,254,0,31,224,15,255,255,252,0,255,255,255,128,15,255,255,248,0,255,255,255,224,15,255,255,255,0,254,0,7,248,15,224,0,63,128,254,0,1,252,15,224,0,31,192,254,0,1,252,15,224,0,31,192,254,0,1,252,15,224,0,63,192,254,0,7,252,15,255,255,255,128,255,255,255,248,15,255,255,255,0,255,255,255,224,15,255,255,252,0,255,255,254,0,0,0,0,0,0,0,255,248,0,0,63,255,240,0,15,255,255,128,1,255,255,252,0,63,255,255,224,7,255,255,255,0,255,192,31,248,15,240,0,127,129,254,0,3,248,31,224,0,63,193,252,0,1,252,31,192,0,31,195,248,0,1,252,63,128,0,0,3,248,0,0,0,63,128,0,0,3,248,0,0,0,63,128,0,0,3,248,0,0,0,63,128,0,0,3,248,0,0,0,63,128,0,31,193,252,0,1,252,31,192,0,31,193,254,0,3,252,31,224,0,63,192,255,0,7,248,15,252,1,255,128,127,255,255,240,3,255,255,254,0,31,255,255,224,0,255,255,248,0,7,255,255,0,0,15,255,128,0,0,0,0,0,15,255,254,0,0,127,255,254,0,3,255,255,252,0,31,255,255,248,0,255,255,255,224,7,255,255,255,128,63,128,15,252,1,252,0,31,240,15,224,0,127,128,127,0,1,254,3,248,0,7,240,31,192,0,63,128,254,0,1,254,7,240,0,7,240,63,128,0,63,129,252,0,1,252,15,224,0,15,224,127,0,0,127,3,248,0,3,248,31,192,0,31,192,254,0,0,254,7,240,0,15,240,63,128,0,127,1,252,0,3,248,15,224,0,63,192,127,0,3,252,3,248,0,63,224,31,192,7,254,0,255,255,255,240,7,255,255,255,0,63,255,255,240,1,255,255,254,0,15,255,255,192,0,127,255,240,0,0,15,255,255,255,3,255,255,255,192,255,255,255,240,63,255,255,252,15,255,255,255,3,255,255,255,192,254,0,0,0,63,128,0,0,15,224,0,0,3,248,0,0,0,254,0,0,0,63,128,0,0,15,224,0,0,3,255,255,254,0,255,255,255,128,63,255,255,224,15,255,255,248,3,255,255,254,0,255,255,255,128,63,128,0,0,15,224,0,0,3,248,0,0,0,254,0,0,0,63,128,0,0,15,224,0,0,3,248,0,0,0,254,0,0,0,63,128,0,0,15,255,255,255,3,255,255,255,192,255,255,255,240,63,255,255,252,15,255,255,255,3,255,255,255,192,15,255,255,252,15,255,255,252,15,255,255,252,15,255,255,252,15,255,255,252,15,255,255,252,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,255,255,240,15,255,255,240,15,255,255,240,15,255,255,240,15,255,255,240,15,255,255,240,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,0,0,0,0,0,0,3,255,240,0,0,31,255,252,0,0,127,255,255,0,0,255,255,255,128,1,255,255,255,192,3,255,255,255,224,3,254,0,63,240,7,252,0,15,240,15,248,0,7,248,15,240,0,7,248,15,224,0,3,248,15,224,0,3,248,31,224,0,0,0,31,192,0,0,0,31,192,0,0,0,31,192,0,0,0,31,192,31,255,248,31,192,31,255,248,31,192,31,255,248,31,192,31,255,248,31,192,31,255,248,31,192,0,3,248,15,224,0,3,248,15,224,0,7,248,15,240,0,7,248,15,240,0,15,248,7,248,0,31,248,7,254,0,63,248,3,255,255,255,248,1,255,255,255,248,0,255,255,255,248,0,127,255,252,248,0,31,255,248,248,0,7,255,224,248,0,0,0,0,0,15,224,0,31,192,63,128,0,127,0,254,0,1,252,3,248,0,7,240,15,224,0,31,192,63,128,0,127,0,254,0,1,252,3,248,0,7,240,15,224,0,31,192,63,128,0,127,0,254,0,1,252,3,248,0,7,240,15,224,0,31,192,63,255,255,255,0,255,255,255,252,3,255,255,255,240,15,255,255,255,192,63,255,255,255,0,255,255,255,252,3,248,0,7,240,15,224,0,31,192,63,128,0,127,0,254,0,1,252,3,248,0,7,240,15,224,0,31,192,63,128,0,127,0,254,0,1,252,3,248,0,7,240,15,224,0,31,192,63,128,0,127,0,254,0,1,252,3,248,0,7,240,15,224,0,31,192,63,128,0,127,0,15,224,31,192,63,128,127,0,254,1,252,3,248,7,240,15,224,31,192,63,128,127,0,254,1,252,3,248,7,240,15,224,31,192,63,128,127,0,254,1,252,3,248,7,240,15,224,31,192,63,128,127,0,254,1,252,3,248,7,240,15,224,31,192,0,0,15,224,0,0,31,192,0,0,63,128,0,0,127,0,0,0,254,0,0,1,252,0,0,3,248,0,0,7,240,0,0,15,224,0,0,31,192,0,0,63,128,0,0,127,0,0,0,254,0,0,1,252,0,0,3,248,0,0,7,240,0,0,15,224,0,0,31,192,0,0,63,128,0,0,127,0,0,0,254,0,0,1,252,15,224,3,248,31,192,7,240,63,128,15,224,127,0,63,192,255,0,127,1,255,1,254,1,255,255,252,3,255,255,240,3,255,255,224,3,255,255,128,3,255,252,0,1,255,224,0,0,0,0,0,15,224,0,127,192,254,0,15,248,15,224,1,255,128,254,0,63,240,15,224,7,254,0,254,0,255,192,15,224,15,248,0,254,1,255,0,15,224,63,224,0,254,7,252,0,15,224,255,128,0,254,31,240,0,15,227,254,0,0,254,127,192,0,15,239,252,0,0,255,255,224,0,15,255,255,0,0,255,255,240,0,15,255,255,128,0,255,247,252,0,15,254,63,192,0,255,195,254,0,15,248,31,240,0,255,0,255,0,15,224,15,248,0,254,0,127,192,15,224,7,252,0,254,0,63,224,15,224,1,255,0,254,0,31,240,15,224,0,255,128,254,0,7,248,15,224,0,127,192,254,0,3,254,15,224,0,0,63,128,0,0,254,0,0,3,248,0,0,15,224,0,0,63,128,0,0,254,0,0,3,248,0,0,15,224,0,0,63,128,0,0,254,0,0,3,248,0,0,15,224,0,0,63,128,0,0,254,0,0,3,248,0,0,15,224,0,0,63,128,0,0,254,0,0,3,248,0,0,15,224,0,0,63,128,0,0,254,0,0,3,248,0,0,15,224,0,0,63,128,0,0,254,0,0,3,248,0,0,15,255,255,248,63,255,255,224,255,255,255,131,255,255,254,15,255,255,248,63,255,255,224,15,255,0,7,255,0,255,240,0,127,240,15,255,0,15,255,0,255,248,0,255,240,15,255,128,15,255,0,255,248,1,255,240,15,255,128,31,255,0,255,252,1,255,240,15,239,192,31,127,0,254,252,3,247,240,15,239,192,63,127,0,254,254,3,231,240,15,231,224,126,127,0,254,126,7,231,240,15,231,240,126,127,0,254,127,7,199,240,15,227,240,252,127,0,254,63,15,199,240,15,227,248,252,127,0,254,31,143,135,240,15,225,249,248,127,0,254,31,159,135,240,15,225,253,240,127,0,254,15,223,7,240,15,224,255,240,127,0,254,15,255,7,240,15,224,127,224,127,0,254,7,254,7,240,15,224,127,224,127,0,254,7,252,7,240,15,224,63,192,127,0,254,3,252,7,240,15,224,63,192,127,0,254,1,248,7,240,15,224,0,31,192,63,192,0,127,0,255,128,1,252,3,255,0,7,240,15,252,0,31,192,63,248,0,127,0,255,240,1,252,3,255,224,7,240,15,255,128,31,192,63,255,0,127,0,255,254,1,252,3,255,252,7,240,15,255,240,31,192,63,191,224,127,0,254,127,193,252,3,248,255,135,240,15,227,254,31,192,63,135,252,127,0,254,15,249,252,3,248,31,231,240,15,224,127,223,192,63,128,255,255,0,254,1,255,252,3,248,7,255,240,15,224,15,255,192,63,128,31,255,0,254,0,63,252,3,248,0,255,240,15,224,1,255,192,63,128,3,255,0,254,0,7,252,3,248,0,31,240,15,224,0,63,192,63,128,0,127,0,0,0,0,0,0,0,31,255,192,0,0,255,255,224,0,7,255,255,240,0,31,255,255,240,0,127,255,255,240,1,255,255,255,240,3,254,0,63,224,15,248,0,63,224,63,224,0,63,192,127,128,0,63,192,254,0,0,63,129,252,0,0,127,7,248,0,0,255,15,224,0,0,254,31,192,0,1,252,63,128,0,3,248,127,0,0,7,240,254,0,0,15,225,252,0,0,31,195,248,0,0,63,135,240,0,0,127,15,240,0,1,254,15,224,0,3,248,31,192,0,7,240,63,192,0,31,224,63,192,0,127,128,127,192,1,255,0,127,192,7,252,0,255,255,255,248,0,255,255,255,224,0,255,255,255,128,0,255,255,254,0,0,127,255,240,0,0,63,255,128,0,0,0,0,0,0,15,255,255,192,3,255,255,252,0,255,255,255,192,63,255,255,248,15,255,255,254,3,255,255,255,192,254,0,31,240,63,128,1,254,15,224,0,127,131,248,0,15,224,254,0,3,248,63,128,0,254,15,224,0,63,131,248,0,31,224,254,0,7,248,63,128,7,252,15,255,255,255,3,255,255,255,128,255,255,255,192,63,255,255,224,15,255,255,240,3,255,255,224,0,254,0,0,0,63,128,0,0,15,224,0,0,3,248,0,0,0,254,0,0,0,63,128,0,0,15,224,0,0,3,248,0,0,0,254,0,0,0,63,128,0,0,15,224,0,0,3,248,0,0,0,0,0,0,0,0,0,31,255,192,0,0,255,255,224,0,7,255,255,240,0,31,255,255,240,0,127,255,255,240,1,255,255,255,240,3,254,0,63,224,15,248,0,63,224,63,224,0,63,192,127,128,0,63,192,254,0,0,63,129,252,0,0,127,7,248,0,0,255,15,224,0,0,254,31,192,0,1,252,63,128,0,3,248,127,0,0,7,240,254,0,0,15,225,252,0,0,31,195,248,0,0,63,135,240,0,0,127,15,240,0,1,254,15,224,0,3,248,31,192,0,7,240,63,192,0,31,224,63,192,0,127,192,127,192,1,255,0,127,192,7,254,0,255,255,255,248,0,255,255,255,224,0,255,255,255,128,0,255,255,254,0,0,127,255,240,0,0,31,255,224,0,0,0,63,224,0,0,0,63,224,0,0,0,63,224,0,0,0,63,224,0,0,0,63,224,0,0,0,63,224,0,0,0,63,192,15,255,255,224,0,127,255,255,192,3,255,255,255,128,31,255,255,254,0,255,255,255,240,7,255,255,255,192,63,128,3,254,1,252,0,7,248,15,224,0,63,192,127,0,0,254,3,248,0,7,240,31,192,0,63,128,254,0,1,252,7,240,0,31,224,63,128,0,254,1,252,0,31,240,15,255,255,255,0,127,255,255,248,3,255,255,255,128,31,255,255,248,0,255,255,255,128,7,240,3,252,0,63,128,15,240,1,252,0,127,128,15,224,1,254,0,127,0,15,240,3,248,0,63,192,31,192,1,254,0,254,0,7,248,7,240,0,63,192,63,128,0,254,1,252,0,7,248,15,224,0,31,192,127,0,0,255,0,0,0,0,0,0,31,255,128,0,63,255,240,0,127,255,254,0,127,255,255,128,127,255,255,224,63,224,31,240,63,192,3,252,31,192,1,254,15,224,0,127,7,240,0,63,131,252,0,0,1,255,128,0,0,127,248,0,0,63,255,192,0,15,255,252,0,3,255,255,192,0,255,255,240,0,31,255,254,0,1,255,255,0,0,31,255,192,0,0,255,240,0,0,15,248,0,0,3,252,63,128,0,254,31,192,0,127,15,240,0,63,135,248,0,63,193,255,0,63,192,255,255,255,224,63,255,255,224,15,255,255,240,3,255,255,240,0,255,255,224,0,15,255,192,0,0,0,0,0,127,255,255,254,127,255,255,254,127,255,255,254,127,255,255,254,127,255,255,254,127,255,255,254,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,0,15,224,0,15,224,0,31,192,63,128,0,127,0,254,0,1,252,3,248,0,7,240,15,224,0,31,192,63,128,0,127,0,254,0,1,252,3,248,0,7,240,15,224,0,31,192,63,128,0,127,0,254,0,1,252,3,248,0,7,240,15,224,0,31,192,63,128,0,127,0,254,0,1,252,3,248,0,7,240,15,224,0,31,192,63,128,0,127,0,254,0,1,252,3,248,0,7,240,15,224,0,31,192,63,128,0,127,0,254,0,1,252,3,248,0,7,240,15,240,0,63,192,31,192,0,254,0,127,128,7,248,1,255,0,127,224,3,255,255,255,0,15,255,255,252,0,31,255,255,224,0,63,255,255,0,0,63,255,240,0,0,63,255,0,0,0,0,0,0,0,127,128,0,63,207,240,0,7,248,255,0,0,254,31,224,0,63,195,252,0,7,248,63,192,0,254,7,248,0,63,192,255,0,7,240,15,240,0,254,1,254,0,63,192,63,192,7,240,3,252,1,254,0,127,128,63,192,7,240,7,240,0,255,1,254,0,31,224,63,128,1,252,7,240,0,63,193,254,0,7,248,63,128,0,127,7,240,0,15,241,254,0,1,254,63,128,0,31,199,240,0,3,253,254,0,0,63,191,128,0,7,247,240,0,0,255,252,0,0,15,255,128,0,1,255,240,0,0,63,252,0,0,3,255,128,0,0,127,240,0,0,15,252,0,0,0,255,128,0,127,0,15,248,0,255,127,128,15,248,0,254,127,128,31,248,0,254,127,128,31,252,0,254,63,128,31,252,1,254,63,192,31,252,1,252,63,192,63,252,1,252,63,192,63,254,1,252,31,192,63,254,1,248,31,224,63,254,3,248,31,224,127,126,3,248,31,224,127,126,3,248,15,224,126,127,3,240,15,240,126,127,7,240,15,240,126,127,7,240,7,240,254,63,7,240,7,240,252,63,135,224,7,248,252,63,143,224,7,248,252,63,143,224,3,249,252,31,143,224,3,249,248,31,207,192,3,253,248,31,223,192,3,253,248,31,223,192,1,253,248,15,223,128,1,255,240,15,255,128,1,255,240,15,255,128,1,255,240,15,255,128,0,255,240,7,255,0,0,255,240,7,255,0,0,255,224,7,255,0,0,127,224,7,255,0,0,127,224,3,254,0,0,127,224,3,254,0,0,127,192,3,254,0,63,224,0,127,131,254,0,31,240,127,192,3,252,7,252,0,255,0,127,192,63,224,15,248,7,248,0,255,129,254,0,15,248,127,128,0,255,15,240,0,31,243,252,0,1,255,255,0,0,31,255,224,0,3,255,248,0,0,63,254,0,0,3,255,192,0,0,127,240,0,0,7,254,0,0,1,255,192,0,0,127,252,0,0,31,255,192,0,3,255,252,0,0,255,255,128,0,63,239,248,0,7,248,255,128,1,254,15,240,0,127,193,255,0,15,240,31,240,3,252,3,254,0,255,128,63,224,31,224,3,254,7,248,0,127,193,255,0,7,252,127,192,0,127,207,240,0,15,248,127,192,0,63,199,248,0,15,240,255,128,1,254,15,240,0,127,128,255,0,31,224,31,240,3,252,1,254,0,255,0,31,224,31,192,3,254,7,248,0,63,193,254,0,3,252,63,128,0,127,143,240,0,7,249,252,0,0,127,255,128,0,15,255,224,0,0,255,248,0,0,31,255,0,0,1,255,192,0,0,31,240,0,0,3,254,0,0,0,63,128,0,0,7,240,0,0,0,254,0,0,0,31,192,0,0,3,248,0,0,0,127,0,0,0,15,224,0,0,1,252,0,0,0,63,128,0,0,7,240,0,0,0,254,0,0,0,31,192,0,0,3,248,0,0,0,127,0,0,63,255,255,252,63,255,255,252,63,255,255,252,63,255,255,252,63,255,255,248,63,255,255,240,0,0,63,240,0,0,63,224,0,0,127,192,0,0,255,128,0,1,255,128,0,1,255,0,0,3,254,0,0,7,254,0,0,7,252,0,0,15,248,0,0,31,240,0,0,63,240,0,0,63,224,0,0,127,192,0,0,255,128,0,0,255,128,0,1,255,0,0,3,254,0,0,3,254,0,0,7,252,0,0,15,248,0,0,31,240,0,0,31,255,255,252,63,255,255,252,127,255,255,252,127,255,255,252,127,255,255,252,127,255,255,252,7,255,3,255,129,255,192,255,224,126,0,63,0,31,128,15,192,7,224,3,240,1,248,0,252,0,126,0,63,0,31,128,15,192,7,224,3,240,1,248,0,252,0,126,0,63,0,31,128,15,192,7,224,3,240,1,248,0,252,0,126,0,63,0,31,128,15,192,7,224,3,240,1,248,0,252,0,126,0,63,0,31,128,15,192,7,255,3,255,129,255,192,255,224,127,240,112,1,240,1,224,3,224,7,192,15,128,15,0,31,0,62,0,124,0,120,0,248,1,240,3,224,3,192,7,192,15,128,31,0,30,0,62,0,124,0,248,0,240,1,240,3,224,7,192,7,128,15,128,31,0,30,0,62,0,124,0,248,0,240,1,240,3,224,7,192,7,128,15,128,31,127,240,63,248,31,252,15,254,7,255,0,31,128,15,192,7,224,3,240,1,248,0,252,0,126,0,63,0,31,128,15,192,7,224,3,240,1,248,0,252,0,126,0,63,0,31,128,15,192,7,224,3,240,1,248,0,252,0,126,0,63,0,31,128,15,192,7,224,3,240,1,248,0,252,0,126,0,63,0,31,128,15,192,7,224,3,240,63,248,31,252,15,254,7,255,0,0,7,224,0,0,7,240,0,0,15,240,0,0,15,240,0,0,31,248,0,0,31,248,0,0,63,252,0,0,62,124,0,0,62,124,0,0,126,62,0,0,124,62,0,0,252,63,0,0,248,31,0,1,248,31,128,1,248,15,128,1,240,15,128,3,240,15,192,3,224,7,192,7,224,7,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,7,240,63,129,248,15,192,126,3,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,255,224,0,63,255,224,1,255,255,192,15,255,255,128,127,255,254,1,254,7,252,7,240,15,240,31,192,31,192,0,0,127,0,0,1,252,0,3,255,240,0,255,255,192,31,255,255,0,255,255,252,7,254,7,240,31,224,31,192,255,0,127,3,248,1,252,15,224,15,240,63,128,127,192,255,3,255,3,255,255,255,135,255,255,254,31,255,239,248,63,255,63,224,63,240,127,128,0,0,0,31,192,0,0,127,0,0,1,252,0,0,7,240,0,0,31,192,0,0,127,0,0,1,252,0,0,7,240,0,0,31,192,0,0,127,0,0,1,252,127,192,7,243,255,192,31,223,255,128,127,255,255,1,255,255,252,7,254,15,248,31,240,31,224,127,128,63,193,254,0,127,7,240,1,252,31,192,7,240,127,0,31,193,252,0,127,7,240,1,252,31,192,7,240,127,0,31,193,252,0,127,7,248,1,252,31,224,15,224,127,192,63,129,255,131,254,7,255,255,240,31,255,255,192,126,255,254,1,249,255,240,7,225,255,0,0,0,0,0,0,0,0,0,3,255,192,0,127,255,128,7,255,254,0,127,255,248,7,255,255,224,127,192,255,3,252,3,248,31,192,31,225,254,0,127,15,224,3,248,127,0,0,3,248,0,0,31,192,0,0,254,0,0,7,240,0,0,63,128,0,1,252,0,127,15,240,3,248,63,128,63,193,254,1,252,15,248,31,224,63,255,255,0,255,255,240,3,255,255,0,15,255,240,0,31,254,0,0,0,0,0,0,0,15,224,0,0,63,128,0,0,254,0,0,3,248,0,0,15,224,0,0,63,128,0,0,254,0,0,3,248,0,0,15,224,0,0,63,128,15,248,254,0,255,251,248,7,255,255,224,63,255,255,128,255,255,254,7,252,31,248,31,224,63,224,127,0,127,131,252,1,254,15,224,3,248,63,128,15,224,254,0,63,131,248,0,254,15,224,3,248,63,128,15,224,254,0,63,131,248,0,254,15,240,7,248,31,192,31,224,127,128,255,129,255,7,254,3,255,255,248,15,255,255,224,31,255,223,128,63,254,126,0,63,225,248,0,0,0,0,0,0,0,0,3,255,192,0,127,255,128,7,255,254,0,127,255,248,7,255,255,192,127,192,255,3,252,3,248,31,192,15,225,252,0,127,15,224,3,248,127,255,255,195,255,255,254,31,255,255,240,255,255,255,135,240,0,0,63,128,0,1,252,0,127,15,240,3,248,63,128,31,193,254,1,254,15,248,31,224,63,255,255,0,255,255,240,3,255,255,0,15,255,240,0,31,254,0,0,0,0,0,0,127,0,127,224,63,248,15,254,7,255,129,254,0,127,0,31,192,7,240,1,252,7,255,249,255,254,127,255,159,255,231,255,248,31,192,7,240,1,252,0,127,0,31,192,7,240,1,252,0,127,0,31,192,7,240,1,252,0,127,0,31,192,7,240,1,252,0,127,0,31,192,7,240,1,252,0,127,0,31,192,0,0,1,248,0,0,3,248,0,0,15,240,0,0,31,192,0,0,127,128,15,255,254,0,127,255,240,3,255,255,128,15,255,255,128,31,224,63,128,127,128,127,128,254,0,127,1,252,0,254,3,248,1,252,7,240,3,248,15,240,15,240,15,240,63,192,15,255,255,128,15,255,254,0,15,255,248,0,63,255,224,1,255,254,0,7,248,0,0,15,224,0,0,31,192,0,0,63,192,0,0,127,255,255,128,127,255,255,128,255,255,255,129,255,255,255,7,255,255,255,31,192,1,254,127,0,1,252,254,0,3,249,252,0,7,243,252,0,31,231,255,255,255,135,255,255,254,15,255,255,248,15,255,255,224,7,255,255,0,31,192,0,0,127,0,0,1,252,0,0,7,240,0,0,31,192,0,0,127,0,0,1,252,0,0,7,240,0,0,31,192,0,0,127,0,0,1,252,63,224,7,243,255,192,31,223,255,128,127,255,255,1,255,255,252,7,255,31,248,31,240,31,224,127,128,63,129,254,0,254,7,240,3,248,31,192,15,224,127,0,63,129,252,0,254,7,240,3,248,31,192,15,224,127,0,63,129,252,0,254,7,240,3,248,31,192,15,224,127,0,63,129,252,0,254,7,240,3,248,31,192,15,224,127,0,63,129,252,0,254,7,240,3,248,31,192,254,7,240,63,129,252,15,224,0,0,0,0,0,0,7,240,63,129,252,15,224,127,3,248,31,192,254,7,240,63,129,252,15,224,127,3,248,31,192,254,7,240,63,129,252,15,224,127,3,248,31,192,254,7,240,63,128,7,240,15,224,31,192,63,128,127,0,254,0,0,0,0,0,0,0,0,31,192,63,128,127,0,254,1,252,3,248,7,240,15,224,31,192,63,128,127,0,254,1,252,3,248,7,240,15,224,31,192,63,128,127,0,254,1,252,3,248,7,240,15,224,31,192,63,128,127,0,254,1,252,7,248,255,241,255,227,255,135,254,15,248,0,0,0,31,192,0,1,252,0,0,31,192,0,1,252,0,0,31,192,0,1,252,0,0,31,192,0,1,252,0,0,31,192,0,1,252,0,0,31,192,31,225,252,3,252,31,192,127,129,252,15,240,31,193,254,1,252,31,224,31,195,252,1,252,127,128,31,207,240,1,253,254,0,31,255,224,1,255,254,0,31,255,240,1,255,255,0,31,255,248,1,255,191,192,31,241,252,1,254,31,224,31,192,254,1,252,15,240,31,192,127,129,252,7,248,31,192,63,193,252,1,252,31,192,31,225,252,0,255,31,192,254,7,240,63,129,252,15,224,127,3,248,31,192,254,7,240,63,129,252,15,224,127,3,248,31,192,254,7,240,63,129,252,15,224,127,3,248,31,192,254,7,240,63,129,252,15,224,127,3,248,31,192,254,7,240,63,128,0,0,0,0,0,0,252,63,224,63,224,7,227,255,199,255,128,63,63,254,127,254,1,255,255,255,255,248,15,255,255,255,255,192,127,225,255,225,255,3,254,7,254,7,248,31,224,31,224,31,192,255,0,254,0,254,7,240,7,240,7,240,63,128,63,128,63,129,252,1,252,1,252,15,224,15,224,15,224,127,0,127,0,127,3,248,3,248,3,248,31,192,31,192,31,192,254,0,254,0,254,7,240,7,240,7,240,63,128,63,128,63,129,252,1,252,1,252,15,224,15,224,15,224,127,0,127,0,127,3,248,3,248,3,248,31,192,31,192,31,192,254,0,254,0,254,7,240,7,240,7,240,0,0,0,0,126,31,248,1,248,255,240,7,231,255,224,31,255,255,192,127,255,255,1,255,131,254,7,252,7,248,31,224,15,224,127,0,63,129,252,0,254,7,240,3,248,31,192,15,224,127,0,63,129,252,0,254,7,240,3,248,31,192,15,224,127,0,63,129,252,0,254,7,240,3,248,31,192,15,224,127,0,63,129,252,0,254,7,240,3,248,31,192,15,224,127,0,63,129,252,0,254,0,0,0,0,0,0,255,248,0,7,255,252,0,31,255,252,0,127,255,252,1,255,255,252,3,252,7,248,15,240,7,248,31,192,7,240,127,128,15,240,254,0,15,225,252,0,31,195,248,0,63,135,240,0,127,15,224,0,254,31,192,1,252,63,128,3,248,127,0,7,240,255,0,31,224,254,0,63,129,254,0,255,1,254,3,252,3,255,255,248,3,255,255,224,3,255,255,128,3,255,254,0,1,255,240,0,0,0,0,0,0,0,0,0,126,31,240,1,249,255,240,7,239,255,224,31,255,255,192,127,255,255,1,255,131,254,7,252,7,248,31,224,15,240,127,128,31,193,252,0,127,7,240,1,252,31,192,7,240,127,0,31,193,252,0,127,7,240,1,252,31,192,7,240,127,0,31,193,254,0,127,7,248,3,248,31,240,15,224,127,224,255,129,255,255,252,7,255,255,240,31,255,255,128,127,127,252,1,252,127,192,7,240,0,0,31,192,0,0,127,0,0,1,252,0,0,7,240,0,0,31,192,0,0,127,0,0,1,252,0,0,7,240,0,0,0,0,0,0,3,254,31,128,63,254,126,1,255,253,248,15,255,255,224,63,255,255,129,255,7,254,7,248,15,248,31,192,31,224,255,0,127,131,248,0,254,15,224,3,248,63,128,15,224,254,0,63,131,248,0,254,15,224,3,248,63,128,15,224,254,0,63,131,252,1,254,7,240,7,248,31,224,63,224,127,193,255,128,255,255,254,3,255,255,248,7,255,239,224,15,255,63,128,15,248,254,0,0,3,248,0,0,15,224,0,0,63,128,0,0,254,0,0,3,248,0,0,15,224,0,0,63,128,0,0,254,0,0,3,248,0,0,3,240,252,126,127,143,207,241,251,254,63,255,199,255,248,255,254,31,240,3,252,0,127,128,15,224,1,252,0,63,128,7,240,0,254,0,31,192,3,248,0,127,0,15,224,1,252,0,63,128,7,240,0,254,0,31,192,3,248,0,127,0,0,0,0,0,0,15,255,0,3,255,252,0,127,255,224,15,255,255,0,255,255,240,31,224,127,129,252,3,248,31,192,63,129,254,0,0,31,248,0,0,255,248,0,7,255,248,0,63,255,224,1,255,255,0,1,255,248,0,3,255,192,0,7,252,0,0,31,195,248,1,252,63,192,31,195,254,3,252,31,255,255,129,255,255,248,15,255,255,0,63,255,224,0,255,248,0,0,0,0,7,224,3,240,1,248,0,252,0,126,0,63,0,63,128,31,192,127,255,63,255,159,255,207,255,231,255,240,127,0,63,128,31,192,15,224,7,240,3,248,1,252,0,254,0,127,0,63,128,31,192,15,224,7,240,3,248,1,252,0,255,0,127,248,63,252,15,254,3,255,0,255,128,0,0,31,192,15,224,127,0,63,129,252,0,254,7,240,3,248,31,192,15,224,127,0,63,129,252,0,254,7,240,3,248,31,192,15,224,127,0,63,129,252,0,254,7,240,3,248,31,192,15,224,127,0,63,129,252,0,254,7,240,3,248,31,192,15,224,127,0,63,129,252,1,254,7,248,15,248,31,240,127,224,63,255,255,128,255,255,254,1,255,249,248,3,255,199,224,7,254,31,128,0,0,0,0,255,0,15,231,240,0,254,127,0,31,195,248,1,252,63,128,31,131,248,3,248,31,192,63,129,252,3,240,31,224,127,0,254,7,240,15,224,254,0,127,15,224,7,240,252,0,127,15,192,3,249,252,0,63,159,128,3,249,248,0,31,191,128,1,255,240,0,15,255,0,0,255,224,0,15,254,0,0,127,224,0,7,252,0,0,127,192,0,3,248,0,255,0,255,0,127,127,0,255,0,126,127,0,255,0,254,127,0,255,128,254,63,129,255,128,252,63,129,255,128,252,63,129,255,193,252,63,129,255,193,248,31,195,255,193,248,31,195,255,193,248,31,195,231,227,240,15,195,231,227,240,15,231,231,227,240,15,231,231,227,240,7,231,195,247,224,7,231,195,247,224,7,247,195,247,224,3,255,195,247,192,3,255,129,255,192,3,255,129,255,192,1,255,129,255,128,1,255,129,255,128,1,255,0,255,128,0,255,0,255,0,0,255,0,255,0,0,255,0,255,0,63,192,31,225,254,0,255,7,248,15,240,31,224,127,0,255,7,240,3,252,127,128,15,243,248,0,127,191,128,1,255,252,0,7,255,192,0,31,252,0,0,255,192,0,3,254,0,0,63,240,0,1,255,192,0,31,255,0,1,255,252,0,31,255,224,0,254,127,128,15,225,254,0,255,15,240,7,240,63,192,127,128,255,7,248,7,248,63,128,31,227,252,0,127,128,127,0,7,247,248,0,254,63,128,15,227,252,0,254,31,192,31,193,252,1,252,31,224,31,128,254,3,248,15,224,63,128,127,3,240,7,240,63,0,127,7,240,3,248,126,0,63,135,224,1,248,254,0,31,207,192,1,252,252,0,15,207,192,0,255,248,0,7,255,128,0,127,240,0,7,255,0,0,63,240,0,3,254,0,0,31,224,0,1,254,0,0,31,192,0,3,252,0,0,63,128,0,15,248,0,31,255,0,1,255,240,0,31,254,0,1,255,192,0,31,240,0,0,0,0,0,63,255,255,31,255,255,143,255,255,199,255,255,227,255,255,224,0,63,224,0,31,224,0,31,240,0,15,240,0,15,240,0,15,240,0,15,248,0,7,248,0,7,248,0,7,252,0,3,252,0,3,252,0,3,252,0,3,254,0,1,254,0,1,255,0,1,255,255,249,255,255,252,255,255,254,127,255,255,63,255,255,128,0,31,0,15,240,7,254,0,255,192,63,192,7,224,0,252,0,31,128,3,240,0,126,0,15,192,1,248,0,63,0,7,224,0,252,0,31,128,3,240,0,126,0,15,192,3,248,0,254,0,255,128,31,192,3,248,0,127,192,1,252,0,15,192,1,248,0,63,0,7,224,0,252,0,31,128,3,240,0,126,0,15,192,1,248,0,63,0,7,224,0,252,0,31,128,3,252,0,63,240,7,254,0,127,192,1,240,15,128,124,3,224,31,0,248,7,192,62,1,240,15,128,124,3,224,31,0,248,7,192,62,1,240,15,128,124,3,224,31,0,248,7,192,62,1,240,15,128,124,3,224,31,0,248,7,192,62,1,240,15,128,124,3,224,31,0,248,7,192,62,1,240,15,128,124,3,224,31,0,248,7,192,31,128,1,254,0,31,248,1,255,128,3,252,0,31,192,0,252,0,15,192,0,252,0,15,192,0,252,0,15,192,0,252,0,15,192,0,252,0,15,192,0,252,0,15,192,0,252,0,15,192,0,127,0,3,252,0,15,192,0,252,0,63,192,7,240,0,252,0,15,192,0,252,0,15,192,0,252,0,15,192,0,252,0,15,192,0,252,0,15,192,0,252,0,15,192,0,252,0,15,192,3,252,1,255,128,31,248,1,254,0,31,128,0,0,0,0,0,1,252,0,24,7,255,128,56,15,255,241,248,31,255,255,248,31,255,255,248,31,7,255,240,28,0,255,224,16,0,31,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +static const Glyph f0_50_glyphs[]={{0,10,0,0,35,10,32},{0,15,34,0,1,15,33},{64,23,34,0,1,23,34},{162,29,36,0,0,29,35},{293,28,40,0,-1,28,36},{433,49,36,0,0,49,37},{654,38,36,0,0,38,38},{825,13,34,0,1,13,39},{881,18,45,0,-2,18,40},{983,18,45,0,-2,18,41},{1085,20,34,0,1,20,42},{1170,32,26,0,9,32,43},{1274,15,16,0,28,15,44},{1304,17,17,0,18,17,45},{1341,15,7,0,28,15,46},{1355,15,40,0,-3,15,47},{1430,29,36,0,0,29,48},{1561,30,34,0,1,30,49},{1689,30,35,0,0,30,50},{1821,29,36,0,0,29,51},{1952,30,35,0,0,30,52},{2084,29,35,0,1,29,53},{2211,29,36,0,0,29,54},{2342,29,34,0,1,29,55},{2466,29,36,0,0,29,56},{2597,30,36,0,0,30,57},{2732,17,26,0,9,17,58},{2788,17,35,0,9,17,59},{2863,32,27,0,8,32,60},{2971,32,21,0,14,32,61},{3055,32,27,0,8,32,62},{3163,30,35,0,0,30,63},{3295,49,43,0,0,49,64},{3559,36,34,0,1,36,65},{3712,36,34,0,1,36,66},{3865,36,36,0,0,36,67},{4027,37,34,0,1,37,68},{4185,34,34,0,1,34,69},{4330,32,34,0,1,32,70},{4466,40,36,0,0,40,71},{4646,38,34,0,1,38,72},{4808,15,34,0,1,15,73},{4872,31,35,0,1,31,74},{5008,36,34,0,1,36,75},{5161,30,34,0,1,30,76},{5289,44,34,0,1,44,77},{5476,38,34,0,1,38,78},{5638,39,36,0,0,39,79},{5814,34,34,0,1,34,80},{5959,39,42,0,0,39,81},{6164,37,34,0,1,37,82},{6322,33,36,0,0,33,83},{6471,32,34,0,1,32,84},{6607,38,35,0,1,38,85},{6774,35,34,0,1,35,86},{6923,48,34,0,1,48,87},{7127,35,34,0,1,35,88},{7276,35,34,0,1,35,89},{7425,32,34,0,1,32,90},{7561,17,45,0,-2,17,91},{7657,15,40,0,-3,15,92},{7732,17,45,0,-2,17,93},{7828,32,34,0,1,32,94},{7964,26,8,0,35,26,95},{7990,12,37,0,-2,11,96},{8046,30,28,0,8,30,97},{8151,30,37,0,-1,30,98},{8290,29,28,0,8,29,99},{8392,30,37,0,-1,30,100},{8531,29,28,0,8,29,101},{8633,18,36,0,-1,18,102},{8714,31,41,0,4,31,103},{8873,30,36,0,-1,30,104},{9008,13,36,0,-1,13,105},{9067,15,46,-2,-1,13,106},{9154,28,36,0,-1,28,107},{9280,13,36,0,-1,13,108},{9339,45,27,0,8,45,109},{9491,30,27,0,8,30,110},{9593,31,28,0,8,31,111},{9702,30,36,0,8,30,112},{9837,30,36,0,8,30,113},{9972,19,27,0,8,19,114},{10037,28,28,0,8,28,115},{10135,17,35,0,1,17,116},{10210,30,27,0,9,30,117},{10312,28,26,0,9,27,118},{10403,40,26,0,9,40,119},{10533,29,26,0,9,29,120},{10628,28,36,-1,9,27,121},{10754,25,26,0,9,25,122},{10836,19,45,0,-1,19,123},{10943,13,46,0,-1,13,124},{11018,20,45,0,-1,20,125},{11131,32,20,0,15,32,126}}; +static const uint8_t f0_52_bits[]={15,240,15,240,15,240,15,224,15,224,15,224,15,224,15,224,15,224,15,224,15,224,15,224,7,224,7,224,7,224,7,224,7,224,7,192,7,192,7,192,7,192,7,192,7,192,7,192,7,192,0,0,0,0,0,0,0,0,15,224,15,224,15,224,15,224,15,224,15,224,15,224,31,195,248,31,195,248,31,195,248,31,195,248,31,195,248,31,195,248,31,195,248,31,195,248,31,195,248,31,195,248,31,195,240,15,195,240,15,131,240,15,129,240,15,129,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,1,240,0,63,3,224,0,124,15,192,0,248,31,128,3,240,62,0,7,224,124,0,15,128,248,0,31,3,240,0,62,7,224,0,252,15,128,63,255,255,240,127,255,255,224,255,255,255,193,255,255,255,131,255,255,255,7,255,255,254,0,248,15,128,1,240,63,0,3,224,126,0,15,192,248,0,31,1,240,0,62,7,224,31,255,255,252,63,255,255,248,127,255,255,240,255,255,255,225,255,255,255,195,255,255,255,128,126,7,192,0,252,15,128,1,240,31,0,3,224,126,0,7,192,252,0,31,129,240,0,63,3,224,0,124,7,192,0,248,31,128,1,240,30,0,0,0,7,0,0,0,56,0,0,1,192,0,0,14,0,0,0,112,0,0,3,128,0,3,255,224,0,127,255,192,7,255,255,0,127,255,252,7,255,255,240,63,238,255,129,252,115,252,15,227,143,224,127,28,0,3,254,224,0,31,255,0,0,127,255,0,3,255,255,0,15,255,254,0,31,255,248,0,31,255,224,0,31,255,0,0,239,252,0,7,31,225,252,56,255,15,241,195,248,127,142,63,193,255,115,254,15,255,255,224,63,255,255,0,255,255,240,3,255,254,0,7,255,192,0,1,192,0,0,14,0,0,0,112,0,0,3,128,0,0,28,0,0,0,224,0,0,7,0,0,0,0,0,0,0,0,0,63,224,0,7,192,0,63,254,0,3,224,0,31,255,128,1,248,0,7,255,240,0,124,0,1,240,124,0,62,0,0,248,15,128,31,0,0,62,3,224,15,192,0,15,128,248,3,224,0,3,224,62,1,240,0,0,248,15,128,252,0,0,62,3,224,62,0,0,15,128,248,31,0,0,3,240,126,15,192,0,0,126,63,3,224,0,0,31,255,193,240,0,0,3,255,224,248,0,0,0,127,240,126,0,0,0,7,240,31,0,0,0,0,0,15,128,254,0,0,0,7,224,255,224,0,0,1,240,127,252,0,0,0,248,63,255,128,0,0,124,15,199,224,0,0,31,7,224,252,0,0,15,129,240,31,0,0,7,192,124,7,192,0,3,240,31,1,240,0,0,248,7,192,124,0,0,124,1,240,31,0,0,63,0,124,7,192,0,15,128,31,1,240,0,7,192,3,224,248,0,3,224,0,255,254,0,0,248,0,63,255,128,0,124,0,7,255,192,0,62,0,0,127,192,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,128,0,0,127,255,240,0,1,255,255,248,0,3,255,255,254,0,3,255,255,254,0,7,254,3,255,0,15,248,0,255,128,15,240,0,127,128,15,240,0,127,128,15,224,0,63,128,15,224,0,63,128,15,240,0,0,0,7,240,0,0,0,7,248,0,0,0,3,252,0,0,0,1,255,248,63,252,0,255,248,63,252,0,127,248,63,252,1,255,248,63,252,3,255,248,63,252,7,255,240,63,252,15,248,0,63,128,15,224,0,63,128,31,224,0,63,128,31,224,0,63,128,31,192,0,63,128,31,224,0,127,128,31,224,0,127,128,31,224,0,127,128,15,240,0,255,0,15,254,3,255,0,7,255,255,254,0,3,255,255,252,0,1,255,255,248,0,0,255,255,240,0,0,31,255,128,0,0,0,0,0,0,31,192,254,7,240,63,129,252,15,224,127,3,248,31,192,254,7,240,31,128,248,7,192,62,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,128,3,224,0,252,0,31,0,7,224,1,248,0,63,0,15,192,1,248,0,127,0,15,192,3,248,0,127,0,15,192,3,248,0,127,0,15,224,1,252,0,127,0,15,224,1,252,0,63,128,7,240,0,254,0,31,192,3,248,0,127,0,15,224,1,252,0,31,192,3,248,0,127,0,15,224,0,252,0,31,192,3,248,0,63,0,7,240,0,126,0,15,192,0,252,0,31,128,1,248,0,31,0,3,240,0,62,0,3,224,62,0,3,224,0,126,0,7,192,0,252,0,15,192,1,248,0,31,128,3,240,0,127,0,7,224,0,254,0,31,192,1,248,0,63,128,7,240,0,254,0,31,192,1,252,0,63,128,7,240,0,254,0,31,192,3,248,0,127,0,15,224,1,252,0,63,128,7,240,1,252,0,63,128,7,240,0,254,0,31,128,7,240,0,254,0,31,128,7,240,0,252,0,31,128,7,224,0,252,0,63,0,7,192,1,248,0,62,0,15,128,0,0,120,0,3,192,0,28,0,48,224,129,199,28,31,57,240,255,255,129,255,248,3,255,0,15,224,0,255,192,31,255,131,255,254,31,57,240,113,199,2,14,8,0,112,0,3,192,0,30,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,224,0,0,1,240,0,0,0,248,0,0,0,124,0,0,0,62,0,0,0,31,0,0,0,15,128,0,0,7,192,0,0,3,224,0,0,1,240,0,0,0,248,0,1,255,255,255,0,255,255,255,128,127,255,255,192,63,255,255,224,31,255,255,240,15,255,255,248,0,1,240,0,0,0,248,0,0,0,124,0,0,0,62,0,0,0,31,0,0,0,15,128,0,0,7,192,0,0,3,224,0,0,1,240,0,0,0,248,0,0,15,224,15,224,15,224,15,224,15,224,15,224,15,224,0,224,1,224,1,224,1,224,3,192,15,192,31,128,30,0,8,0,63,254,31,255,15,255,135,255,195,255,225,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,224,15,224,15,224,15,224,15,224,15,224,15,224,0,30,0,62,0,62,0,62,0,124,0,124,0,124,0,124,0,248,0,248,0,248,0,248,1,240,1,240,1,240,1,240,3,224,3,224,3,224,3,224,7,192,7,192,7,192,7,128,15,128,15,128,15,128,15,0,31,0,31,0,31,0,62,0,62,0,62,0,62,0,124,0,124,0,124,0,124,0,248,0,248,0,0,0,0,0,0,255,240,0,7,255,248,0,31,255,248,0,127,255,248,0,255,255,240,3,254,31,240,7,240,15,224,31,224,31,224,63,128,31,192,127,0,63,128,254,0,127,3,252,0,255,7,248,1,254,15,224,1,252,31,192,3,248,63,128,7,240,127,0,15,224,254,0,31,193,252,0,63,131,248,0,127,7,240,0,254,15,224,1,252,31,192,3,248,63,192,15,240,127,128,31,224,127,0,63,128,254,0,127,1,252,0,254,3,252,3,252,3,248,7,240,7,252,63,224,7,255,255,128,15,255,255,0,15,255,252,0,15,255,240,0,7,255,128,0,0,0,0,0,0,0,120,0,0,3,240,0,0,31,224,0,1,255,192,0,127,255,128,0,255,255,0,1,255,254,0,3,255,252,0,7,255,248,0,0,7,240,0,0,15,224,0,0,31,192,0,0,63,128,0,0,127,0,0,0,254,0,0,1,252,0,0,3,248,0,0,7,240,0,0,15,224,0,0,31,192,0,0,63,128,0,0,127,0,0,0,254,0,0,1,252,0,0,3,248,0,0,7,240,0,0,15,224,0,0,31,192,0,0,63,128,0,0,127,0,1,255,255,254,3,255,255,252,7,255,255,248,15,255,255,240,31,255,255,224,63,255,255,192,0,0,0,0,0,127,248,0,3,255,252,0,15,255,252,0,127,255,252,0,255,255,252,3,255,15,248,7,248,7,248,31,224,15,240,63,192,15,224,127,0,31,192,254,0,63,129,252,0,127,1,248,1,254,0,0,3,252,0,0,15,248,0,0,63,224,0,0,255,192,0,3,255,0,0,15,252,0,0,63,240,0,0,255,192,0,7,255,0,0,31,252,0,0,127,240,0,1,255,128,0,7,254,0,0,31,248,0,0,127,192,0,1,255,0,0,3,252,0,0,7,255,255,252,31,255,255,248,63,255,255,240,127,255,255,224,255,255,255,193,255,255,255,128,0,0,0,0,0,255,240,0,7,255,248,0,31,255,248,0,127,255,248,1,255,255,248,7,254,31,248,15,240,15,240,31,224,31,224,63,128,31,192,127,0,63,128,254,0,127,0,0,0,254,0,0,3,252,0,0,7,240,0,0,63,224,0,15,255,128,0,31,252,0,0,63,240,0,0,127,248,0,0,255,248,0,1,255,248,0,0,7,248,0,0,7,248,0,0,7,240,0,0,15,224,254,0,31,193,252,0,63,131,252,0,127,7,248,1,254,7,248,3,252,15,248,31,240,15,255,255,224,31,255,255,128,31,255,254,0,15,255,248,0,7,255,192,0,0,0,0,0,0,15,192,0,0,31,224,0,0,31,224,0,0,31,192,0,0,31,192,0,0,63,192,0,0,63,131,128,0,63,135,128,0,127,7,128,0,127,7,128,0,255,15,128,0,254,15,128,0,254,31,128,1,252,31,128,1,252,63,128,3,248,63,128,3,248,63,128,7,240,63,128,7,224,63,128,15,224,63,128,15,192,63,128,31,128,63,128,31,128,63,128,63,255,255,252,63,255,255,252,63,255,255,252,63,255,255,252,63,255,255,252,63,255,255,252,0,0,63,128,0,0,63,128,0,0,63,128,0,0,63,128,0,0,63,128,0,0,63,128,0,0,63,128,0,0,63,128,7,255,255,192,15,255,255,128,31,255,255,0,63,255,254,0,127,255,252,1,255,255,248,3,248,0,0,7,240,0,0,15,224,0,0,31,192,0,0,63,128,0,0,127,0,0,0,254,6,0,1,252,255,192,3,255,255,192,7,255,255,192,15,255,255,192,31,255,255,192,127,224,255,128,255,0,255,128,28,0,255,0,0,0,254,0,0,1,252,0,0,3,248,0,0,7,240,127,0,15,224,255,0,31,193,254,0,127,129,252,0,255,3,252,3,252,7,254,31,248,7,255,255,224,7,255,255,192,7,255,255,0,7,255,248,0,3,255,192,0,0,0,0,0,0,0,0,0,0,31,252,0,0,127,255,0,0,255,255,128,1,255,255,192,3,255,255,224,7,252,63,224,7,240,15,240,7,240,15,240,15,224,7,240,15,224,7,240,15,224,7,240,15,224,0,0,31,224,0,0,31,193,252,0,31,199,255,0,31,223,255,192,31,255,255,224,31,255,255,240,31,255,255,240,31,248,15,248,31,240,7,248,31,224,7,248,31,224,3,248,31,224,3,248,31,224,3,248,15,224,3,248,15,224,3,248,15,224,3,248,15,240,7,248,7,240,15,240,7,252,31,240,3,255,255,224,3,255,255,224,1,255,255,192,0,127,255,0,0,31,252,0,0,0,0,0,63,255,255,240,255,255,255,195,255,255,255,15,255,255,252,63,255,255,240,255,255,255,128,0,1,252,0,0,15,224,0,0,127,128,0,1,252,0,0,15,224,0,0,127,128,0,1,252,0,0,15,224,0,0,63,128,0,1,252,0,0,7,240,0,0,63,128,0,0,254,0,0,7,240,0,0,31,192,0,0,254,0,0,3,248,0,0,15,224,0,0,127,0,0,1,252,0,0,7,240,0,0,31,192,0,0,254,0,0,3,248,0,0,15,224,0,0,63,128,0,0,254,0,0,3,248,0,0,15,224,0,0,63,128,0,0,0,0,0,0,127,248,0,3,255,252,0,31,255,252,0,127,255,252,1,255,255,252,3,254,15,248,15,248,7,248,31,224,15,240,63,192,15,224,127,0,31,192,254,0,63,129,254,0,127,3,252,1,254,3,252,3,248,7,252,31,240,7,255,255,192,7,255,255,0,7,255,248,0,31,255,248,0,127,255,252,1,255,255,252,7,252,7,248,15,224,7,248,63,192,7,240,127,128,15,224,254,0,31,193,252,0,63,131,252,0,127,7,248,1,254,7,248,7,252,15,252,31,240,31,255,255,224,31,255,255,128,31,255,254,0,15,255,248,0,7,255,192,0,0,0,0,0,0,0,0,0,0,255,240,0,7,255,248,0,31,255,248,0,127,255,248,1,255,255,248,7,254,31,240,15,240,15,240,31,192,15,224,127,128,31,192,255,0,31,193,252,0,63,131,248,0,127,7,248,0,254,15,240,3,252,31,224,7,248,31,224,31,240,63,224,127,224,127,255,255,192,127,255,255,128,127,255,255,0,127,254,254,0,127,249,252,0,63,195,248,0,0,7,240,0,0,15,224,127,0,31,192,254,0,127,1,254,0,254,3,252,1,252,3,252,7,240,7,252,31,224,7,255,255,128,15,255,255,0,15,255,252,0,15,255,224,0,7,255,128,0,0,0,0,0,7,240,3,248,1,252,0,254,0,127,0,63,128,31,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,0,63,128,31,192,15,224,7,240,3,248,1,252,0,7,240,3,248,1,252,0,254,0,127,0,63,128,31,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,0,63,128,31,192,15,224,7,240,3,248,1,252,0,14,0,15,0,7,128,3,192,3,192,7,224,7,224,3,192,0,128,0,0,0,0,0,0,0,0,6,0,0,0,7,128,0,0,7,224,0,0,15,248,0,0,15,254,0,0,31,255,128,0,31,255,192,0,31,255,128,0,63,255,0,0,63,255,0,0,63,254,0,0,15,254,0,0,3,252,0,0,0,252,0,0,0,63,192,0,0,15,254,0,0,3,255,224,0,0,63,255,0,0,3,255,248,0,0,31,255,128,0,1,255,252,0,0,15,255,128,0,0,255,224,0,0,15,248,0,0,0,126,0,0,0,7,128,0,0,0,32,15,255,255,248,7,255,255,252,3,255,255,254,1,255,255,255,0,255,255,255,128,127,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,0,255,255,255,128,127,255,255,192,63,255,255,224,31,255,255,240,15,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,128,0,0,0,120,0,0,0,31,128,0,0,7,252,0,0,1,255,192,0,0,127,254,0,0,15,255,224,0,0,127,254,0,0,3,255,240,0,0,63,255,0,0,1,255,240,0,0,31,252,0,0,0,255,0,0,0,15,192,0,0,15,240,0,0,31,252,0,0,31,255,0,0,63,255,0,0,127,255,0,0,127,254,0,0,255,254,0,0,127,252,0,0,31,252,0,0,7,252,0,0,1,248,0,0,0,120,0,0,0,16,0,0,0,0,0,0,0,0,127,248,0,3,255,252,0,31,255,252,0,127,255,252,0,255,255,252,3,254,15,248,7,248,7,248,31,224,15,240,63,192,31,224,127,128,31,192,254,0,127,129,252,0,255,0,0,3,254,0,0,7,248,0,0,63,240,0,0,255,192,0,3,255,0,0,15,252,0,0,63,240,0,0,127,192,0,1,255,0,0,3,252,0,0,7,240,0,0,15,224,0,0,31,192,0,0,63,128,0,0,0,0,0,0,0,0,0,0,0,0,0,3,248,0,0,7,240,0,0,15,224,0,0,31,192,0,0,63,128,0,0,127,0,0,0,254,0,0,0,0,0,0,0,0,0,0,3,255,254,0,0,0,1,255,255,252,0,0,0,127,255,255,248,0,0,31,255,255,255,192,0,3,254,0,3,255,0,0,255,0,0,7,248,0,31,224,0,0,31,192,1,248,0,0,0,252,0,63,0,0,0,7,224,7,224,3,252,0,63,0,124,0,255,248,1,240,15,192,63,255,192,31,128,248,7,255,254,0,248,31,128,127,255,224,15,129,240,15,240,255,0,248,31,0,252,7,240,7,195,240,15,192,63,0,124,62,0,252,3,240,7,195,224,0,0,63,0,124,62,0,0,3,240,7,195,224,0,15,255,0,124,62,0,15,255,240,7,195,224,3,255,255,0,124,62,0,127,255,240,7,195,224,15,248,63,0,124,62,0,252,3,240,15,131,224,31,128,63,0,248,62,1,248,3,240,15,129,240,31,128,127,1,240,31,1,248,15,240,63,1,240,31,193,255,135,224,31,129,255,255,255,254,0,248,15,255,255,255,192,15,192,127,249,255,240,0,124,3,255,15,254,0,7,224,15,192,63,0,0,63,0,0,0,0,0,1,252,0,0,0,0,0,15,240,0,0,0,0,0,127,224,3,192,0,0,3,255,255,252,0,0,0,15,255,255,192,0,0,0,63,255,252,0,0,0,0,63,252,0,0,0,0,3,255,0,0,0,31,252,0,0,0,127,248,0,0,1,255,224,0,0,15,255,128,0,0,63,255,0,0,0,255,252,0,0,7,247,240,0,0,31,223,224,0,0,255,63,128,0,3,248,255,0,0,15,227,252,0,0,127,143,240,0,1,252,31,224,0,7,240,127,128,0,63,192,255,0,0,254,3,252,0,7,248,15,240,0,31,224,31,224,0,127,0,127,128,3,252,1,254,0,15,240,3,252,0,127,128,15,240,1,255,255,255,224,7,255,255,255,128,63,255,255,254,0,255,255,255,252,3,255,255,255,240,31,255,255,255,192,127,128,0,127,131,252,0,1,254,15,240,0,7,252,63,192,0,15,241,254,0,0,63,199,248,0,0,255,159,192,0,1,254,15,255,255,240,0,127,255,255,224,3,255,255,255,128,31,255,255,254,0,255,255,255,248,7,255,255,255,192,63,128,1,255,1,252,0,7,248,15,224,0,63,192,127,0,0,254,3,248,0,7,240,31,192,0,127,128,254,0,3,248,7,240,0,31,192,63,128,3,252,1,255,255,255,224,15,255,255,252,0,127,255,255,192,3,255,255,255,128,31,255,255,254,0,255,255,255,248,7,240,0,31,224,63,128,0,127,1,252,0,3,252,15,224,0,15,224,127,0,0,127,3,248,0,7,248,31,192,0,63,192,254,0,1,254,7,240,0,63,240,63,255,255,255,1,255,255,255,248,15,255,255,255,128,127,255,255,248,3,255,255,255,0,31,255,255,224,0,0,0,0,0,0,0,31,255,0,0,3,255,255,0,0,63,255,255,0,1,255,255,254,0,15,255,255,252,0,127,255,255,248,1,255,0,63,240,15,248,0,127,192,63,192,0,255,129,254,0,1,254,7,248,0,3,248,31,192,0,15,224,255,0,0,63,131,252,0,0,0,15,240,0,0,0,63,128,0,0,0,254,0,0,0,3,248,0,0,0,15,224,0,0,0,63,128,0,0,0,254,0,0,0,3,252,0,0,0,15,240,0,0,0,63,192,0,15,224,127,0,0,63,129,254,0,0,254,7,248,0,7,248,15,240,0,63,224,63,224,0,255,0,127,192,15,252,1,255,255,255,224,3,255,255,255,0,7,255,255,252,0,15,255,255,192,0,15,255,254,0,0,15,255,192,0,0,0,0,0,0,15,255,255,0,0,63,255,255,128,0,255,255,255,128,3,255,255,255,0,15,255,255,254,0,63,255,255,252,0,254,0,63,248,3,248,0,63,240,15,224,0,127,192,63,128,0,255,128,254,0,1,254,3,248,0,7,248,15,224,0,31,224,63,128,0,63,192,254,0,0,255,3,248,0,3,252,15,224,0,15,240,63,128,0,63,192,254,0,0,255,3,248,0,3,252,15,224,0,15,240,63,128,0,63,192,254,0,0,255,3,248,0,7,248,15,224,0,31,224,63,128,0,127,128,254,0,3,254,3,248,0,31,240,15,224,0,255,192,63,128,15,254,0,255,255,255,240,3,255,255,255,128,15,255,255,252,0,63,255,255,224,0,255,255,254,0,3,255,255,192,0,15,255,255,255,128,255,255,255,248,15,255,255,255,128,255,255,255,248,15,255,255,255,128,255,255,255,248,15,224,0,0,0,254,0,0,0,15,224,0,0,0,254,0,0,0,15,224,0,0,0,254,0,0,0,15,224,0,0,0,254,0,0,0,15,224,0,0,0,255,255,255,192,15,255,255,252,0,255,255,255,192,15,255,255,252,0,255,255,255,192,15,255,255,252,0,254,0,0,0,15,224,0,0,0,254,0,0,0,15,224,0,0,0,254,0,0,0,15,224,0,0,0,254,0,0,0,15,224,0,0,0,254,0,0,0,15,255,255,255,128,255,255,255,248,15,255,255,255,128,255,255,255,248,15,255,255,255,128,255,255,255,248,15,255,255,254,7,255,255,255,3,255,255,255,129,255,255,255,192,255,255,255,224,127,255,255,240,63,128,0,0,31,192,0,0,15,224,0,0,7,240,0,0,3,248,0,0,1,252,0,0,0,254,0,0,0,127,0,0,0,63,128,0,0,31,192,0,0,15,255,255,248,7,255,255,252,3,255,255,254,1,255,255,255,0,255,255,255,128,127,255,255,192,63,128,0,0,31,192,0,0,15,224,0,0,7,240,0,0,3,248,0,0,1,252,0,0,0,254,0,0,0,127,0,0,0,63,128,0,0,31,192,0,0,15,224,0,0,7,240,0,0,3,248,0,0,1,252,0,0,0,0,0,0,0,0,0,0,255,254,0,0,0,255,255,224,0,0,255,255,254,0,0,255,255,255,192,0,127,255,255,248,0,31,255,255,255,0,15,252,0,127,224,7,252,0,15,248,1,254,0,0,255,0,255,0,0,63,192,63,192,0,7,240,15,224,0,1,252,3,248,0,0,127,1,254,0,0,0,0,127,128,0,0,0,31,192,0,0,0,7,240,0,0,0,1,252,0,255,255,192,127,0,63,255,240,31,192,15,255,252,7,240,3,255,255,1,254,0,255,255,192,127,128,63,255,240,31,224,0,1,252,3,248,0,0,127,0,255,0,0,63,192,63,192,0,15,240,7,248,0,7,252,1,255,0,3,255,0,63,240,3,255,192,15,255,255,255,240,1,255,255,255,252,0,63,255,255,255,0,7,255,255,231,192,0,127,255,241,240,0,3,255,240,60,0,0,0,0,0,0,15,224,0,15,224,31,192,0,31,192,63,128,0,63,128,127,0,0,127,0,254,0,0,254,1,252,0,1,252,3,248,0,3,248,7,240,0,7,240,15,224,0,15,224,31,192,0,31,192,63,128,0,63,128,127,0,0,127,0,254,0,0,254,1,252,0,1,252,3,255,255,255,248,7,255,255,255,240,15,255,255,255,224,31,255,255,255,192,63,255,255,255,128,127,255,255,255,0,255,255,255,254,1,252,0,1,252,3,248,0,3,248,7,240,0,7,240,15,224,0,15,224,31,192,0,31,192,63,128,0,63,128,127,0,0,127,0,254,0,0,254,1,252,0,1,252,3,248,0,3,248,7,240,0,7,240,15,224,0,15,224,31,192,0,31,192,63,128,0,63,128,127,0,0,127,0,15,224,31,192,63,128,127,0,254,1,252,3,248,7,240,15,224,31,192,63,128,127,0,254,1,252,3,248,7,240,15,224,31,192,63,128,127,0,254,1,252,3,248,7,240,15,224,31,192,63,128,127,0,254,1,252,3,248,7,240,15,224,31,192,63,128,127,0,0,0,15,224,0,0,31,192,0,0,63,128,0,0,127,0,0,0,254,0,0,1,252,0,0,3,248,0,0,7,240,0,0,15,224,0,0,31,192,0,0,63,128,0,0,127,0,0,0,254,0,0,1,252,0,0,3,248,0,0,7,240,0,0,15,224,0,0,31,192,0,0,63,128,0,0,127,0,0,0,254,0,0,1,252,0,0,3,248,0,0,7,240,63,128,15,224,127,0,31,192,254,0,63,129,254,0,255,3,252,1,254,3,252,7,248,7,255,255,240,15,255,255,192,15,255,255,128,15,255,254,0,15,255,248,0,7,255,192,0,0,0,0,0,15,224,0,63,240,127,0,3,255,3,248,0,63,240,31,192,3,255,0,254,0,63,240,7,240,3,255,0,63,128,31,240,1,252,1,255,0,15,224,31,240,0,127,1,255,0,3,248,31,240,0,31,193,255,0,0,254,31,248,0,7,241,255,128,0,63,159,248,0,1,253,255,192,0,15,255,255,0,0,127,255,248,0,3,255,255,224,0,31,255,255,128,0,255,255,252,0,7,255,159,240,0,63,240,127,192,1,255,3,254,0,15,240,15,248,0,127,0,63,224,3,248,1,255,0,31,192,7,252,0,254,0,63,240,7,240,0,255,128,63,128,3,254,1,252,0,31,240,15,224,0,127,192,127,0,1,255,3,248,0,15,248,31,192,0,63,224,15,224,0,0,31,192,0,0,63,128,0,0,127,0,0,0,254,0,0,1,252,0,0,3,248,0,0,7,240,0,0,15,224,0,0,31,192,0,0,63,128,0,0,127,0,0,0,254,0,0,1,252,0,0,3,248,0,0,7,240,0,0,15,224,0,0,31,192,0,0,63,128,0,0,127,0,0,0,254,0,0,1,252,0,0,3,248,0,0,7,240,0,0,15,224,0,0,31,192,0,0,63,128,0,0,127,0,0,0,254,0,0,1,255,255,255,131,255,255,255,7,255,255,254,15,255,255,252,31,255,255,248,63,255,255,240,127,255,255,224,15,255,0,3,255,128,127,248,0,31,252,3,255,224,1,255,224,31,255,0,15,255,0,255,248,0,127,248,7,255,224,7,255,192,63,255,0,63,254,1,255,248,1,255,240,15,255,192,15,191,128,127,127,0,253,252,3,251,248,7,239,224,31,207,192,63,127,0,254,126,1,243,248,7,243,248,31,159,192,63,159,192,252,254,1,252,126,7,231,240,15,227,248,126,63,128,127,31,195,241,252,3,248,254,31,143,224,31,195,240,252,127,0,254,31,207,195,248,7,240,254,126,31,192,63,131,243,240,254,1,252,31,159,7,240,15,224,254,248,63,128,127,7,255,193,252,3,248,31,254,15,224,31,192,255,224,127,0,254,7,255,3,248,7,240,63,248,31,192,63,128,255,192,254,1,252,7,252,7,240,15,224,63,224,63,128,127,0,255,1,252,3,248,7,240,15,224,31,192,63,128,127,0,15,240,0,15,224,31,224,0,31,192,63,224,0,63,128,127,224,0,127,0,255,224,0,254,1,255,192,1,252,3,255,192,3,248,7,255,192,7,240,15,255,128,15,224,31,255,128,31,192,63,255,128,63,128,127,255,128,127,0,255,255,0,254,1,255,255,1,252,3,251,255,3,248,7,243,255,7,240,15,227,254,15,224,31,199,254,31,192,63,135,254,63,128,127,7,254,127,0,254,7,252,254,1,252,15,253,252,3,248,15,255,248,7,240,15,255,240,15,224,15,255,224,31,192,31,255,192,63,128,31,255,128,127,0,31,255,0,254,0,31,254,1,252,0,63,252,3,248,0,63,248,7,240,0,63,240,15,224,0,63,224,31,192,0,127,192,63,128,0,127,128,127,0,0,127,0,0,0,0,0,0,0,3,255,240,0,0,7,255,255,0,0,15,255,255,224,0,31,255,255,248,0,31,255,255,254,0,15,255,255,255,128,15,252,0,255,224,15,248,0,31,240,7,248,0,7,252,7,248,0,1,254,3,252,0,0,127,1,252,0,0,63,192,254,0,0,31,224,255,0,0,7,240,127,128,0,3,248,63,128,0,1,252,31,192,0,0,254,15,224,0,0,127,7,240,0,0,63,131,248,0,0,31,193,252,0,0,15,224,255,0,0,7,240,127,128,0,3,248,31,192,0,3,252,15,224,0,1,254,7,248,0,0,254,3,252,0,0,255,0,255,0,0,255,128,127,192,0,255,128,31,248,1,255,128,7,255,255,255,192,3,255,255,255,192,0,127,255,255,192,0,31,255,255,192,0,3,255,255,128,0,0,127,254,0,0,0,0,0,0,0,15,255,255,192,1,255,255,255,0,63,255,255,240,7,255,255,255,0,255,255,255,240,31,255,255,255,3,248,0,63,224,127,0,3,252,15,224,0,63,193,252,0,7,248,63,128,0,127,7,240,0,15,224,254,0,1,252,31,192,0,127,131,248,0,15,240,127,0,3,252,15,224,0,255,129,255,255,255,240,63,255,255,252,7,255,255,255,0,255,255,255,192,31,255,255,240,3,255,255,240,0,127,0,0,0,15,224,0,0,1,252,0,0,0,63,128,0,0,7,240,0,0,0,254,0,0,0,31,192,0,0,3,248,0,0,0,127,0,0,0,15,224,0,0,1,252,0,0,0,63,128,0,0,7,240,0,0,0,0,0,0,0,0,0,3,255,240,0,0,7,255,255,0,0,15,255,255,224,0,31,255,255,248,0,31,255,255,254,0,15,255,255,255,128,15,252,0,255,224,15,248,0,31,240,7,248,0,7,252,7,248,0,1,254,3,252,0,0,127,1,252,0,0,63,192,254,0,0,31,224,255,0,0,7,240,127,128,0,3,248,63,128,0,1,252,31,192,0,0,254,15,224,0,0,127,7,240,0,0,63,131,248,0,0,31,193,252,0,0,15,224,255,0,0,7,240,127,128,0,3,248,31,192,0,3,252,15,224,0,1,254,7,248,0,0,254,3,252,0,0,255,0,255,0,0,255,128,127,192,0,255,128,31,248,1,255,192,7,255,255,255,192,1,255,255,255,192,0,127,255,255,192,0,31,255,255,192,0,3,255,255,128,0,0,127,255,192,0,0,0,31,240,0,0,0,7,252,0,0,0,1,255,0,0,0,0,127,192,0,0,0,31,240,0,0,0,7,252,0,0,0,1,254,0,15,255,255,224,0,63,255,255,240,0,255,255,255,224,3,255,255,255,192,15,255,255,255,128,63,255,255,255,0,254,0,7,252,3,248,0,15,248,15,224,0,31,224,63,128,0,127,128,254,0,0,254,3,248,0,3,248,15,224,0,15,224,63,128,0,127,128,254,0,1,254,3,248,0,15,240,15,224,0,127,192,63,255,255,254,0,255,255,255,248,3,255,255,255,192,15,255,255,254,0,63,255,255,240,0,255,255,255,192,3,248,0,255,0,15,224,1,254,0,63,128,7,248,0,254,0,15,240,3,248,0,63,192,15,224,0,255,128,63,128,1,254,0,254,0,7,252,3,248,0,15,240,15,224,0,63,224,63,128,0,127,128,254,0,1,254,3,248,0,3,252,0,0,0,0,0,3,255,240,0,3,255,255,128,0,255,255,252,0,127,255,255,192,15,255,255,248,3,255,1,255,128,127,128,15,248,31,224,0,255,3,248,0,31,224,127,128,1,252,15,240,0,0,1,255,0,0,0,63,252,0,0,3,255,240,0,0,127,255,224,0,7,255,255,128,0,127,255,252,0,7,255,255,224,0,63,255,254,0,1,255,255,224,0,7,255,252,0,0,15,255,192,0,0,63,248,0,0,1,255,7,240,0,31,224,254,0,1,252,31,224,0,63,131,252,0,7,240,127,192,1,254,7,252,0,127,192,255,255,255,240,15,255,255,252,0,255,255,255,128,15,255,255,224,0,127,255,240,0,3,255,240,0,0,0,0,0,0,127,255,255,255,63,255,255,255,159,255,255,255,207,255,255,255,231,255,255,255,243,255,255,255,249,255,255,255,252,0,15,224,0,0,7,240,0,0,3,248,0,0,1,252,0,0,0,254,0,0,0,127,0,0,0,63,128,0,0,31,192,0,0,15,224,0,0,7,240,0,0,3,248,0,0,1,252,0,0,0,254,0,0,0,127,0,0,0,63,128,0,0,31,192,0,0,15,224,0,0,7,240,0,0,3,248,0,0,1,252,0,0,0,254,0,0,0,127,0,0,0,63,128,0,0,31,192,0,0,15,224,0,0,7,240,0,0,3,248,0,0,1,252,0,0,0,254,0,0,15,224,0,15,224,31,192,0,31,192,63,128,0,63,128,127,0,0,127,0,254,0,0,254,1,252,0,1,252,3,248,0,3,248,7,240,0,7,240,15,224,0,15,224,31,192,0,31,192,63,128,0,63,128,127,0,0,127,0,254,0,0,254,1,252,0,1,252,3,248,0,3,248,7,240,0,7,240,15,224,0,15,224,31,192,0,31,192,63,128,0,63,128,127,0,0,127,0,254,0,0,254,1,252,0,1,252,3,248,0,3,248,7,240,0,7,240,15,240,0,31,224,31,224,0,63,192,63,192,0,127,128,63,192,1,254,0,127,192,7,252,0,127,192,31,240,0,255,255,255,224,0,255,255,255,128,0,255,255,254,0,0,255,255,248,0,0,255,255,224,0,0,63,254,0,0,0,0,0,0,0,127,128,0,31,231,252,0,1,254,63,192,0,31,227,252,0,3,252,63,224,0,63,193,254,0,3,252,31,224,0,127,129,255,0,7,248,15,240,0,127,0,255,0,15,240,15,248,0,255,0,127,128,15,224,7,248,1,254,0,127,192,31,224,3,252,1,252,0,63,192,63,192,1,254,3,252,0,31,224,63,128,1,254,7,248,0,15,240,127,0,0,255,7,240,0,15,240,255,0,0,127,143,224,0,7,248,254,0,0,127,159,224,0,3,253,252,0,0,63,223,192,0,3,255,252,0,0,31,255,128,0,1,255,248,0,0,15,255,128,0,0,255,240,0,0,15,255,0,0,0,127,224,0,0,7,254,0,0,0,127,224,0,127,128,7,252,0,63,223,224,3,255,0,15,231,248,0,255,224,3,249,255,0,63,248,1,254,63,192,15,254,0,127,15,240,7,255,128,31,195,252,1,255,224,7,240,255,128,127,252,3,252,31,224,31,255,0,254,7,248,7,255,192,63,129,254,3,251,240,15,224,127,128,254,254,7,248,15,240,63,63,129,252,3,252,15,207,224,127,0,255,7,243,248,31,192,63,193,252,127,7,240,7,248,127,31,195,248,1,254,31,135,240,254,0,127,135,225,252,63,128,15,227,248,127,15,224,3,252,254,15,231,240,0,255,63,3,249,252,0,63,207,192,254,127,0,7,247,240,63,159,192,1,253,252,7,239,224,0,127,254,1,255,248,0,31,255,128,127,254,0,3,255,224,31,255,0,0,255,248,3,255,192,0,63,252,0,255,240,0,15,255,0,63,252,0,1,255,192,15,254,0,0,127,240,1,255,128,0,31,252,0,127,224,0,7,254,0,31,248,0,0,255,128,7,252,0,63,240,0,63,224,255,128,3,254,7,254,0,31,224,31,240,1,255,0,127,192,31,240,3,255,0,255,0,15,248,15,248,0,63,224,255,128,1,255,135,248,0,7,252,127,192,0,31,247,252,0,0,127,255,192,0,3,255,254,0,0,15,255,224,0,0,63,254,0,0,1,255,224,0,0,7,255,0,0,0,63,248,0,0,3,255,192,0,0,31,255,0,0,1,255,252,0,0,31,255,224,0,0,255,255,128,0,15,243,254,0,0,255,159,240,0,7,248,127,192,0,127,129,255,0,7,252,15,248,0,63,192,63,224,3,254,0,255,128,63,224,7,254,1,254,0,31,240,31,240,0,127,193,255,0,3,255,31,240,0,15,248,255,128,0,127,224,255,128,0,63,231,252,0,3,252,63,224,0,127,195,254,0,7,248,31,240,0,255,0,255,0,31,240,15,248,1,254,0,127,192,63,192,3,252,3,252,0,63,224,127,128,1,254,15,240,0,15,240,255,0,0,255,159,224,0,7,249,254,0,0,127,255,192,0,3,255,248,0,0,31,255,128,0,1,255,240,0,0,15,254,0,0,0,127,224,0,0,7,252,0,0,0,63,128,0,0,3,248,0,0,0,63,128,0,0,3,248,0,0,0,63,128,0,0,3,248,0,0,0,63,128,0,0,3,248,0,0,0,63,128,0,0,3,248,0,0,0,63,128,0,0,3,248,0,0,0,63,128,0,0,3,248,0,0,0,63,128,0,63,255,255,254,15,255,255,255,131,255,255,255,224,255,255,255,248,63,255,255,252,15,255,255,254,0,0,1,255,128,0,0,255,192,0,0,63,224,0,0,31,248,0,0,15,252,0,0,3,254,0,0,1,255,128,0,0,255,192,0,0,127,224,0,0,31,240,0,0,15,252,0,0,7,254,0,0,1,255,0,0,0,255,192,0,0,127,224,0,0,31,240,0,0,15,252,0,0,7,254,0,0,1,255,0,0,0,255,192,0,0,127,224,0,0,31,240,0,0,15,248,0,0,7,254,0,0,1,255,255,255,240,255,255,255,252,127,255,255,255,31,255,255,255,199,255,255,255,241,255,255,255,252,7,255,129,255,224,127,248,31,254,7,255,129,248,0,126,0,31,128,7,224,1,248,0,126,0,31,128,7,224,1,248,0,126,0,31,128,7,224,1,248,0,126,0,31,128,7,224,1,248,0,126,0,31,128,7,224,1,248,0,126,0,31,128,7,224,1,248,0,126,0,31,128,7,224,1,248,0,126,0,31,128,7,224,1,248,0,126,0,31,128,7,224,1,248,0,127,248,31,254,7,255,129,255,224,127,248,248,0,248,0,124,0,124,0,124,0,124,0,62,0,62,0,62,0,62,0,31,0,31,0,31,0,15,0,15,128,15,128,15,128,7,128,7,192,7,192,7,192,3,224,3,224,3,224,3,224,1,240,1,240,1,240,1,240,0,248,0,248,0,248,0,248,0,124,0,124,0,124,0,124,0,62,0,62,0,62,0,30,127,248,31,254,7,255,129,255,224,127,248,0,126,0,31,128,7,224,1,248,0,126,0,31,128,7,224,1,248,0,126,0,31,128,7,224,1,248,0,126,0,31,128,7,224,1,248,0,126,0,31,128,7,224,1,248,0,126,0,31,128,7,224,1,248,0,126,0,31,128,7,224,1,248,0,126,0,31,128,7,224,1,248,0,126,0,31,128,7,224,1,248,0,126,7,255,129,255,224,127,248,31,254,7,255,128,0,7,240,0,0,3,248,0,0,1,254,0,0,1,255,0,0,0,255,192,0,0,255,224,0,0,127,240,0,0,62,252,0,0,63,62,0,0,31,31,128,0,31,143,192,0,15,195,224,0,15,193,248,0,7,224,124,0,3,224,63,0,3,240,31,128,1,248,7,224,1,248,3,240,0,252,0,248,0,124,0,126,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,7,240,63,129,252,15,192,126,3,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,255,248,0,15,255,252,0,63,255,252,0,255,255,252,1,255,255,248,7,252,15,248,15,240,15,240,31,192,31,224,0,0,31,192,0,0,63,128,0,127,255,0,15,255,254,0,127,255,252,3,255,255,248,7,254,7,240,31,224,15,224,127,128,31,192,255,0,127,129,252,0,255,3,252,3,254,7,248,7,252,15,248,63,252,31,255,255,254,31,255,255,252,63,255,207,248,63,255,31,240,31,248,31,224,0,0,0,0,31,192,0,0,63,128,0,0,127,0,0,0,254,0,0,1,252,0,0,3,248,0,0,7,240,0,0,15,224,0,0,31,192,0,0,63,128,0,0,127,0,0,0,254,31,240,1,252,255,248,3,251,255,248,7,255,255,248,15,255,255,248,31,252,31,240,63,224,31,224,127,192,31,224,255,0,63,193,254,0,63,131,252,0,127,7,240,0,254,15,224,1,252,31,192,3,248,63,128,7,240,127,0,15,224,255,0,31,193,254,0,63,131,252,0,255,7,252,1,254,15,248,7,248,31,252,31,240,63,255,255,224,127,255,255,128,252,255,254,1,248,255,248,3,240,127,192,0,0,0,0,0,0,0,0,0,1,255,224,0,31,255,224,0,255,255,224,7,255,255,192,63,255,255,0,255,131,254,7,248,7,248,31,224,15,240,127,0,63,195,252,0,127,15,240,0,0,63,128,0,0,254,0,0,3,248,0,0,15,224,0,0,63,128,0,0,255,0,0,3,252,0,127,7,240,3,252,31,224,15,240,127,128,127,128,255,131,254,3,255,255,240,7,255,255,192,15,255,254,0,31,255,224,0,31,254,0,0,0,0,0,0,0,7,240,0,0,7,240,0,0,7,240,0,0,7,240,0,0,7,240,0,0,7,240,0,0,7,240,0,0,7,240,0,0,7,240,0,0,7,240,0,0,7,240,0,255,199,240,1,255,247,240,7,255,255,240,7,255,255,240,15,255,255,240,31,248,127,240,31,224,31,240,31,192,15,240,63,192,15,240,63,192,7,240,63,192,7,240,63,128,7,240,63,128,7,240,63,128,7,240,63,128,7,240,63,128,7,240,63,192,7,240,63,192,7,240,63,192,15,240,31,192,15,240,31,224,31,240,31,248,127,240,15,255,255,240,7,255,255,240,7,255,251,240,1,255,243,240,0,255,195,240,0,0,0,0,0,0,0,0,1,255,240,0,31,255,240,0,255,255,224,7,255,255,192,63,255,255,0,255,131,254,7,248,3,248,31,192,15,240,127,0,31,195,252,0,127,15,255,255,252,63,255,255,240,255,255,255,195,255,255,255,15,255,255,252,63,128,0,0,255,0,0,3,252,0,127,7,240,1,252,31,192,15,240,127,128,63,192,255,131,255,3,255,255,248,7,255,255,192,15,255,254,0,31,255,240,0,31,255,0,0,0,0,0,0,127,0,63,224,31,248,15,254,3,255,129,255,224,127,128,31,192,7,240,1,252,0,127,1,255,254,127,255,159,255,231,255,249,255,254,127,255,129,252,0,127,0,31,192,7,240,1,252,0,127,0,31,192,7,240,1,252,0,127,0,31,192,7,240,1,252,0,127,0,31,192,7,240,1,252,0,127,0,31,192,7,240,1,252,0,0,0,0,252,0,0,0,254,0,0,1,254,0,0,1,252,0,0,3,252,0,63,255,248,1,255,255,240,3,255,255,192,7,255,255,192,7,255,255,224,15,240,15,240,15,240,15,240,15,224,7,240,15,224,7,240,15,224,7,240,15,224,7,240,15,240,15,240,7,248,31,240,7,255,255,224,3,255,255,192,1,255,255,128,3,255,255,0,15,255,248,0,31,224,0,0,31,192,0,0,31,192,0,0,31,255,255,0,31,255,255,240,15,255,255,248,7,255,255,252,7,255,255,252,31,255,255,254,63,192,1,254,127,128,1,254,127,0,0,254,127,128,1,254,127,192,3,252,127,255,255,252,63,255,255,248,63,255,255,240,31,255,255,224,7,255,255,128,31,192,0,0,63,128,0,0,127,0,0,0,254,0,0,1,252,0,0,3,248,0,0,7,240,0,0,15,224,0,0,31,192,0,0,63,128,0,0,127,0,0,0,254,31,248,1,252,255,248,3,251,255,248,7,255,255,248,15,255,255,240,31,254,127,240,63,240,63,224,127,192,63,192,255,0,127,129,254,0,127,3,248,0,254,7,240,1,252,15,224,3,248,31,192,7,240,63,128,15,224,127,0,31,192,254,0,63,129,252,0,127,3,248,0,254,7,240,1,252,15,224,3,248,31,192,7,240,63,128,15,224,127,0,31,192,254,0,63,129,252,0,127,3,248,0,254,0,31,192,127,1,252,7,240,31,192,127,0,0,0,0,0,0,0,0,0,7,240,31,192,127,1,252,7,240,31,192,127,1,252,7,240,31,192,127,1,252,7,240,31,192,127,1,252,7,240,31,192,127,1,252,7,240,31,192,127,1,252,7,240,31,192,127,0,7,240,7,240,7,240,7,240,7,240,7,240,0,0,0,0,0,0,0,0,0,0,7,240,7,240,7,240,7,240,7,240,7,240,7,240,7,240,7,240,7,240,7,240,7,240,7,240,7,240,7,240,7,240,7,240,7,240,7,240,7,240,7,240,7,240,7,240,7,240,7,240,7,240,7,240,7,240,7,240,7,240,15,240,255,240,255,240,255,224,255,224,255,128,0,0,31,192,0,0,254,0,0,7,240,0,0,63,128,0,1,252,0,0,15,224,0,0,127,0,0,3,248,0,0,31,192,0,0,254,0,0,7,240,0,0,63,128,31,225,252,1,254,15,224,31,224,127,1,254,3,248,31,224,31,193,255,0,254,31,240,7,240,255,0,63,143,240,1,252,255,0,15,239,240,0,127,255,192,3,255,254,0,31,255,248,0,255,255,192,7,255,255,0,63,243,252,1,255,31,224,15,240,127,128,127,3,252,3,248,15,240,31,192,63,192,254,1,254,7,240,7,248,63,128,63,193,252,0,255,15,224,7,252,31,192,127,1,252,7,240,31,192,127,1,252,7,240,31,192,127,1,252,7,240,31,192,127,1,252,7,240,31,192,127,1,252,7,240,31,192,127,1,252,7,240,31,192,127,1,252,7,240,31,192,127,1,252,7,240,31,192,127,1,252,7,240,31,192,127,0,0,0,0,0,0,0,126,15,240,31,240,1,248,255,240,255,240,7,247,255,231,255,224,31,255,255,191,255,128,127,255,255,255,255,1,255,199,255,199,252,7,252,15,252,15,240,31,224,31,224,31,192,127,128,127,128,127,1,254,1,254,1,252,7,240,7,240,7,240,31,192,31,192,31,192,127,0,127,0,127,1,252,1,252,1,252,7,240,7,240,7,240,31,192,31,192,31,192,127,0,127,0,127,1,252,1,252,1,252,7,240,7,240,7,240,31,192,31,192,31,192,127,0,127,0,127,1,252,1,252,1,252,7,240,7,240,7,240,31,192,31,192,31,192,127,0,127,0,127,1,252,1,252,1,252,7,240,7,240,7,240,0,0,0,0,63,7,254,0,126,63,254,0,254,255,254,1,255,255,254,3,255,255,252,7,255,15,252,15,248,7,248,31,224,15,240,63,192,31,224,127,128,31,192,254,0,63,129,252,0,127,3,248,0,254,7,240,1,252,15,224,3,248,31,192,7,240,63,128,15,224,127,0,31,192,254,0,63,129,252,0,127,3,248,0,254,7,240,1,252,15,224,3,248,31,192,7,240,63,128,15,224,127,0,31,192,254,0,63,128,0,0,0,0,0,63,252,0,1,255,255,128,3,255,255,192,7,255,255,224,15,255,255,240,15,248,31,240,31,240,15,248,31,224,7,248,31,192,3,248,63,192,3,252,63,192,3,252,63,128,1,252,63,128,1,252,63,128,1,252,63,128,1,252,63,128,1,252,63,192,3,252,63,192,3,252,31,192,3,248,31,224,7,248,31,240,15,248,15,248,31,240,15,255,255,240,7,255,255,224,3,255,255,192,1,255,255,128,0,63,252,0,0,0,0,0,0,0,0,0,63,15,252,0,126,63,254,0,252,255,254,1,255,255,254,3,255,255,254,7,255,7,252,15,248,7,248,31,240,7,248,63,192,15,240,127,128,15,224,255,0,31,193,252,0,63,131,248,0,127,7,240,0,254,15,224,1,252,31,192,3,248,63,192,7,240,127,128,15,224,255,0,63,193,255,0,127,131,254,1,254,7,255,7,252,15,255,255,240,31,255,255,224,63,255,255,128,127,63,254,0,254,63,240,1,252,0,0,3,248,0,0,7,240,0,0,15,224,0,0,31,192,0,0,63,128,0,0,127,0,0,0,254,0,0,1,252,0,0,0,0,0,0,0,0,255,195,240,3,255,227,240,7,255,251,240,7,255,255,240,15,255,255,240,31,248,127,240,31,224,31,240,31,192,15,240,63,192,15,240,63,192,7,240,63,192,7,240,63,128,7,240,63,128,7,240,63,128,7,240,63,128,7,240,63,128,7,240,63,192,7,240,63,192,7,240,63,192,15,240,31,192,15,240,31,224,31,240,31,248,63,240,15,255,255,240,7,255,255,240,7,255,247,240,3,255,231,240,0,255,199,240,0,0,7,240,0,0,7,240,0,0,7,240,0,0,7,240,0,0,7,240,0,0,7,240,0,0,7,240,0,0,7,240,0,0,7,240,0,0,1,248,126,31,143,225,249,254,31,255,225,255,254,31,255,225,255,254,31,248,1,255,0,31,224,1,254,0,31,224,1,252,0,31,192,1,252,0,31,192,1,252,0,31,192,1,252,0,31,192,1,252,0,31,192,1,252,0,31,192,1,252,0,31,192,1,252,0,0,0,0,0,7,255,192,0,255,255,128,15,255,254,0,255,255,248,15,255,255,224,127,193,255,3,248,3,248,31,192,31,192,255,0,0,7,252,0,0,63,254,0,0,255,255,0,3,255,255,0,15,255,252,0,15,255,248,0,15,255,192,0,7,255,0,0,7,248,127,0,63,195,252,0,254,31,224,15,240,127,192,255,131,255,255,248,15,255,255,192,63,255,252,0,255,255,128,1,255,240,0,0,0,0,0,1,240,0,252,0,63,0,15,192,3,240,1,252,0,127,0,31,192,7,240,31,255,231,255,249,255,254,127,255,159,255,231,255,248,31,192,7,240,1,252,0,127,0,31,192,7,240,1,252,0,127,0,31,192,7,240,1,252,0,127,0,31,192,7,240,1,252,0,127,128,31,254,7,255,128,255,224,63,248,3,254,0,0,0,31,192,7,240,63,128,15,224,127,0,31,192,254,0,63,129,252,0,127,3,248,0,254,7,240,1,252,15,224,3,248,31,192,7,240,63,128,15,224,127,0,31,192,254,0,63,129,252,0,127,3,248,0,254,7,240,1,252,15,224,3,248,31,192,7,240,63,128,31,224,127,128,63,192,255,0,127,129,254,1,255,3,255,15,254,3,255,255,252,7,255,255,248,7,255,243,240,7,255,199,224,7,254,15,192,0,0,0,0,255,0,7,243,248,0,127,159,224,3,248,127,0,31,195,248,1,254,31,224,15,224,127,0,127,3,248,7,240,31,224,63,128,127,1,252,3,252,31,192,15,224,254,0,127,7,224,3,252,127,0,15,227,248,0,127,31,128,3,249,252,0,15,239,224,0,127,126,0,1,255,240,0,15,255,0,0,127,248,0,1,255,192,0,15,252,0,0,127,224,0,1,255,0,0,15,240,0,255,0,127,128,63,159,192,31,224,15,231,240,7,248,3,249,254,3,255,0,252,63,128,255,192,127,15,224,63,240,31,195,248,15,252,7,224,255,7,255,129,248,31,193,255,224,254,7,240,125,248,63,1,252,31,126,15,192,63,143,207,195,240,15,227,243,241,248,3,248,252,252,126,0,126,62,63,31,128,31,223,135,231,192,7,247,225,251,240,0,253,248,126,252,0,63,252,31,255,0,15,255,3,255,128,1,255,192,255,224,0,127,240,63,248,0,31,248,15,252,0,3,254,1,255,0,0,255,128,127,192,0,63,224,31,224,0,7,240,7,248,0,63,192,15,240,255,128,63,193,254,1,254,3,252,15,240,15,248,63,192,31,225,254,0,63,207,240,0,127,191,128,1,255,254,0,3,255,240,0,7,255,128,0,31,254,0,0,63,240,0,0,255,192,0,7,255,128,0,31,254,0,0,255,252,0,7,255,248,0,63,223,224,0,254,63,192,7,248,127,128,63,193,254,0,255,3,252,7,248,15,248,63,192,31,240,255,0,63,199,248,0,255,128,127,128,7,251,252,0,63,143,224,1,252,127,128,31,225,252,0,254,15,224,7,240,127,128,127,1,252,3,248,15,240,31,192,63,128,252,1,252,15,224,15,240,127,0,63,131,240,1,252,63,128,7,241,252,0,63,143,192,1,252,254,0,7,247,224,0,63,191,0,0,255,248,0,7,255,128,0,63,252,0,0,255,224,0,7,254,0,0,31,240,0,0,255,128,0,7,248,0,0,63,192,0,1,252,0,0,31,224,0,3,254,0,3,255,240,0,31,255,0,0,255,240,0,7,255,0,0,63,224,0,0,0,0,0,0,63,255,255,143,255,255,227,255,255,248,255,255,254,63,255,255,15,255,255,128,0,63,192,0,31,240,0,15,248,0,3,252,0,1,255,0,0,255,128,0,127,192,0,31,224,0,15,248,0,7,252,0,3,254,0,0,255,0,0,127,192,0,63,224,0,15,240,0,7,255,255,227,255,255,249,255,255,254,127,255,255,159,255,255,231,255,255,248,0,15,192,1,254,0,63,240,1,255,128,31,224,0,254,0,7,224,0,63,0,1,248,0,15,192,0,126,0,3,240,0,31,128,0,252,0,7,224,0,63,0,1,248,0,15,192,0,126,0,7,240,0,127,0,31,248,0,255,128,7,192,0,63,192,1,255,128,1,252,0,7,240,0,31,128,0,252,0,7,224,0,63,0,1,248,0,15,192,0,126,0,3,240,0,31,128,0,252,0,7,224,0,63,0,1,248,0,15,224,0,127,128,1,255,128,15,252,0,31,224,0,63,0,15,128,124,3,224,31,0,248,7,192,62,1,240,15,128,124,3,224,31,0,248,7,192,62,1,240,15,128,124,3,224,31,0,248,7,192,62,1,240,15,128,124,3,224,31,0,248,7,192,62,1,240,15,128,124,3,224,31,0,248,7,192,62,1,240,15,128,124,3,224,31,0,248,7,192,62,1,240,31,128,0,255,0,7,252,0,63,240,0,63,192,0,254,0,3,240,0,31,128,0,252,0,7,224,0,63,0,1,248,0,15,192,0,126,0,3,240,0,31,128,0,252,0,7,224,0,63,0,1,252,0,7,240,0,63,240,0,127,128,0,124,0,63,224,3,255,0,31,192,1,252,0,15,192,0,126,0,3,240,0,31,128,0,252,0,7,224,0,63,0,1,248,0,15,192,0,126,0,3,240,0,31,128,0,252,0,15,224,0,255,0,63,240,1,255,0,15,240,0,126,0,0,0,0,0,0,0,126,0,2,0,255,192,3,1,255,248,7,129,255,255,255,192,255,255,255,224,127,255,255,240,60,3,255,240,24,0,127,224,8,0,15,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +static const Glyph f0_52_glyphs[]={{0,10,0,0,37,10,32},{0,16,36,0,1,16,33},{72,24,36,0,1,24,34},{180,31,38,0,0,31,35},{328,29,41,0,-1,29,36},{477,50,38,0,0,50,37},{715,40,38,0,0,40,38},{905,13,36,0,1,13,39},{964,19,47,0,-2,19,40},{1076,19,47,0,-2,19,41},{1188,21,36,0,1,21,42},{1283,33,27,0,10,33,43},{1395,16,16,0,30,16,44},{1427,17,17,0,20,17,45},{1464,16,7,0,30,16,46},{1478,16,41,0,-1,16,47},{1560,31,38,0,0,31,48},{1708,31,36,0,1,31,49},{1848,31,37,0,0,31,50},{1992,31,38,0,0,31,51},{2140,32,37,0,0,32,52},{2288,31,37,0,1,31,53},{2432,32,38,0,0,32,54},{2584,30,36,0,1,30,55},{2719,31,38,0,0,31,56},{2867,31,38,0,0,31,57},{3015,17,27,0,10,17,58},{3073,17,36,0,10,17,59},{3150,34,28,0,9,34,60},{3269,33,22,0,15,33,61},{3360,34,28,0,9,34,62},{3479,31,37,0,0,31,63},{3623,52,45,0,0,52,64},{3916,38,36,0,1,38,65},{4087,37,36,0,1,37,66},{4254,38,38,0,0,38,67},{4435,38,36,0,1,38,68},{4606,36,36,0,1,36,69},{4768,33,36,0,1,33,70},{4917,42,38,0,0,42,71},{5117,39,36,0,1,39,72},{5293,15,36,0,1,15,73},{5361,31,37,0,1,31,74},{5505,37,36,0,1,37,75},{5672,31,36,0,1,31,76},{5812,45,36,0,1,45,77},{6015,39,36,0,1,39,78},{6191,41,38,0,0,41,79},{6386,35,36,0,1,35,80},{6544,41,44,0,0,41,81},{6770,38,36,0,1,38,82},{6941,35,38,0,0,35,83},{7108,33,36,0,1,33,84},{7257,39,37,0,1,39,85},{7438,36,36,0,1,36,86},{7600,50,36,0,1,50,87},{7825,37,36,0,1,37,88},{7992,36,36,0,1,36,89},{8154,34,36,0,1,34,90},{8307,18,47,0,-2,18,91},{8413,16,41,0,-1,16,92},{8495,18,47,0,-2,18,93},{8601,33,36,0,1,33,94},{8750,27,8,0,37,27,95},{8777,12,38,0,-1,12,96},{8834,31,29,0,9,31,97},{8947,31,39,0,-1,31,98},{9099,30,29,0,9,30,99},{9208,32,39,0,-1,32,100},{9364,30,29,0,9,30,101},{9473,18,38,0,-1,18,102},{9559,32,42,0,5,32,103},{9727,31,38,0,-1,31,104},{9875,14,38,0,-1,14,105},{9942,16,48,-2,-1,14,106},{10038,29,38,0,-1,29,107},{10176,14,38,0,-1,14,108},{10243,46,28,0,9,46,109},{10404,31,28,0,9,31,110},{10513,32,29,0,9,32,111},{10629,31,37,0,9,31,112},{10773,32,37,0,9,32,113},{10921,20,28,0,9,20,114},{10991,29,29,0,9,29,115},{11097,18,37,0,1,18,116},{11181,31,28,0,10,31,117},{11290,29,27,0,10,28,118},{11388,42,27,0,10,42,119},{11530,30,27,0,10,30,120},{11632,29,37,-1,10,28,121},{11767,26,27,0,10,26,122},{11855,21,47,0,-1,21,123},{11979,13,48,0,-1,13,124},{12057,21,47,0,-1,21,125},{12181,33,21,0,16,33,126}}; +static const uint8_t f0_56_bits[]={15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,224,7,224,7,224,7,224,7,224,7,224,7,224,7,224,7,224,7,224,7,224,7,224,7,224,7,192,7,192,0,0,0,0,0,0,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,31,227,252,15,241,254,7,248,255,3,252,127,129,254,63,192,255,31,224,127,143,240,63,199,248,31,227,252,15,241,254,7,240,255,1,248,63,0,252,31,128,126,15,192,63,7,224,31,3,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,128,124,0,7,224,126,0,3,224,63,0,3,240,31,128,1,248,15,128,0,252,15,192,0,126,7,224,0,126,3,240,0,63,1,248,0,31,129,248,0,15,192,252,1,255,255,255,224,255,255,255,240,127,255,255,248,63,255,255,252,31,255,255,254,15,255,255,255,0,126,3,240,0,63,1,248,0,31,129,248,0,15,128,252,0,15,192,126,0,7,224,63,0,3,240,31,0,127,255,255,252,63,255,255,254,31,255,255,255,15,255,255,255,135,255,255,255,195,255,255,255,224,15,128,252,0,15,192,126,0,7,224,63,0,3,240,63,0,1,248,31,128,1,248,15,192,0,252,7,224,0,126,3,240,0,63,3,240,0,31,129,248,0,0,3,128,0,0,7,0,0,0,14,0,0,0,28,0,0,0,56,0,0,0,112,0,0,7,252,0,0,127,255,0,3,255,255,128,15,255,255,128,63,255,255,128,127,255,255,1,255,187,255,3,252,115,254,7,248,227,252,15,241,195,240,31,243,128,0,63,255,0,0,63,255,128,0,127,255,224,0,127,255,248,0,63,255,248,0,31,255,248,0,7,255,248,0,3,255,248,0,7,63,240,0,14,63,225,254,28,63,195,252,56,127,135,252,113,255,7,254,231,254,15,255,255,248,15,255,255,240,15,255,255,192,15,255,255,0,7,255,248,0,1,255,128,0,0,112,0,0,0,224,0,0,1,192,0,0,3,128,0,0,7,0,0,0,14,0,0,0,28,0,0,0,0,0,0,0,0,0,0,255,224,0,3,224,0,3,255,224,0,15,128,0,15,255,224,0,63,0,0,63,255,224,0,124,0,0,126,15,192,1,240,0,1,252,31,192,7,192,0,3,240,31,128,31,128,0,7,224,63,0,62,0,0,15,192,126,0,248,0,0,31,128,252,3,240,0,0,63,1,248,15,192,0,0,126,3,240,31,0,0,0,252,7,224,124,0,0,0,252,31,129,248,0,0,1,252,127,3,224,0,0,3,255,254,15,128,0,0,3,255,248,62,0,0,0,3,255,224,252,0,0,0,0,254,1,240,31,192,0,0,0,7,192,255,240,0,0,0,31,135,255,240,0,0,0,62,15,255,224,0,0,0,248,63,143,224,0,0,3,224,126,15,192,0,0,15,193,248,15,192,0,0,31,3,240,31,128,0,0,124,7,224,63,0,0,1,248,15,192,126,0,0,3,224,31,128,252,0,0,15,128,63,1,248,0,0,62,0,126,3,240,0,0,252,0,252,7,224,0,1,240,1,252,31,192,0,7,192,1,248,63,0,0,31,128,3,255,254,0,0,62,0,3,255,248,0,0,248,0,3,255,224,0,3,224,0,3,255,128,0,0,0,0,0,0,0,0,0,0,0,0,0,3,255,248,0,0,7,255,255,128,0,3,255,255,248,0,1,255,255,255,0,0,255,255,255,224,0,127,254,255,252,0,31,240,1,255,0,15,248,0,63,224,3,252,0,7,248,0,255,0,1,254,0,63,192,0,127,128,15,240,0,0,0,1,254,0,0,0,0,127,128,0,0,0,15,248,0,0,0,1,255,252,31,255,0,63,255,7,255,224,3,255,193,255,248,3,255,240,127,254,1,255,252,31,255,128,255,255,7,255,224,127,192,1,254,0,63,224,0,127,128,15,240,0,31,224,7,248,0,7,248,1,254,0,1,254,0,127,128,0,127,128,31,224,0,31,224,7,248,0,7,248,1,255,0,3,254,0,63,192,0,255,0,15,252,0,255,192,3,255,251,255,224,0,127,255,255,248,0,15,255,255,252,0,1,255,255,254,0,0,31,255,254,0,0,0,255,254,0,0,0,0,0,0,0,31,224,127,129,254,7,248,31,224,127,129,254,7,248,31,224,127,129,252,3,240,15,192,63,0,252,3,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,128,1,248,0,31,0,3,240,0,126,0,15,224,0,252,0,31,192,1,248,0,63,128,3,248,0,63,0,7,240,0,127,0,7,224,0,254,0,15,224,0,254,0,15,224,1,252,0,31,192,1,252,0,31,192,1,252,0,31,192,1,252,0,31,192,1,252,0,31,192,1,252,0,31,192,0,254,0,15,224,0,254,0,15,224,0,126,0,7,240,0,127,0,3,240,0,63,128,3,248,0,31,128,1,252,0,15,192,0,126,0,7,224,0,63,0,1,240,0,31,128,0,248,31,0,1,248,0,15,128,0,252,0,7,224,0,126,0,3,240,0,63,128,1,248,0,31,192,1,252,0,15,192,0,254,0,15,224,0,126,0,7,240,0,127,0,7,240,0,127,0,3,248,0,63,128,3,248,0,63,128,3,248,0,63,128,3,248,0,63,128,3,248,0,63,128,3,248,0,63,128,7,240,0,127,0,7,240,0,127,0,7,224,0,254,0,15,224,0,252,0,31,192,1,252,0,31,128,3,248,0,63,0,7,240,0,126,0,15,192,0,248,0,31,128,1,240,0,0,124,0,0,240,0,1,224,0,67,193,1,195,135,3,199,62,15,238,254,15,255,248,7,255,192,3,254,0,7,252,0,63,254,1,255,255,7,247,127,7,142,124,14,28,56,8,120,32,0,240,0,1,224,0,3,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,248,0,0,0,63,0,0,0,7,224,0,0,0,252,0,0,0,31,128,0,0,3,240,0,0,0,126,0,0,0,15,192,0,0,1,248,0,0,0,63,0,0,0,7,224,0,0,0,252,0,0,255,255,255,224,31,255,255,252,3,255,255,255,128,127,255,255,240,15,255,255,254,1,255,255,255,192,0,7,224,0,0,0,252,0,0,0,31,128,0,0,3,240,0,0,0,126,0,0,0,15,192,0,0,1,248,0,0,0,63,0,0,0,7,224,0,0,0,252,0,0,0,31,128,0,7,248,3,252,1,254,0,255,0,127,128,63,192,31,224,15,240,0,120,0,60,0,30,0,30,0,31,0,31,0,31,0,31,0,15,0,0,0,0,63,255,15,255,195,255,240,255,252,63,255,15,255,195,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,248,3,252,1,254,0,255,0,127,128,63,192,31,224,15,240,0,15,0,15,192,7,192,3,224,3,240,1,240,0,248,0,124,0,126,0,62,0,31,0,15,128,15,192,7,192,3,224,1,240,1,248,0,248,0,124,0,126,0,63,0,31,0,15,128,15,192,7,192,3,224,1,240,1,248,0,248,0,124,0,62,0,63,0,31,0,15,128,7,192,7,224,3,224,1,240,1,248,0,248,0,124,0,62,0,63,0,31,0,0,0,0,0,0,0,7,255,128,0,7,255,248,0,3,255,255,0,1,255,255,224,0,255,255,252,0,127,255,255,128,31,240,63,224,7,248,7,248,3,252,0,255,0,255,0,63,192,63,192,15,240,15,224,1,252,7,248,0,127,129,254,0,31,224,127,128,7,248,31,224,1,254,7,248,0,127,129,254,0,31,224,127,128,7,248,31,224,1,254,7,248,0,127,129,254,0,31,224,127,128,7,248,31,224,1,254,7,248,0,127,129,254,0,31,224,63,128,7,240,15,240,3,252,3,252,0,255,0,255,0,63,192,31,224,31,224,7,252,15,248,1,255,255,254,0,63,255,255,0,7,255,255,128,0,255,255,192,0,31,255,224,0,1,255,224,0,0,0,0,0,0,0,30,0,0,0,31,128,0,0,63,224,0,0,63,248,0,0,255,254,0,0,255,255,128,0,63,255,224,0,15,255,248,0,3,255,254,0,0,0,127,128,0,0,31,224,0,0,7,248,0,0,1,254,0,0,0,127,128,0,0,31,224,0,0,7,248,0,0,1,254,0,0,0,127,128,0,0,31,224,0,0,7,248,0,0,1,254,0,0,0,127,128,0,0,31,224,0,0,7,248,0,0,1,254,0,0,0,127,128,0,0,31,224,0,0,7,248,0,0,1,254,0,0,0,127,128,0,0,31,224,0,0,7,248,0,3,255,255,255,0,255,255,255,192,63,255,255,240,15,255,255,252,3,255,255,255,0,255,255,255,192,0,0,0,0,0,7,255,192,0,7,255,252,0,7,255,255,128,3,255,255,240,1,255,255,254,0,255,251,255,192,63,224,31,240,15,240,3,252,7,252,0,127,129,254,0,31,224,127,128,7,248,31,224,1,254,7,248,0,127,128,0,0,63,224,0,0,15,240,0,0,7,252,0,0,3,254,0,0,1,255,128,0,0,255,192,0,0,127,224,0,0,127,240,0,0,63,248,0,0,31,252,0,0,31,254,0,0,15,254,0,0,7,255,0,0,3,255,0,0,1,255,128,0,0,255,128,0,0,127,192,0,0,63,224,0,0,15,255,255,254,3,255,255,255,129,255,255,255,224,127,255,255,248,31,255,255,254,7,255,255,255,129,255,255,255,224,0,0,0,0,0,31,255,0,0,63,255,224,0,127,255,252,0,127,255,255,0,63,255,255,192,63,253,255,224,31,240,31,240,31,240,7,252,15,240,1,254,7,248,0,255,3,252,0,127,128,0,0,63,192,0,0,63,192,0,0,31,224,0,0,63,224,0,7,255,224,0,3,255,192,0,1,255,192,0,0,255,240,0,0,127,252,0,0,63,255,128,0,0,127,192,0,0,15,240,0,0,7,248,0,0,1,254,0,0,0,255,7,248,0,127,131,252,0,63,193,254,0,31,224,255,0,15,240,63,192,15,248,31,240,15,248,15,255,191,252,3,255,255,252,0,255,255,252,0,63,255,252,0,15,255,252,0,0,255,240,0,0,0,0,0,0,15,240,0,0,3,252,0,0,0,255,0,0,0,127,128,0,0,31,224,0,0,7,248,120,0,3,252,30,0,0,255,15,128,0,63,131,224,0,31,224,248,0,7,248,126,0,1,252,31,128,0,255,15,224,0,63,131,248,0,31,225,254,0,7,240,127,128,3,252,31,224,0,254,7,248,0,63,129,254,0,31,192,127,128,7,224,31,224,3,248,7,248,1,252,1,254,0,127,0,127,128,63,128,31,224,15,255,255,255,195,255,255,255,240,255,255,255,252,63,255,255,255,15,255,255,255,195,255,255,255,240,0,0,127,128,0,0,31,224,0,0,7,248,0,0,1,254,0,0,0,127,128,0,0,31,224,0,0,7,248,0,0,1,254,0,7,255,255,240,3,255,255,248,1,255,255,252,0,255,255,254,0,127,255,255,0,63,255,255,128,31,255,255,192,31,192,0,0,15,224,0,0,7,240,0,0,3,248,0,0,1,252,0,0,0,254,0,0,0,127,15,224,0,63,159,254,0,31,255,255,128,15,255,255,224,7,255,255,248,3,255,255,252,1,255,3,255,1,255,0,255,128,31,0,63,224,0,0,15,240,0,0,7,248,0,0,3,252,0,0,1,254,15,240,0,255,7,248,0,127,131,252,0,63,193,255,0,63,192,127,128,31,224,63,224,63,240,15,255,255,240,7,255,255,248,1,255,255,248,0,127,255,248,0,15,255,240,0,1,255,224,0,0,0,0,0,0,0,0,0,0,15,255,0,0,31,255,224,0,31,255,252,0,31,255,255,0,31,255,255,128,15,255,255,224,15,240,31,240,7,248,7,252,7,248,1,254,3,252,0,255,1,252,0,127,128,254,0,0,0,127,0,0,0,127,131,248,0,63,199,255,0,31,239,255,224,15,255,255,248,7,255,255,254,3,255,255,255,1,255,224,255,192,255,192,63,224,127,192,15,248,63,224,7,252,31,240,1,254,15,240,0,255,7,248,0,127,131,252,0,63,192,255,0,31,224,127,128,15,240,63,192,15,240,15,240,7,248,7,252,7,252,3,255,255,252,0,255,255,254,0,63,255,254,0,15,255,254,0,3,255,252,0,0,127,248,0,0,0,0,0,31,255,255,255,7,255,255,255,193,255,255,255,240,127,255,255,252,31,255,255,255,7,255,255,255,129,255,255,255,192,0,0,15,240,0,0,7,248,0,0,3,252,0,0,1,254,0,0,0,127,0,0,0,63,192,0,0,31,224,0,0,7,240,0,0,3,252,0,0,1,254,0,0,0,127,0,0,0,63,192,0,0,15,224,0,0,7,248,0,0,1,252,0,0,0,255,0,0,0,63,192,0,0,15,224,0,0,7,248,0,0,1,252,0,0,0,127,0,0,0,63,192,0,0,15,240,0,0,3,248,0,0,0,254,0,0,0,127,128,0,0,31,224,0,0,7,248,0,0,1,254,0,0,0,127,128,0,0,31,224,0,0,0,0,0,0,0,31,255,0,0,63,255,224,0,127,255,248,0,127,255,255,0,63,255,255,128,63,253,255,224,31,240,31,240,31,240,7,252,15,240,1,254,7,248,0,255,3,252,0,127,129,254,0,63,192,255,0,63,224,63,192,31,224,31,248,63,240,7,255,255,240,1,255,255,240,0,127,255,240,0,63,255,248,0,63,255,254,0,127,255,255,128,63,224,127,224,63,224,15,248,31,224,3,252,31,224,0,255,15,240,0,127,135,248,0,63,195,252,0,31,225,254,0,15,240,255,128,15,248,127,192,7,248,31,240,7,252,15,255,191,254,3,255,255,254,0,255,255,254,0,63,255,254,0,15,255,254,0,0,255,248,0,0,0,0,0,0,0,0,0,0,31,254,0,0,63,255,192,0,127,255,240,0,127,255,252,0,127,255,255,0,63,255,255,192,63,224,31,224,31,224,7,240,31,240,3,252,15,240,0,254,7,248,0,127,3,252,0,63,193,254,0,31,224,255,0,15,240,127,128,15,248,63,224,7,252,15,248,7,254,7,254,7,255,1,255,255,255,128,255,255,255,192,63,255,255,224,15,255,239,240,3,255,231,248,0,63,131,252,0,0,1,254,0,0,0,254,3,252,0,127,1,254,0,63,128,255,0,63,192,127,128,31,224,31,224,31,224,15,248,15,240,7,255,191,240,1,255,255,248,0,127,255,248,0,31,255,248,0,7,255,248,0,0,255,240,0,0,0,0,0,7,248,1,254,0,127,128,31,224,7,248,1,254,0,127,128,31,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,254,0,127,128,31,224,7,248,1,254,0,127,128,31,224,7,248,0,3,252,0,127,128,15,240,1,254,0,63,192,7,248,0,255,0,31,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,248,0,255,0,31,224,3,252,0,127,128,15,240,1,254,0,63,192,0,120,0,15,0,3,224,0,120,0,31,0,7,192,1,240,0,124,0,15,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,30,0,0,0,7,224,0,0,3,254,0,0,0,255,224,0,0,127,254,0,0,31,255,224,0,7,255,240,0,3,255,252,0,0,255,254,0,0,127,255,128,0,15,255,192,0,0,255,224,0,0,15,248,0,0,0,254,0,0,0,15,248,0,0,0,255,224,0,0,15,255,192,0,0,127,255,128,0,0,255,254,0,0,3,255,252,0,0,7,255,240,0,0,31,255,224,0,0,127,254,0,0,0,255,224,0,0,3,254,0,0,0,7,224,0,0,0,30,0,0,0,0,32,15,255,255,254,1,255,255,255,192,63,255,255,248,7,255,255,255,0,255,255,255,224,31,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,224,31,255,255,252,3,255,255,255,128,127,255,255,240,15,255,255,254,1,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,7,128,0,0,0,126,0,0,0,7,252,0,0,0,127,240,0,0,7,255,224,0,0,127,255,128,0,0,255,254,0,0,3,255,252,0,0,7,255,240,0,0,31,255,224,0,0,63,255,0,0,0,127,240,0,0,1,255,0,0,0,7,240,0,0,1,255,0,0,0,127,240,0,0,63,255,0,0,31,255,224,0,7,255,240,0,3,255,252,0,0,255,254,0,0,127,255,128,0,7,255,224,0,0,127,240,0,0,7,252,0,0,0,126,0,0,0,7,128,0,0,0,64,0,0,0,0,0,0,0,0,7,255,128,0,15,255,248,0,7,255,255,128,3,255,255,240,1,255,255,254,0,255,247,255,128,63,224,63,240,31,240,7,252,7,248,0,255,1,254,0,63,192,127,128,15,240,31,224,3,252,0,0,1,255,0,0,0,127,128,0,0,127,224,0,0,63,240,0,0,31,248,0,0,15,252,0,0,7,254,0,0,3,255,0,0,0,255,128,0,0,127,128,0,0,31,224,0,0,7,240,0,0,1,252,0,0,0,127,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,224,0,0,7,248,0,0,1,254,0,0,0,127,128,0,0,31,224,0,0,7,248,0,0,1,254,0,0,0,127,128,0,0,0,0,0,0,0,0,0,0,7,255,254,0,0,0,0,31,255,255,240,0,0,0,63,255,255,254,0,0,0,127,255,255,255,192,0,0,255,192,0,63,240,0,0,255,0,0,3,252,0,0,254,0,0,0,127,0,0,254,0,0,0,31,192,0,254,0,0,0,3,240,0,126,0,15,240,0,248,0,126,0,63,254,0,126,0,126,0,127,255,192,31,0,62,0,127,255,240,7,192,31,0,63,255,248,3,224,31,0,63,195,254,1,240,15,128,31,128,255,0,252,7,192,15,192,63,128,62,7,224,7,224,31,192,31,3,224,0,0,15,224,15,129,240,0,0,7,240,7,192,248,0,1,255,248,3,224,124,0,7,255,252,1,240,62,0,15,255,254,0,248,31,0,31,255,255,0,124,15,128,31,255,255,128,62,7,192,15,252,31,192,62,3,224,15,240,15,224,31,1,240,7,240,7,240,15,128,248,3,248,3,248,15,192,62,1,252,3,252,7,192,31,0,254,3,254,7,224,15,128,127,131,255,143,224,7,224,31,255,255,255,224,1,240,15,255,255,255,224,0,252,3,255,231,255,224,0,62,0,255,224,255,224,0,31,128,31,192,63,128,0,7,224,0,0,0,0,0,3,248,0,0,0,0,0,0,255,0,0,0,0,0,0,63,224,0,0,0,0,0,7,254,0,31,0,0,0,1,255,255,255,128,0,0,0,63,255,255,192,0,0,0,3,255,255,192,0,0,0,0,31,254,0,0,0,0,1,255,192,0,0,0,255,224,0,0,0,255,240,0,0,0,127,252,0,0,0,127,254,0,0,0,63,255,128,0,0,31,255,192,0,0,31,255,224,0,0,15,247,248,0,0,15,243,252,0,0,7,249,255,0,0,3,252,127,128,0,3,252,63,192,0,1,254,31,240,0,1,255,7,248,0,0,255,3,252,0,0,127,129,255,0,0,127,128,127,128,0,63,192,63,224,0,31,224,31,240,0,31,224,7,248,0,15,240,3,254,0,15,248,1,255,0,7,248,0,127,192,3,255,255,255,224,3,255,255,255,240,1,255,255,255,252,1,255,255,255,254,0,255,255,255,255,128,127,255,255,255,192,127,255,255,255,224,63,224,0,15,248,63,224,0,7,252,31,240,0,3,254,15,240,0,0,255,143,248,0,0,127,199,252,0,0,31,243,252,0,0,15,248,15,255,255,248,0,15,255,255,255,0,15,255,255,255,128,15,255,255,255,192,15,255,255,255,224,15,255,255,255,224,15,255,255,255,240,15,240,0,63,240,15,240,0,31,240,15,240,0,15,240,15,240,0,15,240,15,240,0,15,240,15,240,0,15,224,15,240,0,31,224,15,240,0,63,192,15,255,255,255,128,15,255,255,255,0,15,255,255,254,0,15,255,255,255,0,15,255,255,255,192,15,255,255,255,224,15,240,0,63,240,15,240,0,15,248,15,240,0,15,248,15,240,0,7,252,15,240,0,7,252,15,240,0,7,252,15,240,0,7,252,15,240,0,7,252,15,240,0,15,252,15,240,0,31,252,15,255,255,255,248,15,255,255,255,248,15,255,255,255,240,15,255,255,255,224,15,255,255,255,192,15,255,255,255,128,15,255,255,252,0,0,0,0,0,0,0,0,255,252,0,0,3,255,255,128,0,7,255,255,240,0,7,255,255,252,0,7,255,255,255,0,7,255,255,255,192,7,255,255,255,240,3,255,128,31,252,3,255,128,7,254,1,255,128,1,255,1,255,128,0,127,192,255,128,0,31,224,127,192,0,15,240,127,224,0,7,248,63,224,0,1,248,31,240,0,0,0,15,248,0,0,0,7,252,0,0,0,3,254,0,0,0,1,255,0,0,0,0,255,128,0,0,0,127,192,0,0,0,63,224,0,0,0,31,240,0,0,0,15,252,0,0,255,3,254,0,0,127,129,255,0,0,63,192,255,192,0,63,224,63,224,0,63,224,31,248,0,31,240,15,255,0,63,248,3,255,255,255,248,0,255,255,255,248,0,63,255,255,252,0,15,255,255,252,0,3,255,255,248,0,0,127,255,240,0,0,15,255,224,0,0,0,0,0,0,15,255,255,128,0,3,255,255,254,0,0,255,255,255,224,0,63,255,255,254,0,15,255,255,255,192,3,255,255,255,248,0,255,255,255,255,0,63,192,7,255,192,15,240,0,63,248,3,252,0,7,254,0,255,0,0,255,192,63,192,0,63,240,15,240,0,7,252,3,252,0,1,255,0,255,0,0,63,224,63,192,0,15,248,15,240,0,3,254,3,252,0,0,255,128,255,0,0,63,224,63,192,0,15,248,15,240,0,3,254,3,252,0,0,255,128,255,0,0,63,224,63,192,0,15,248,15,240,0,7,252,3,252,0,1,255,0,255,0,0,127,192,63,192,0,63,240,15,240,0,31,248,3,252,0,15,254,0,255,0,31,255,0,63,255,255,255,192,15,255,255,255,224,3,255,255,255,240,0,255,255,255,248,0,63,255,255,248,0,15,255,255,248,0,3,255,255,224,0,0,15,255,255,255,224,63,255,255,255,128,255,255,255,254,3,255,255,255,248,15,255,255,255,224,63,255,255,255,128,255,255,255,254,3,252,0,0,0,15,240,0,0,0,63,192,0,0,0,255,0,0,0,3,252,0,0,0,15,240,0,0,0,63,192,0,0,0,255,0,0,0,3,255,255,255,192,15,255,255,255,0,63,255,255,252,0,255,255,255,240,3,255,255,255,192,15,255,255,255,0,63,255,255,252,0,255,0,0,0,3,252,0,0,0,15,240,0,0,0,63,192,0,0,0,255,0,0,0,3,252,0,0,0,15,240,0,0,0,63,192,0,0,0,255,0,0,0,3,255,255,255,248,15,255,255,255,224,63,255,255,255,128,255,255,255,254,3,255,255,255,248,15,255,255,255,224,63,255,255,255,128,15,255,255,255,129,255,255,255,240,63,255,255,254,7,255,255,255,192,255,255,255,248,31,255,255,255,3,255,255,255,224,127,128,0,0,15,240,0,0,1,254,0,0,0,63,192,0,0,7,248,0,0,0,255,0,0,0,31,224,0,0,3,252,0,0,0,127,128,0,0,15,255,255,252,1,255,255,255,128,63,255,255,240,7,255,255,254,0,255,255,255,192,31,255,255,248,3,255,255,255,0,127,128,0,0,15,240,0,0,1,254,0,0,0,63,192,0,0,7,248,0,0,0,255,0,0,0,31,224,0,0,3,252,0,0,0,127,128,0,0,15,240,0,0,1,254,0,0,0,63,192,0,0,7,248,0,0,0,255,0,0,0,31,224,0,0,0,0,0,0,0,0,0,0,15,255,224,0,0,3,255,255,224,0,0,63,255,255,192,0,7,255,255,255,128,0,127,255,255,254,0,7,255,255,255,248,0,127,255,255,255,192,3,255,192,7,255,0,63,248,0,15,248,1,255,128,0,63,224,31,248,0,1,255,0,255,128,0,7,248,7,252,0,0,63,192,127,224,0,0,0,3,254,0,0,0,0,31,240,0,0,0,0,255,128,0,0,0,7,252,0,255,255,224,63,224,7,255,255,1,255,0,63,255,248,15,248,1,255,255,192,127,192,15,255,254,3,254,0,127,255,240,31,240,0,0,127,128,255,192,0,3,252,3,254,0,0,31,224,31,240,0,1,255,0,255,192,0,15,248,3,255,0,0,255,192,31,252,0,15,254,0,255,240,1,255,240,3,255,255,255,255,128,15,255,255,255,252,0,63,255,255,255,224,0,255,255,255,191,0,3,255,255,249,248,0,7,255,255,7,192,0,7,255,224,62,0,0,0,0,0,0,15,240,0,3,252,3,252,0,0,255,0,255,0,0,63,192,63,192,0,15,240,15,240,0,3,252,3,252,0,0,255,0,255,0,0,63,192,63,192,0,15,240,15,240,0,3,252,3,252,0,0,255,0,255,0,0,63,192,63,192,0,15,240,15,240,0,3,252,3,252,0,0,255,0,255,0,0,63,192,63,255,255,255,240,15,255,255,255,252,3,255,255,255,255,0,255,255,255,255,192,63,255,255,255,240,15,255,255,255,252,3,255,255,255,255,0,255,0,0,63,192,63,192,0,15,240,15,240,0,3,252,3,252,0,0,255,0,255,0,0,63,192,63,192,0,15,240,15,240,0,3,252,3,252,0,0,255,0,255,0,0,63,192,63,192,0,15,240,15,240,0,3,252,3,252,0,0,255,0,255,0,0,63,192,63,192,0,15,240,15,240,0,3,252,3,252,0,0,255,0,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,0,0,3,252,0,0,0,255,0,0,0,63,192,0,0,15,240,0,0,3,252,0,0,0,255,0,0,0,63,192,0,0,15,240,0,0,3,252,0,0,0,255,0,0,0,63,192,0,0,15,240,0,0,3,252,0,0,0,255,0,0,0,63,192,0,0,15,240,0,0,3,252,0,0,0,255,0,0,0,63,192,0,0,15,240,0,0,3,252,0,0,0,255,0,0,0,63,192,0,0,15,240,0,0,3,252,15,240,0,255,3,252,0,63,192,255,0,15,240,63,192,7,252,15,248,1,254,3,255,0,255,128,127,255,255,224,31,255,255,240,3,255,255,252,0,127,255,254,0,15,255,255,0,1,255,255,128,0,31,255,0,0,0,0,0,0,15,240,0,15,252,15,240,0,31,248,15,240,0,63,240,15,240,0,127,224,15,240,0,255,192,15,240,1,255,128,15,240,3,255,0,15,240,7,254,0,15,240,15,254,0,15,240,31,252,0,15,240,63,248,0,15,240,127,240,0,15,240,255,224,0,15,240,255,192,0,15,241,255,128,0,15,243,255,0,0,15,247,255,128,0,15,255,255,128,0,15,255,255,192,0,15,255,255,224,0,15,255,255,224,0,15,255,255,240,0,15,255,159,248,0,15,255,15,248,0,15,252,15,252,0,15,248,7,254,0,15,240,3,254,0,15,240,3,255,0,15,240,1,255,128,15,240,1,255,128,15,240,0,255,192,15,240,0,127,224,15,240,0,127,224,15,240,0,63,240,15,240,0,31,248,15,240,0,31,248,15,240,0,15,252,15,240,0,7,254,15,240,0,0,7,248,0,0,3,252,0,0,1,254,0,0,0,255,0,0,0,127,128,0,0,63,192,0,0,31,224,0,0,15,240,0,0,7,248,0,0,3,252,0,0,1,254,0,0,0,255,0,0,0,127,128,0,0,63,192,0,0,31,224,0,0,15,240,0,0,7,248,0,0,3,252,0,0,1,254,0,0,0,255,0,0,0,127,128,0,0,63,192,0,0,31,224,0,0,15,240,0,0,7,248,0,0,3,252,0,0,1,254,0,0,0,255,0,0,0,127,128,0,0,63,192,0,0,31,255,255,254,15,255,255,255,7,255,255,255,131,255,255,255,193,255,255,255,224,255,255,255,240,127,255,255,248,15,255,128,0,255,240,15,255,128,1,255,240,15,255,192,1,255,240,15,255,192,1,255,240,15,255,192,3,255,240,15,255,192,3,255,240,15,255,224,3,255,240,15,255,224,3,255,240,15,255,224,7,255,240,15,247,224,7,239,240,15,247,240,7,239,240,15,247,240,7,239,240,15,247,240,15,239,240,15,247,248,15,207,240,15,243,248,15,207,240,15,243,248,31,207,240,15,243,248,31,207,240,15,241,252,31,143,240,15,241,252,31,143,240,15,241,252,63,143,240,15,241,254,63,15,240,15,240,254,63,15,240,15,240,254,63,15,240,15,240,254,127,15,240,15,240,127,126,15,240,15,240,127,126,15,240,15,240,127,126,15,240,15,240,127,252,15,240,15,240,63,252,15,240,15,240,63,252,15,240,15,240,63,252,15,240,15,240,31,248,15,240,15,240,31,248,15,240,15,240,31,248,15,240,15,240,31,248,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,0,3,252,3,254,0,0,255,0,255,192,0,63,192,63,240,0,15,240,15,254,0,3,252,3,255,192,0,255,0,255,248,0,63,192,63,254,0,15,240,15,255,192,3,252,3,255,248,0,255,0,255,255,0,63,192,63,255,192,15,240,15,255,248,3,252,3,255,255,0,255,0,255,127,224,63,192,63,223,252,15,240,15,243,255,3,252,3,252,127,224,255,0,255,15,252,63,192,63,195,255,143,240,15,240,127,227,252,3,252,15,252,255,0,255,1,255,191,192,63,192,127,239,240,15,240,15,255,252,3,252,1,255,255,0,255,0,63,255,192,63,192,15,255,240,15,240,1,255,252,3,252,0,63,255,0,255,0,7,255,192,63,192,1,255,240,15,240,0,63,252,3,252,0,7,255,0,255,0,0,255,192,63,192,0,63,240,15,240,0,7,252,3,252,0,0,255,0,0,0,0,0,0,0,0,7,255,248,0,0,0,127,255,248,0,0,7,255,255,248,0,0,127,255,255,248,0,3,255,255,255,240,0,31,255,255,255,224,0,255,255,255,255,192,3,255,192,15,255,0,31,252,0,15,254,0,127,224,0,31,248,3,255,0,0,63,240,15,248,0,0,127,192,63,224,0,1,255,0,255,128,0,7,252,7,252,0,0,15,248,31,240,0,0,63,224,127,192,0,0,255,129,255,0,0,3,254,7,252,0,0,15,248,31,240,0,0,63,224,127,192,0,0,255,129,255,0,0,3,254,7,252,0,0,15,248,31,240,0,0,63,224,63,224,0,1,255,0,255,128,0,7,252,3,254,0,0,31,240,15,252,0,0,255,192,31,248,0,7,254,0,127,240,0,63,248,0,255,240,3,255,192,3,255,255,255,255,0,7,255,255,255,248,0,15,255,255,255,192,0,31,255,255,254,0,0,31,255,255,224,0,0,31,255,254,0,0,0,31,255,224,0,0,0,0,0,0,0,15,255,255,240,0,127,255,255,224,3,255,255,255,192,31,255,255,255,0,255,255,255,252,7,255,255,255,224,63,255,255,255,129,254,0,15,252,15,240,0,31,224,127,128,0,255,131,252,0,3,252,31,224,0,31,224,255,0,0,255,7,248,0,7,248,63,192,0,63,193,254,0,3,254,15,240,0,31,224,127,128,3,255,3,255,255,255,248,31,255,255,255,128,255,255,255,252,7,255,255,255,192,63,255,255,252,1,255,255,255,128,15,255,255,224,0,127,128,0,0,3,252,0,0,0,31,224,0,0,0,255,0,0,0,7,248,0,0,0,63,192,0,0,1,254,0,0,0,15,240,0,0,0,127,128,0,0,3,252,0,0,0,31,224,0,0,0,255,0,0,0,7,248,0,0,0,0,0,0,0,0,0,0,7,255,248,0,0,0,127,255,248,0,0,7,255,255,248,0,0,127,255,255,248,0,3,255,255,255,240,0,31,255,255,255,224,0,255,255,255,255,192,3,255,192,15,255,0,31,252,0,15,254,0,127,224,0,31,248,3,255,0,0,63,240,15,248,0,0,127,192,63,224,0,1,255,0,255,128,0,7,252,7,252,0,0,15,248,31,240,0,0,63,224,127,192,0,0,255,129,255,0,0,3,254,7,252,0,0,15,248,31,240,0,0,63,224,127,192,0,0,255,129,255,0,0,3,254,7,252,0,0,15,248,31,240,0,0,63,224,63,224,0,1,255,0,255,128,0,7,252,3,254,0,0,31,240,15,252,0,0,255,192,31,248,0,7,254,0,127,240,0,63,248,0,255,240,3,255,192,3,255,255,255,254,0,7,255,255,255,248,0,15,255,255,255,192,0,31,255,255,252,0,0,31,255,255,224,0,0,63,255,254,0,0,0,31,255,252,0,0,0,0,63,248,0,0,0,0,127,240,0,0,0,0,255,224,0,0,0,1,255,192,0,0,0,3,255,128,0,0,0,7,255,0,0,0,0,15,254,0,15,255,255,252,0,7,255,255,255,128,3,255,255,255,240,1,255,255,255,252,0,255,255,255,255,0,127,255,255,255,128,63,255,255,255,224,31,224,0,63,240,15,240,0,7,252,7,248,0,3,254,3,252,0,0,255,1,254,0,0,127,128,255,0,0,63,192,127,128,0,31,224,63,192,0,15,240,31,224,0,15,248,15,240,0,15,248,7,248,0,15,252,3,255,255,255,252,1,255,255,255,254,0,255,255,255,254,0,127,255,255,254,0,63,255,255,252,0,31,255,255,254,0,15,240,0,255,128,7,248,0,127,192,3,252,0,31,240,1,254,0,15,248,0,255,0,3,254,0,127,128,1,255,0,63,192,0,127,192,31,224,0,63,224,15,240,0,15,248,7,248,0,7,252,3,252,0,1,255,1,254,0,0,255,128,255,0,0,63,224,127,128,0,31,240,0,0,0,0,0,0,63,255,128,0,3,255,255,128,0,63,255,255,128,1,255,255,255,0,15,255,255,254,0,127,254,255,252,1,255,0,63,240,15,248,0,63,224,63,192,0,127,128,255,0,1,254,3,252,0,7,248,15,248,0,0,0,63,248,0,0,0,127,248,0,0,1,255,254,0,0,3,255,255,128,0,7,255,255,192,0,15,255,255,192,0,31,255,255,192,0,31,255,255,128,0,15,255,255,0,0,3,255,254,0,0,0,255,248,0,0,0,127,240,0,0,0,255,193,254,0,1,255,7,248,0,3,252,31,224,0,15,240,127,192,0,63,193,255,0,1,255,3,255,0,15,248,15,255,255,255,224,31,255,255,255,0,63,255,255,252,0,127,255,255,224,0,255,255,255,0,0,255,255,240,0,0,255,254,0,0,0,0,0,0,127,255,255,255,207,255,255,255,249,255,255,255,255,63,255,255,255,231,255,255,255,252,255,255,255,255,159,255,255,255,240,0,31,224,0,0,3,252,0,0,0,127,128,0,0,15,240,0,0,1,254,0,0,0,63,192,0,0,7,248,0,0,0,255,0,0,0,31,224,0,0,3,252,0,0,0,127,128,0,0,15,240,0,0,1,254,0,0,0,63,192,0,0,7,248,0,0,0,255,0,0,0,31,224,0,0,3,252,0,0,0,127,128,0,0,15,240,0,0,1,254,0,0,0,63,192,0,0,7,248,0,0,0,255,0,0,0,31,224,0,0,3,252,0,0,0,127,128,0,0,15,240,0,0,1,254,0,0,0,63,192,0,0,7,248,0,0,15,240,0,3,252,3,252,0,0,255,0,255,0,0,63,192,63,192,0,15,240,15,240,0,3,252,3,252,0,0,255,0,255,0,0,63,192,63,192,0,15,240,15,240,0,3,252,3,252,0,0,255,0,255,0,0,63,192,63,192,0,15,240,15,240,0,3,252,3,252,0,0,255,0,255,0,0,63,192,63,192,0,15,240,15,240,0,3,252,3,252,0,0,255,0,255,0,0,63,192,63,192,0,15,240,15,240,0,3,252,3,252,0,0,255,0,255,0,0,63,192,63,192,0,15,240,15,240,0,3,252,3,252,0,0,255,0,255,0,0,127,192,63,224,0,31,240,7,252,0,15,248,1,255,0,7,254,0,127,240,3,255,128,15,255,255,255,192,3,255,255,255,240,0,127,255,255,248,0,15,255,255,252,0,0,255,255,252,0,0,31,255,254,0,0,0,255,252,0,0,0,0,0,0,0,127,192,0,7,252,255,128,0,15,248,255,128,0,31,225,255,0,0,127,195,254,0,0,255,131,254,0,1,254,7,252,0,7,252,15,248,0,15,240,15,248,0,31,224,31,240,0,127,192,31,224,0,255,0,63,224,1,254,0,127,192,7,252,0,127,128,15,240,0,255,128,31,224,1,255,0,127,192,1,254,0,255,0,3,254,1,254,0,7,252,7,248,0,7,248,15,240,0,15,248,31,224,0,15,240,127,128,0,31,224,255,0,0,63,225,254,0,0,63,199,248,0,0,127,143,240,0,0,255,159,224,0,0,255,127,128,0,1,254,255,0,0,3,255,252,0,0,3,255,248,0,0,7,255,240,0,0,7,255,192,0,0,15,255,128,0,0,31,255,0,0,0,31,252,0,0,0,63,248,0,0,0,127,224,0,0,127,128,3,255,0,7,249,255,0,15,254,0,31,231,252,0,63,248,0,127,159,240,1,255,224,1,254,63,192,7,255,128,15,240,255,128,31,255,0,63,195,254,0,127,252,0,255,15,248,1,255,240,3,248,31,224,15,255,192,31,224,127,192,63,255,0,127,129,255,0,255,254,1,254,3,252,3,251,248,7,240,15,240,31,207,224,63,192,63,224,127,63,128,255,0,255,129,252,255,3,252,1,254,7,243,252,15,224,7,248,63,199,240,127,128,31,240,254,31,193,254,0,127,195,248,127,135,248,0,255,15,225,254,31,192,3,252,63,131,248,255,0,15,249,252,15,227,252,0,63,231,240,63,207,224,0,127,159,192,255,63,128,1,254,127,1,253,254,0,7,253,248,7,247,248,0,15,255,224,31,223,192,0,63,255,128,127,255,0,0,255,254,0,255,252,0,3,255,240,3,255,240,0,7,255,192,15,255,128,0,31,255,0,63,254,0,0,127,252,0,255,248,0,1,255,224,1,255,224,0,3,255,128,7,255,0,0,15,254,0,31,252,0,0,63,248,0,127,240,0,0,255,224,0,255,128,0,63,240,0,15,248,31,248,0,31,248,15,252,0,31,240,15,252,0,63,224,7,254,0,127,224,3,255,0,127,192,3,255,0,255,128,1,255,129,255,128,0,255,193,255,0,0,255,195,254,0,0,127,231,252,0,0,63,247,252,0,0,31,255,248,0,0,31,255,240,0,0,15,255,240,0,0,7,255,224,0,0,7,255,192,0,0,3,255,192,0,0,3,255,192,0,0,7,255,192,0,0,7,255,224,0,0,15,255,240,0,0,31,255,240,0,0,31,255,248,0,0,63,239,252,0,0,127,199,252,0,0,255,199,254,0,0,255,131,255,0,1,255,1,255,0,3,255,1,255,128,3,254,0,255,192,7,252,0,127,224,15,252,0,127,224,15,248,0,63,240,31,240,0,31,248,63,240,0,31,248,63,224,0,15,252,127,192,0,7,254,255,192,0,15,249,255,128,0,63,227,254,0,1,255,15,252,0,15,248,31,248,0,63,224,63,224,1,255,0,255,192,7,252,1,255,0,63,224,3,254,1,255,0,15,252,7,252,0,31,240,63,224,0,63,224,255,0,0,255,135,252,0,1,255,63,224,0,3,254,255,0,0,15,255,252,0,0,31,255,224,0,0,63,255,0,0,0,255,252,0,0,1,255,224,0,0,3,255,0,0,0,15,252,0,0,0,31,224,0,0,0,127,128,0,0,1,254,0,0,0,7,248,0,0,0,31,224,0,0,0,127,128,0,0,1,254,0,0,0,7,248,0,0,0,31,224,0,0,0,127,128,0,0,1,254,0,0,0,7,248,0,0,0,31,224,0,0,0,127,128,0,0,1,254,0,0,0,7,248,0,0,31,255,255,255,192,255,255,255,254,7,255,255,255,240,63,255,255,255,129,255,255,255,248,15,255,255,255,192,127,255,255,252,0,0,0,255,192,0,0,15,252,0,0,0,127,224,0,0,7,254,0,0,0,127,224,0,0,3,255,0,0,0,63,240,0,0,3,255,0,0,0,63,240,0,0,1,255,128,0,0,31,248,0,0,1,255,128,0,0,15,252,0,0,0,255,192,0,0,15,252,0,0,0,255,192,0,0,7,254,0,0,0,127,224,0,0,7,254,0,0,0,63,224,0,0,3,255,0,0,0,63,240,0,0,3,255,0,0,0,31,248,0,0,1,255,255,255,252,31,255,255,255,224,255,255,255,255,15,255,255,255,248,127,255,255,255,195,255,255,255,254,31,255,255,255,240,3,255,224,63,254,3,255,224,63,254,3,255,224,63,0,3,240,0,63,0,3,240,0,63,0,3,240,0,63,0,3,240,0,63,0,3,240,0,63,0,3,240,0,63,0,3,240,0,63,0,3,240,0,63,0,3,240,0,63,0,3,240,0,63,0,3,240,0,63,0,3,240,0,63,0,3,240,0,63,0,3,240,0,63,0,3,240,0,63,0,3,240,0,63,0,3,240,0,63,0,3,240,0,63,0,3,240,0,63,0,3,240,0,63,254,3,255,224,63,254,3,255,224,63,254,120,0,126,0,31,0,15,128,7,192,3,240,0,248,0,124,0,62,0,31,128,7,192,3,224,1,240,0,252,0,62,0,31,0,15,192,7,224,1,240,0,248,0,126,0,31,0,15,128,7,192,3,240,0,248,0,124,0,62,0,31,128,7,192,3,224,1,240,0,252,0,62,0,31,0,15,192,3,224,1,240,0,248,0,126,0,31,0,15,128,7,192,3,240,63,254,1,255,240,15,255,128,127,252,3,255,224,0,127,0,3,248,0,31,192,0,254,0,7,240,0,63,128,1,252,0,15,224,0,127,0,3,248,0,31,192,0,254,0,7,240,0,63,128,1,252,0,15,224,0,127,0,3,248,0,31,192,0,254,0,7,240,0,63,128,1,252,0,15,224,0,127,0,3,248,0,31,192,0,254,0,7,240,0,63,128,1,252,0,15,224,0,127,0,3,248,0,31,192,0,254,0,7,240,0,63,128,1,252,0,15,224,31,255,0,255,248,7,255,192,63,254,1,255,240,0,0,1,248,0,0,0,63,192,0,0,3,252,0,0,0,127,192,0,0,7,254,0,0,0,127,224,0,0,15,255,0,0,0,255,240,0,0,31,159,128,0,1,249,248,0,0,31,159,128,0,3,240,252,0,0,63,15,192,0,7,224,126,0,0,126,7,224,0,15,224,126,0,0,252,3,240,0,15,192,63,0,1,248,3,248,0,31,128,31,128,3,248,1,248,0,63,0,15,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,248,255,3,252,15,240,63,128,254,3,248,15,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,0,0,127,255,224,0,255,255,248,0,255,255,254,0,127,255,255,128,127,253,255,192,63,224,31,240,31,224,7,248,15,240,3,252,0,0,1,254,0,0,0,255,0,0,127,255,128,3,255,255,192,15,255,255,224,15,255,255,240,15,255,255,248,15,255,3,252,7,252,1,254,7,252,0,255,3,252,0,127,129,254,0,127,192,255,0,127,224,127,192,127,240,63,253,255,254,15,255,255,255,199,255,255,255,225,255,254,127,240,127,254,31,248,31,252,7,248,0,0,0,0,15,240,0,0,3,252,0,0,0,255,0,0,0,63,192,0,0,15,240,0,0,3,252,0,0,0,255,0,0,0,63,192,0,0,15,240,0,0,3,252,0,0,0,255,0,0,0,63,195,254,0,15,243,255,224,3,253,255,252,0,255,255,255,128,63,255,255,240,15,255,255,252,3,255,128,255,128,255,192,31,224,63,224,7,248,15,248,0,255,3,252,0,63,192,255,0,15,240,63,192,3,252,15,240,0,255,3,252,0,63,192,255,0,15,240,63,192,3,252,15,240,0,255,3,252,0,63,192,255,128,15,240,63,224,7,248,15,252,1,254,3,255,128,255,128,255,251,255,192,63,255,255,240,15,239,255,248,3,249,255,252,0,254,63,254,0,63,135,254,0,0,0,0,0,0,0,0,0,0,0,63,254,0,0,255,255,128,1,255,255,192,7,255,255,224,7,255,255,240,15,255,127,248,31,248,15,248,31,240,7,248,31,224,3,252,31,224,3,252,63,192,3,252,63,192,0,0,63,192,0,0,63,192,0,0,63,192,0,0,63,192,0,0,63,192,0,0,63,192,0,0,63,192,3,252,31,224,3,252,31,224,7,252,31,240,7,248,31,248,15,248,15,255,127,248,7,255,255,240,7,255,255,224,3,255,255,192,0,255,255,128,0,63,254,0,0,0,0,0,0,0,3,252,0,0,0,255,0,0,0,63,192,0,0,15,240,0,0,3,252,0,0,0,255,0,0,0,63,192,0,0,15,240,0,0,3,252,0,0,0,255,0,0,0,63,192,1,255,143,240,1,255,243,252,0,255,254,255,0,127,255,255,192,63,255,255,240,15,255,255,252,7,252,3,255,1,254,0,127,192,127,128,31,240,63,192,3,252,15,240,0,255,3,252,0,63,192,255,0,15,240,63,192,3,252,15,240,0,255,3,252,0,63,192,255,0,15,240,63,192,3,252,15,240,0,255,3,252,0,63,192,127,128,31,240,31,224,7,252,7,252,3,255,0,255,247,255,192,63,255,255,240,7,255,253,252,0,255,255,127,0,31,255,31,192,1,255,131,240,0,0,0,0,0,0,0,0,0,0,63,254,0,0,255,255,128,3,255,255,192,7,255,255,224,7,255,255,240,15,255,127,240,31,240,15,248,31,224,7,248,31,224,3,248,63,192,3,252,63,192,3,252,63,255,255,252,63,255,255,252,63,255,255,252,63,255,255,252,63,255,255,252,63,192,0,0,63,192,0,0,63,192,3,252,63,192,3,252,31,224,3,252,31,224,7,252,31,240,15,248,15,255,127,248,7,255,255,240,7,255,255,240,3,255,255,192,0,255,255,128,0,63,254,0,0,0,0,0,0,0,0,15,248,7,255,1,255,224,63,252,7,255,129,255,128,63,192,7,248,0,255,0,31,224,3,252,7,255,252,255,255,159,255,243,255,254,127,255,207,255,248,31,224,3,252,0,127,128,15,240,1,254,0,63,192,7,248,0,255,0,31,224,3,252,0,127,128,15,240,1,254,0,63,192,7,248,0,255,0,31,224,3,252,0,127,128,15,240,1,254,0,63,192,7,248,0,0,0,0,31,192,0,0,7,248,0,0,0,255,0,0,0,63,192,0,0,15,248,0,127,255,254,0,127,255,255,128,31,255,255,128,7,255,255,240,1,255,255,255,0,63,224,63,224,15,248,3,254,1,254,0,63,192,63,192,7,248,7,248,0,255,0,255,0,31,224,31,224,3,252,1,254,0,255,0,63,240,127,224,3,255,255,248,0,63,255,255,0,1,255,255,192,0,255,255,224,0,63,255,224,0,15,240,0,0,3,252,0,0,0,127,128,0,0,15,248,0,0,1,255,255,255,128,63,255,255,252,3,255,255,255,192,63,255,255,252,15,255,255,255,131,255,255,255,248,255,0,1,255,31,192,0,31,231,248,0,3,252,255,0,0,127,159,224,0,15,243,254,0,7,252,127,255,255,255,135,255,255,255,224,127,255,255,248,7,255,255,252,0,63,255,254,0,15,240,0,0,3,252,0,0,0,255,0,0,0,63,192,0,0,15,240,0,0,3,252,0,0,0,255,0,0,0,63,192,0,0,15,240,0,0,3,252,0,0,0,255,0,0,0,63,195,255,0,15,243,255,240,3,253,255,254,0,255,255,255,192,63,255,255,240,15,255,255,252,3,255,129,255,128,255,192,63,224,63,224,7,248,15,240,1,254,3,252,0,127,128,255,0,31,224,63,192,7,248,15,240,1,254,3,252,0,127,128,255,0,31,224,63,192,7,248,15,240,1,254,3,252,0,127,128,255,0,31,224,63,192,7,248,15,240,1,254,3,252,0,127,128,255,0,31,224,63,192,7,248,15,240,1,254,3,252,0,127,128,255,0,31,224,63,192,7,248,15,240,15,240,15,240,15,240,15,240,15,240,15,240,0,0,0,0,0,0,0,0,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,3,252,1,254,0,255,0,127,128,63,192,31,224,15,240,0,0,0,0,0,0,0,0,0,127,128,63,192,31,224,15,240,7,248,3,252,1,254,0,255,0,127,128,63,192,31,224,15,240,7,248,3,252,1,254,0,255,0,127,128,63,192,31,224,15,240,7,248,3,252,1,254,0,255,0,127,128,63,192,31,224,15,240,7,248,3,252,1,254,0,255,0,127,128,255,199,255,195,255,225,255,240,255,240,63,224,0,0,0,15,240,0,0,7,248,0,0,3,252,0,0,1,254,0,0,0,255,0,0,0,127,128,0,0,63,192,0,0,31,224,0,0,15,240,0,0,7,248,0,0,3,252,0,0,1,254,0,127,192,255,0,127,192,127,128,127,192,63,192,127,192,31,224,127,192,15,240,63,192,7,248,63,224,3,252,63,224,1,254,63,224,0,255,63,224,0,127,191,224,0,63,255,224,0,31,255,248,0,15,255,254,0,7,255,255,0,3,255,255,192,1,255,255,224,0,255,231,248,0,127,227,254,0,63,224,255,0,31,224,127,192,15,240,31,224,7,248,15,248,3,252,3,254,1,254,1,255,0,255,0,127,192,127,128,31,224,63,192,15,248,31,224,3,254,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,15,240,0,0,0,0,0,0,7,240,127,192,127,224,3,248,255,240,127,248,1,252,255,252,255,254,0,254,255,255,127,255,128,127,255,255,255,255,192,63,255,255,255,255,240,31,248,31,254,15,248,15,248,15,252,7,252,7,252,3,254,1,254,3,252,1,254,0,255,1,254,0,255,0,127,128,255,0,127,128,63,192,127,128,63,192,31,224,63,192,31,224,15,240,31,224,15,240,7,248,15,240,7,248,3,252,7,248,3,252,1,254,3,252,1,254,0,255,1,254,0,255,0,127,128,255,0,127,128,63,192,127,128,63,192,31,224,63,192,31,224,15,240,31,224,15,240,7,248,15,240,7,248,3,252,7,248,3,252,1,254,3,252,1,254,0,255,1,254,0,255,0,127,128,255,0,127,128,63,192,127,128,63,192,31,224,0,0,0,0,3,248,63,240,0,254,63,255,0,63,159,255,224,15,239,255,252,3,255,255,255,0,255,255,255,192,63,248,31,248,15,252,3,254,3,254,0,127,128,255,0,31,224,63,192,7,248,15,240,1,254,3,252,0,127,128,255,0,31,224,63,192,7,248,15,240,1,254,3,252,0,127,128,255,0,31,224,63,192,7,248,15,240,1,254,3,252,0,127,128,255,0,31,224,63,192,7,248,15,240,1,254,3,252,0,127,128,255,0,31,224,63,192,7,248,15,240,1,254,3,252,0,127,128,0,0,0,0,0,15,255,192,0,15,255,252,0,7,255,255,128,3,255,255,240,1,255,255,254,0,255,251,255,192,63,224,31,240,31,240,3,254,7,248,0,127,129,254,0,31,224,255,0,3,252,63,192,0,255,15,240,0,63,195,252,0,15,240,255,0,3,252,63,192,0,255,15,240,0,63,195,252,0,15,240,255,0,3,252,31,224,1,254,7,248,0,127,129,255,0,63,224,63,224,31,240,15,255,191,252,1,255,255,254,0,63,255,255,0,7,255,255,128,0,255,255,192,0,15,255,192,0,0,0,0,0,0,0,0,0,3,240,127,224,0,254,63,254,0,63,191,255,192,15,239,255,248,3,255,255,255,0,255,251,255,192,63,240,15,248,15,248,1,254,3,254,0,127,128,255,0,31,240,63,192,3,252,15,240,0,255,3,252,0,63,192,255,0,15,240,63,192,3,252,15,240,0,255,3,252,0,63,192,255,0,15,240,63,192,3,252,15,240,1,255,3,254,0,127,128,255,128,31,224,63,240,15,248,15,255,255,252,3,255,255,255,0,255,255,255,128,63,223,255,192,15,243,255,224,3,252,127,224,0,255,0,0,0,63,192,0,0,15,240,0,0,3,252,0,0,0,255,0,0,0,63,192,0,0,15,240,0,0,3,252,0,0,0,255,0,0,0,63,192,0,0,0,0,0,0,0,31,248,127,0,31,255,31,192,15,255,231,240,7,255,253,252,3,255,255,255,0,255,255,255,192,127,192,127,240,31,224,15,252,7,248,1,255,3,252,0,127,192,255,0,15,240,63,192,3,252,15,240,0,255,3,252,0,63,192,255,0,15,240,63,192,3,252,15,240,0,255,3,252,0,63,192,255,0,15,240,63,192,7,252,7,248,1,255,1,254,0,255,192,127,192,127,240,15,255,255,252,3,255,255,255,0,127,255,255,192,15,255,239,240,1,255,243,252,0,31,240,255,0,0,0,63,192,0,0,15,240,0,0,3,252,0,0,0,255,0,0,0,63,192,0,0,15,240,0,0,3,252,0,0,0,255,0,0,0,63,192,0,0,15,240,0,0,0,63,135,224,254,63,131,249,254,15,239,248,63,191,224,255,255,131,255,254,15,255,248,63,248,0,255,192,3,254,0,15,240,0,63,192,0,255,0,3,252,0,15,240,0,63,192,0,255,0,3,252,0,15,240,0,63,192,0,255,0,3,252,0,15,240,0,63,192,0,255,0,3,252,0,15,240,0,63,192,0,0,0,0,0,0,255,248,0,7,255,252,0,31,255,254,0,127,255,252,1,255,255,252,7,255,191,252,15,248,15,248,31,224,15,240,63,192,31,224,127,192,0,0,255,224,0,0,255,252,0,1,255,255,128,1,255,255,224,0,255,255,224,0,127,255,224,0,15,255,224,0,1,255,224,0,0,255,192,0,0,127,135,248,0,255,15,240,1,254,31,240,7,252,31,254,255,240,63,255,255,224,63,255,255,128,63,255,254,0,31,255,248,0,15,255,192,0,0,0,0,0,1,248,0,63,0,15,224,1,252,0,63,128,7,240,0,254,0,31,192,7,248,15,255,249,255,255,63,255,231,255,252,255,255,159,255,240,63,192,7,248,0,255,0,31,224,3,252,0,127,128,15,240,1,254,0,63,192,7,248,0,255,0,31,224,3,252,0,127,128,15,240,1,254,0,63,192,7,254,0,255,248,15,255,1,255,224,31,252,1,255,128,0,0,31,224,3,252,7,248,0,255,1,254,0,63,192,127,128,15,240,31,224,3,252,7,248,0,255,1,254,0,63,192,127,128,15,240,31,224,3,252,7,248,0,255,1,254,0,63,192,127,128,15,240,31,224,3,252,7,248,0,255,1,254,0,63,192,127,128,15,240,31,224,3,252,7,248,0,255,1,254,0,63,192,127,128,15,240,31,224,7,252,7,252,3,255,1,255,1,255,192,63,251,255,240,15,255,255,252,3,255,255,127,0,127,255,159,192,15,255,199,240,0,255,193,252,0,0,0,0,0,255,128,3,252,255,0,7,249,254,0,15,225,254,0,63,195,252,0,127,135,248,0,254,7,248,3,252,15,240,7,248,31,224,15,224,31,224,63,192,63,192,127,0,63,192,254,0,127,131,252,0,255,7,240,0,255,15,224,1,254,63,192,3,252,127,0,3,248,254,0,7,251,248,0,7,247,240,0,15,239,224,0,31,255,128,0,31,255,0,0,63,252,0,0,127,248,0,0,127,240,0,0,255,192,0,0,255,128,0,1,255,0,0,255,0,63,224,7,243,252,1,255,0,63,159,224,15,248,3,252,255,0,127,224,31,195,248,7,255,0,254,31,224,63,248,7,240,255,1,255,192,127,135,248,15,255,3,248,31,192,255,248,31,192,255,7,255,192,254,7,248,63,126,15,224,31,193,251,248,127,0,255,31,223,195,248,7,248,252,126,31,128,31,199,227,241,252,0,254,63,31,207,224,7,241,248,254,126,0,31,223,131,243,240,0,254,252,31,191,128,7,247,224,255,248,0,31,255,7,255,192,0,255,240,31,254,0,7,255,128,255,224,0,31,252,7,255,0,0,255,224,63,248,0,7,254,0,255,128,0,31,240,7,252,0,0,255,128,63,224,0,7,248,1,255,0,0,63,224,3,254,63,240,7,252,31,240,7,248,15,248,15,240,15,252,31,240,7,252,31,224,3,254,63,192,1,254,127,192,1,255,255,128,0,255,255,0,0,127,254,0,0,127,254,0,0,63,252,0,0,31,248,0,0,31,248,0,0,63,252,0,0,127,254,0,0,127,255,0,0,255,255,0,1,254,255,128,1,254,127,192,3,252,127,192,7,252,63,224,15,248,31,240,15,240,31,240,31,240,15,248,63,224,7,252,63,192,7,252,127,192,3,254,127,128,1,254,255,0,7,248,255,0,15,241,254,0,31,227,254,0,63,131,252,0,255,7,248,1,252,7,248,3,248,15,240,15,240,31,224,31,192,31,224,63,128,63,192,255,0,63,193,252,0,127,131,248,0,255,15,240,0,255,31,192,1,254,63,128,1,252,126,0,3,249,252,0,7,251,248,0,7,247,224,0,15,255,192,0,15,255,128,0,31,254,0,0,63,252,0,0,63,248,0,0,127,224,0,0,127,192,0,0,255,0,0,1,254,0,0,7,252,0,0,15,240,0,0,63,224,0,3,255,128,0,127,254,0,0,255,252,0,1,255,240,0,3,255,192,0,7,254,0,0,0,0,0,0,63,255,255,225,255,255,255,15,255,255,248,127,255,255,195,255,255,252,31,255,255,192,0,7,254,0,0,127,224,0,3,254,0,0,63,224,0,3,255,0,0,31,240,0,1,255,0,0,31,240,0,1,255,128,0,15,248,0,0,255,128,0,15,248,0,0,255,192,0,7,252,0,0,127,192,0,7,254,0,0,127,224,0,3,255,255,252,63,255,255,227,255,255,255,31,255,255,248,255,255,255,199,255,255,254,0,0,0,0,0,255,0,31,248,1,255,192,15,252,0,254,0,7,224,0,63,0,1,248,0,15,192,0,126,0,3,240,0,31,128,0,252,0,7,224,0,63,0,1,248,0,15,192,0,126,0,3,240,0,31,128,1,252,0,31,192,7,254,0,63,192,1,240,0,15,240,0,127,224,0,127,0,1,252,0,7,224,0,63,0,1,248,0,15,192,0,126,0,3,240,0,31,128,0,252,0,7,224,0,63,0,1,248,0,15,192,0,126,0,3,240,0,31,192,0,255,0,3,255,128,15,252,0,63,224,0,62,0,15,192,63,0,252,3,240,15,192,63,0,252,3,240,15,192,63,0,252,3,240,15,192,63,0,252,3,240,15,192,63,0,252,3,240,15,192,63,0,252,3,240,15,192,63,0,252,3,240,15,192,63,0,252,3,240,15,192,63,0,252,3,240,15,192,63,0,252,3,240,15,192,63,0,252,3,240,15,192,63,0,252,3,240,15,192,63,0,252,0,0,0,0,255,0,3,255,0,15,254,0,31,248,0,7,240,0,15,192,0,63,0,0,252,0,3,240,0,15,192,0,63,0,0,252,0,3,240,0,15,192,0,63,0,0,252,0,3,240,0,15,192,0,63,0,0,252,0,3,248,0,7,240,0,31,252,0,31,240,0,15,192,1,255,0,31,252,0,127,0,3,248,0,15,192,0,63,0,0,252,0,3,240,0,15,192,0,63,0,0,252,0,3,240,0,15,192,0,63,0,0,252,0,3,240,0,15,192,0,63,0,1,252,0,15,224,3,255,128,15,252,0,63,224,0,124,0,0,0,0,0,0,0,31,128,0,32,15,252,0,12,3,255,224,7,128,255,255,131,240,63,255,255,254,7,255,255,255,192,252,31,255,240,30,0,127,252,3,0,3,255,0,64,0,31,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +static const Glyph f0_56_glyphs[]={{0,11,0,0,39,11,32},{0,16,38,0,1,16,33},{76,25,38,0,1,25,34},{195,33,40,0,0,33,35},{360,31,44,0,-1,31,36},{531,55,40,0,0,55,37},{806,42,40,0,0,42,38},{1016,14,38,0,1,14,39},{1083,20,50,0,-2,20,40},{1208,20,50,0,-2,20,41},{1333,23,38,0,1,23,42},{1443,35,29,0,10,35,43},{1570,17,18,0,31,17,44},{1609,18,19,0,20,18,45},{1652,17,8,0,31,17,46},{1669,17,44,0,-2,17,47},{1763,34,40,0,0,34,48},{1933,34,38,0,1,34,49},{2095,34,39,0,0,34,50},{2261,33,40,0,0,33,51},{2426,34,39,0,0,34,52},{2592,33,39,0,1,33,53},{2753,33,40,0,0,33,54},{2918,34,38,0,1,34,55},{3080,33,40,0,0,33,56},{3245,33,40,0,0,33,57},{3410,18,29,0,10,18,58},{3476,19,39,0,10,19,59},{3569,36,30,0,9,36,60},{3704,35,23,0,16,35,61},{3805,36,30,0,9,36,62},{3940,34,39,0,0,34,63},{4106,57,47,0,0,57,64},{4441,41,38,0,1,41,65},{4636,40,38,0,1,40,66},{4826,41,40,0,0,41,67},{5031,42,38,0,1,42,68},{5231,38,38,0,1,38,69},{5412,35,38,0,1,35,70},{5579,45,40,0,0,45,71},{5804,42,38,0,1,42,72},{6004,16,38,0,1,16,73},{6080,34,39,0,1,34,74},{6246,40,38,0,1,40,75},{6436,33,38,0,1,33,76},{6593,48,38,0,1,48,77},{6821,42,38,0,1,42,78},{7021,46,40,0,0,46,79},{7251,37,38,0,1,37,80},{7427,46,46,0,0,46,81},{7692,41,38,0,1,41,82},{7887,38,40,0,0,38,83},{8077,35,38,0,1,35,84},{8244,42,39,0,1,42,85},{8449,39,38,0,1,39,86},{8635,54,38,0,1,54,87},{8892,40,38,0,1,40,88},{9082,38,38,0,1,38,89},{9263,37,38,0,1,37,90},{9439,20,50,0,-2,20,91},{9564,17,44,0,-2,17,92},{9658,21,50,0,-2,21,93},{9790,36,39,0,0,36,94},{9966,29,9,0,39,29,95},{9999,13,40,0,-1,13,96},{10064,33,31,0,9,33,97},{10192,34,41,0,-1,34,98},{10367,32,31,0,9,32,99},{10491,34,41,0,-1,34,100},{10666,32,31,0,9,32,101},{10790,19,41,0,-2,19,102},{10888,35,45,0,5,35,103},{11085,34,40,0,-1,34,104},{11255,16,40,0,-1,16,105},{11335,17,51,-2,-1,15,106},{11444,33,40,0,-1,33,107},{11609,16,40,0,-1,16,108},{11689,49,30,0,9,49,109},{11873,34,30,0,9,34,110},{12001,34,31,0,9,34,111},{12133,34,40,0,9,34,112},{12303,34,40,0,9,34,113},{12473,22,30,0,9,22,114},{12556,31,31,0,9,31,115},{12677,19,39,0,1,19,116},{12770,34,30,0,10,34,117},{12898,31,29,0,10,31,118},{13011,45,29,0,10,45,119},{13175,32,29,0,10,32,120},{13291,31,40,0,10,31,121},{13446,29,29,0,10,29,122},{13552,21,50,0,-2,21,123},{13684,14,51,0,-1,14,124},{13774,22,50,0,-2,22,125},{13912,35,23,0,16,35,126}}; +static const uint8_t f0_58_bits[]={0,0,0,0,0,3,255,224,0,1,255,255,0,0,127,255,240,0,31,255,255,0,7,255,255,240,0,255,255,254,0,63,224,63,224,7,248,3,252,1,255,0,127,192,63,192,7,248,7,248,0,255,0,255,0,31,224,63,224,3,252,7,248,0,63,192,255,0,7,248,31,224,0,255,3,252,0,31,224,127,128,3,252,15,240,0,127,129,254,0,15,240,63,192,1,254,7,248,0,63,192,255,0,7,248,31,224,0,255,3,252,0,31,224,127,128,3,252,15,240,0,127,129,255,0,31,224,31,224,3,252,3,252,0,127,128,127,128,15,240,15,248,3,254,0,255,0,127,128,31,240,31,240,1,255,255,252,0,63,255,255,128,3,255,255,224,0,63,255,248,0,3,255,254,0,0,31,255,0,0,0,0,0,0,0,0,31,0,0,0,7,224,0,0,7,252,0,0,3,255,128,0,7,255,240,0,7,255,254,0,0,255,255,192,0,31,255,248,0,3,255,255,0,0,127,255,224,0,0,3,252,0,0,0,127,128,0,0,15,240,0,0,1,254,0,0,0,63,192,0,0,7,248,0,0,0,255,0,0,0,31,224,0,0,3,252,0,0,0,127,128,0,0,15,240,0,0,1,254,0,0,0,63,192,0,0,7,248,0,0,0,255,0,0,0,31,224,0,0,3,252,0,0,0,127,128,0,0,15,240,0,0,1,254,0,0,0,63,192,0,0,7,248,0,0,0,255,0,0,0,31,224,0,15,255,255,254,1,255,255,255,192,63,255,255,248,7,255,255,255,0,255,255,255,224,31,255,255,252,0,0,0,0,0,3,255,240,0,1,255,255,128,0,255,255,248,0,63,255,255,128,15,255,255,248,1,255,255,255,0,127,224,63,240,15,248,3,254,3,254,0,63,224,127,192,3,252,15,240,0,127,129,254,0,15,240,63,192,1,254,7,248,0,127,192,0,0,15,248,0,0,3,254,0,0,0,255,192,0,0,63,240,0,0,15,254,0,0,3,255,128,0,0,255,224,0,0,63,248,0,0,31,254,0,0,7,255,128,0,1,255,224,0,0,255,248,0,0,63,252,0,0,15,255,0,0,3,255,128,0,0,255,224,0,0,63,248,0,0,7,252,0,0,1,255,128,0,0,63,255,255,252,7,255,255,255,129,255,255,255,240,63,255,255,254,7,255,255,255,192,255,255,255,248,31,255,255,255,0,0,0,0,0,0,3,255,224,0,1,255,255,0,0,127,255,248,0,63,255,255,128,7,255,255,240,1,255,255,255,0,63,224,63,240,15,248,3,254,1,255,0,127,192,63,192,7,248,7,248,0,255,0,255,0,31,224,0,0,3,252,0,0,0,127,128,0,0,31,224,0,0,7,252,0,0,3,255,0,0,15,255,192,0,1,255,240,0,0,63,248,0,0,7,255,192,0,0,255,254,0,0,31,255,224,0,0,7,254,0,0,0,127,192,0,0,7,252,0,0,0,127,128,0,0,15,240,63,192,1,254,7,248,0,63,192,255,0,7,248,31,240,1,255,1,254,0,63,224,63,240,31,248,7,255,255,255,0,127,255,255,192,7,255,255,240,0,127,255,252,0,3,255,255,0,0,31,255,0,0,0,0,0,0,0,7,248,0,0,1,255,0,0,0,63,192,0,0,7,248,0,0,1,255,0,0,0,63,193,224,0,7,248,60,0,1,255,15,128,0,63,193,240,0,7,248,62,0,1,254,15,192,0,63,193,248,0,15,240,127,0,1,254,15,224,0,63,131,252,0,15,240,127,128,1,252,15,240,0,127,129,254,0,15,224,63,192,3,248,7,248,0,127,0,255,0,31,192,31,224,7,248,3,252,0,254,0,127,128,63,128,15,240,7,255,255,255,240,255,255,255,254,31,255,255,255,195,255,255,255,248,127,255,255,255,15,255,255,255,224,0,0,127,128,0,0,15,240,0,0,1,254,0,0,0,63,192,0,0,7,248,0,0,0,255,0,0,0,31,224,0,0,3,252,0,0,0,127,128,3,255,255,252,0,127,255,255,128,15,255,255,240,1,255,255,254,0,63,255,255,192,7,255,255,248,0,255,255,255,0,31,192,0,0,3,248,0,0,0,255,0,0,0,31,224,0,0,3,252,0,0,0,127,128,0,0,15,240,0,0,1,254,63,224,0,63,223,255,0,7,255,255,240,0,255,255,255,0,31,255,255,240,3,255,255,255,0,127,224,255,224,15,240,7,252,0,252,0,127,192,0,0,15,248,0,0,0,255,0,0,0,31,224,0,0,3,252,15,240,0,127,129,254,0,15,240,63,192,1,254,7,252,0,127,192,127,128,15,248,15,248,3,254,1,255,128,255,192,31,255,255,240,1,255,255,254,0,63,255,255,128,1,255,255,224,0,31,255,240,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,1,255,224,0,0,255,255,0,0,127,255,240,0,31,255,255,0,3,255,255,240,0,255,255,255,0,31,224,63,224,7,248,3,254,0,255,0,127,192,63,192,7,248,7,248,0,255,0,255,0,31,224,31,224,0,0,7,248,0,0,0,255,7,248,0,31,227,255,192,3,253,255,254,0,127,255,255,224,15,255,255,254,1,255,255,255,192,63,254,31,252,7,255,0,255,128,255,192,15,248,31,240,1,255,3,254,0,31,224,127,192,3,252,15,248,0,127,129,255,0,15,240,31,224,1,254,3,252,0,63,192,127,128,15,248,15,248,1,255,0,255,0,127,192,31,248,31,248,1,255,255,254,0,63,255,255,192,3,255,255,240,0,63,255,252,0,1,255,254,0,0,15,255,0,0,0,0,0,0,31,255,255,255,131,255,255,255,240,127,255,255,254,15,255,255,255,193,255,255,255,248,63,255,255,254,7,255,255,255,128,0,0,31,240,0,0,7,252,0,0,0,255,0,0,0,63,192,0,0,15,240,0,0,3,254,0,0,0,127,128,0,0,31,224,0,0,3,252,0,0,0,255,0,0,0,63,192,0,0,7,248,0,0,1,254,0,0,0,63,192,0,0,15,240,0,0,1,254,0,0,0,63,128,0,0,15,240,0,0,1,254,0,0,0,127,128,0,0,15,240,0,0,1,254,0,0,0,127,128,0,0,15,240,0,0,1,254,0,0,0,63,192,0,0,7,240,0,0,1,254,0,0,0,63,192,0,0,7,248,0,0,0,255,0,0,0,31,224,0,0,3,252,0,0,0,0,0,0,0,7,255,192,0,15,255,252,0,7,255,255,128,3,255,255,240,1,255,255,254,0,255,255,255,192,63,240,63,240,31,240,3,254,7,252,0,255,129,254,0,31,224,127,128,7,248,31,224,1,254,7,248,0,127,129,255,0,31,224,63,192,15,240,15,248,7,252,1,255,135,254,0,127,255,255,0,15,255,255,128,0,255,255,192,0,127,255,240,0,63,255,255,0,63,255,255,224,15,252,15,252,7,252,1,255,1,254,0,63,224,255,128,7,248,63,192,1,254,15,240,0,127,131,252,0,31,224,255,0,7,248,63,224,3,254,15,252,0,255,129,255,128,255,192,127,255,255,240,15,255,255,248,1,255,255,252,0,63,255,254,0,7,255,255,0,0,63,255,0,0,0,0,0,0,0,0,0,0,0,15,255,0,0,15,255,248,0,7,255,255,0,3,255,255,224,1,255,255,252,0,255,255,255,0,127,224,63,224,31,240,7,248,7,248,0,255,3,254,0,63,192,255,128,15,240,63,192,1,254,15,240,0,127,131,252,0,31,224,255,0,7,248,63,224,3,254,15,248,0,255,129,255,0,127,224,127,224,63,248,31,252,31,254,3,255,255,255,128,255,255,255,224,31,255,255,248,3,255,253,254,0,63,252,127,128,3,252,31,224,0,0,7,248,0,0,1,254,7,248,0,127,1,254,0,63,192,127,128,15,240,31,240,3,252,3,252,1,254,0,255,128,255,128,63,255,255,192,7,255,255,240,0,255,255,248,0,31,255,252,0,3,255,252,0,0,63,252,0,0,0,0,0,0,0,0,0,0,0,0,0,0,63,248,0,0,124,0,0,63,254,0,0,124,0,0,63,255,128,0,126,0,0,63,255,224,0,62,0,0,31,131,240,0,62,0,0,31,193,252,0,62,0,0,15,192,126,0,63,0,0,7,224,63,0,31,0,0,3,240,31,128,31,0,0,1,248,15,192,31,128,0,0,252,7,224,15,128,0,0,126,3,240,15,128,0,0,63,1,248,15,128,0,0,31,128,252,15,192,0,0,7,224,252,7,192,0,0,3,248,254,7,192,0,0,0,255,254,7,224,0,0,0,127,255,3,224,0,0,0,15,255,3,224,0,0,0,1,252,3,240,0,0,0,0,0,1,240,15,224,0,0,0,1,240,31,254,0,0,0,1,240,63,255,128,0,0,1,248,31,255,192,0,0,0,248,31,199,240,0,0,0,248,15,193,248,0,0,0,252,15,192,126,0,0,0,124,7,224,63,0,0,0,124,3,240,31,128,0,0,124,1,248,15,192,0,0,126,0,252,7,224,0,0,62,0,126,3,240,0,0,62,0,63,1,248,0,0,63,0,31,128,252,0,0,31,0,15,224,254,0,0,31,0,3,240,126,0,0,31,0,1,255,255,0,0,15,128,0,127,255,0,0,15,128,0,31,255,0,0,15,128,0,7,255,0,0,0,0,0,0,0,0,0,3,252,0,63,192,3,252,0,63,192,3,252,0,63,192,3,252,0,63,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,252,0,63,192,3,252,0,63,192,3,252,0,63,192,3,252,0,63,192,63,255,135,255,240,255,254,31,255,195,255,248,127,255,15,255,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,248,1,254,0,127,128,31,224,7,248,1,254,0,127,128,31,224}; +static const Glyph f0_58_glyphs[]={{0,35,42,0,0,35,48},{184,35,40,0,1,35,49},{359,35,41,0,0,35,50},{539,35,42,0,0,35,51},{723,35,40,0,1,35,52},{898,35,41,0,1,35,53},{1078,35,42,0,0,35,54},{1262,35,40,0,1,35,55},{1437,34,42,0,0,34,56},{1616,34,42,0,0,34,57},{1795,57,42,0,0,57,37},{2095,20,30,0,11,20,58},{2170,19,19,0,22,19,45},{2216,18,8,0,33,18,46},{2234,11,0,0,41,11,32}}; +static const uint8_t f0_60_bits[]={0,0,0,0,0,0,255,240,0,0,63,255,192,0,15,255,255,0,1,255,255,248,0,63,255,255,192,3,255,255,252,0,127,224,127,224,7,252,3,254,0,127,128,31,224,15,248,1,255,0,255,0,15,240,15,240,0,255,0,255,0,15,240,31,240,0,255,129,255,0,15,248,31,240,0,255,129,255,0,15,248,31,240,0,255,129,255,0,15,248,31,240,0,255,129,255,0,15,248,31,240,0,255,129,255,0,15,248,31,240,0,255,129,255,0,15,248,31,240,0,255,129,255,0,15,248,31,240,0,255,128,255,0,15,240,15,240,0,255,0,255,0,15,240,15,248,1,255,0,127,128,31,224,7,252,3,254,0,127,224,127,224,3,255,255,252,0,31,255,255,128,1,255,255,248,0,15,255,255,0,0,63,255,192,0,0,255,240,0,0,0,0,0,0,0,0,15,128,0,0,7,240,0,0,3,254,0,0,3,255,192,0,3,255,248,0,7,255,255,0,0,255,255,224,0,31,255,252,0,3,255,255,128,0,127,255,240,0,0,3,254,0,0,0,127,192,0,0,15,248,0,0,1,255,0,0,0,63,224,0,0,7,252,0,0,0,255,128,0,0,31,240,0,0,3,254,0,0,0,127,192,0,0,15,248,0,0,1,255,0,0,0,63,224,0,0,7,252,0,0,0,255,128,0,0,31,240,0,0,3,254,0,0,0,127,192,0,0,15,248,0,0,1,255,0,0,0,63,224,0,0,7,252,0,0,0,255,128,0,0,31,240,0,0,3,254,0,1,255,255,255,192,63,255,255,248,7,255,255,255,0,255,255,255,224,31,255,255,252,3,255,255,255,128,0,0,0,0,0,0,255,248,0,0,127,255,224,0,15,255,255,0,3,255,255,248,0,127,255,255,192,7,255,255,254,0,255,224,127,224,15,248,3,255,1,255,0,31,240,31,240,0,255,1,255,0,15,240,31,240,0,255,1,255,0,15,240,31,240,0,255,0,0,0,31,240,0,0,1,255,0,0,0,63,240,0,0,7,254,0,0,0,255,224,0,0,31,252,0,0,3,255,128,0,0,127,240,0,0,31,254,0,0,3,255,192,0,0,127,248,0,0,31,255,0,0,3,255,224,0,0,127,248,0,0,15,255,0,0,1,255,224,0,0,63,248,0,0,7,255,0,0,0,127,224,0,0,15,252,0,0,0,255,255,255,248,31,255,255,255,129,255,255,255,248,31,255,255,255,129,255,255,255,248,31,255,255,255,129,255,255,255,248,0,0,0,0,0,1,255,248,0,0,127,255,224,0,15,255,255,128,3,255,255,252,0,127,255,255,224,7,255,255,254,0,255,224,127,240,15,248,1,255,0,255,128,31,240,15,240,0,255,1,255,0,15,240,15,240,0,255,0,0,0,15,240,0,0,0,255,0,0,0,31,240,0,0,3,254,0,0,1,255,192,0,3,255,248,0,0,63,255,0,0,3,255,192,0,0,63,255,0,0,3,255,252,0,0,63,255,224,0,0,7,255,0,0,0,31,240,0,0,0,255,128,0,0,7,248,0,0,0,127,129,254,0,7,248,31,240,0,127,129,255,0,7,248,31,240,0,127,129,255,0,15,248,15,248,1,255,128,255,224,63,240,7,255,255,255,0,127,255,255,224,3,255,255,252,0,31,255,255,128,0,127,255,224,0,1,255,248,0,0,0,0,0,0,0,3,248,0,0,0,127,128,0,0,7,248,0,0,0,255,128,0,0,15,248,0,0,0,255,0,0,0,31,240,120,0,1,255,7,128,0,31,224,248,0,3,254,15,128,0,63,193,248,0,3,252,31,128,0,127,129,248,0,7,248,63,128,0,255,3,248,0,15,240,127,128,1,254,7,248,0,31,224,255,128,1,252,15,248,0,63,192,255,128,7,248,15,248,0,127,128,255,128,15,240,15,248,0,254,0,255,128,31,224,15,248,1,252,0,255,128,63,128,15,248,3,255,255,255,252,63,255,255,255,195,255,255,255,252,63,255,255,255,195,255,255,255,252,63,255,255,255,192,0,0,255,128,0,0,15,248,0,0,0,255,128,0,0,15,248,0,0,0,255,128,0,0,15,248,0,0,0,255,128,0,0,15,248,0,0,0,255,128,3,255,255,254,0,63,255,255,224,3,255,255,254,0,63,255,255,224,3,255,255,254,0,63,255,255,224,3,255,255,254,0,63,128,0,0,3,248,0,0,0,63,128,0,0,7,248,0,0,0,127,128,0,0,7,248,0,0,0,127,128,0,0,7,248,127,0,0,127,159,254,0,7,255,255,240,0,127,255,255,192,7,255,255,252,0,127,255,255,224,7,255,159,255,0,127,192,63,240,7,248,1,255,0,1,0,15,248,0,0,0,255,128,0,0,15,248,0,0,0,255,128,0,0,15,248,31,240,0,255,129,255,0,15,248,31,240,0,255,129,255,0,15,248,15,248,1,255,0,255,192,63,240,15,254,7,254,0,127,255,255,224,3,255,255,252,0,31,255,255,128,0,255,255,240,0,7,255,254,0,0,15,255,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,63,255,224,0,7,255,255,0,0,255,255,248,0,31,255,255,192,3,255,255,252,0,63,224,127,224,7,252,3,254,0,127,128,31,240,15,248,1,255,0,255,0,31,240,15,240,1,255,0,255,0,0,0,15,240,0,0,1,255,0,0,0,31,240,255,192,1,255,63,255,0,31,255,255,248,1,255,255,255,192,31,255,255,254,1,255,255,255,240,31,255,7,255,1,255,192,31,248,31,248,0,255,129,255,128,15,248,31,240,0,127,129,255,0,7,248,31,240,0,127,128,255,0,7,248,15,240,0,127,128,255,0,7,248,15,248,0,255,128,127,128,15,248,7,252,1,255,0,127,224,127,240,3,255,255,254,0,31,255,255,224,0,255,255,252,0,7,255,255,128,0,63,255,224,0,0,255,248,0,0,0,0,0,0,31,255,255,255,193,255,255,255,252,31,255,255,255,193,255,255,255,252,31,255,255,255,193,255,255,255,248,31,255,255,255,0,0,0,31,240,0,0,3,254,0,0,0,127,192,0,0,7,248,0,0,0,255,128,0,0,31,240,0,0,1,254,0,0,0,63,192,0,0,7,252,0,0,0,127,128,0,0,15,248,0,0,0,255,0,0,0,31,224,0,0,1,254,0,0,0,63,192,0,0,3,252,0,0,0,127,128,0,0,7,248,0,0,0,255,128,0,0,15,240,0,0,0,255,0,0,0,31,240,0,0,1,254,0,0,0,31,224,0,0,1,254,0,0,0,63,224,0,0,3,252,0,0,0,63,192,0,0,3,252,0,0,0,63,192,0,0,3,252,0,0,0,63,192,0,0,3,252,0,0,0,127,192,0,0,0,0,0,0,0,0,255,248,0,0,127,255,224,0,15,255,255,0,1,255,255,252,0,63,255,255,192,7,255,255,254,0,127,224,127,240,15,252,1,255,0,255,128,31,240,15,248,0,255,0,255,128,15,240,15,248,0,255,0,255,128,15,240,15,248,0,255,0,127,128,31,240,7,252,3,254,0,63,248,255,224,1,255,255,252,0,15,255,255,128,0,127,255,224,0,15,255,255,0,1,255,255,252,0,63,255,255,224,7,254,7,255,0,255,128,31,240,15,248,0,255,129,255,0,7,248,31,240,0,127,129,255,0,7,248,31,240,0,127,129,255,0,7,248,31,240,0,127,129,255,0,15,248,15,248,1,255,128,255,224,63,240,7,255,255,255,0,127,255,255,224,3,255,255,252,0,31,255,255,128,0,127,255,240,0,1,255,248,0,0,0,0,0,0,0,0,0,0,0,3,255,192,0,3,255,254,0,0,255,255,240,0,63,255,255,0,15,255,255,240,3,255,255,255,0,127,224,127,224,31,248,3,254,3,254,0,127,192,255,128,7,248,31,240,0,255,131,254,0,15,240,127,192,1,254,15,248,0,63,193,255,0,15,248,63,224,1,255,7,252,0,63,224,127,192,15,252,15,252,3,255,129,255,192,255,248,31,255,255,254,3,255,255,255,192,63,255,255,248,3,255,254,255,0,31,255,159,224,0,255,195,252,0,0,0,127,128,0,0,15,240,0,0,1,254,7,252,0,127,192,255,128,15,240,31,240,1,254,3,254,0,127,192,63,224,15,240,7,254,7,254,0,127,255,255,128,15,255,255,240,0,255,255,252,0,15,255,255,0,0,127,255,128,0,3,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,254,0,0,15,192,0,3,255,240,0,3,240,0,0,255,255,0,0,252,0,0,63,255,240,0,31,128,0,7,224,126,0,7,224,0,0,252,15,192,1,248,0,0,63,0,252,0,63,0,0,7,224,31,128,15,192,0,0,252,3,240,3,240,0,0,31,128,126,0,252,0,0,3,240,15,192,31,128,0,0,126,1,248,7,224,0,0,15,192,63,1,248,0,0,1,248,7,224,63,0,0,0,31,129,248,15,192,0,0,3,248,127,3,240,0,0,0,63,255,192,252,0,0,0,7,255,248,31,128,0,0,0,63,252,7,224,0,0,0,1,254,1,248,0,0,0,0,0,0,63,1,254,0,0,0,0,15,192,255,240,0,0,0,3,240,63,255,0,0,0,0,252,15,255,240,0,0,0,31,131,248,127,0,0,0,7,224,126,7,224,0,0,1,248,31,192,254,0,0,0,63,3,240,15,192,0,0,15,192,126,1,248,0,0,3,240,15,192,63,0,0,0,252,1,248,7,224,0,0,31,128,63,0,252,0,0,7,224,7,224,31,128,0,1,248,0,252,3,240,0,0,63,0,31,128,126,0,0,15,192,1,248,31,128,0,3,240,0,63,3,240,0,0,252,0,7,255,254,0,0,31,128,0,127,255,128,0,7,224,0,7,255,224,0,1,248,0,0,63,240,0,0,0,0,0,0,0,0,0,3,254,0,63,224,3,254,0,63,224,3,254,0,63,224,3,254,0,63,224,3,254,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,254,0,63,224,3,254,0,63,224,3,254,0,63,224,3,254,0,63,224,3,254,0,63,255,135,255,240,255,254,31,255,195,255,248,127,255,15,255,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,252,1,255,0,127,192,31,240,7,252,1,255,0,127,192,31,240,7,252,0}; +static const Glyph f0_60_glyphs[]={{0,36,43,0,0,36,48},{194,35,41,0,1,35,49},{374,36,42,0,0,36,50},{563,36,43,0,0,36,51},{757,36,42,0,0,36,52},{946,36,42,0,1,36,53},{1135,36,43,0,0,36,54},{1329,36,41,0,1,36,55},{1514,36,43,0,0,36,56},{1708,35,43,0,0,35,57},{1897,59,43,0,0,59,37},{2215,20,31,0,11,20,58},{2293,19,19,0,23,19,45},{2339,18,9,0,33,18,46},{2360,12,0,0,42,12,32}}; +static const uint8_t f0_64_bits[]={0,3,255,0,0,0,127,255,128,0,3,255,255,0,0,63,255,255,0,1,255,255,254,0,7,255,255,248,0,63,255,255,240,0,255,192,255,192,7,254,1,255,128,31,240,3,254,0,255,128,7,252,3,254,0,31,240,15,248,0,127,192,63,224,1,255,0,255,0,3,252,7,252,0,15,248,31,240,0,63,224,127,192,0,255,129,255,0,3,254,7,252,0,15,248,31,240,0,63,224,127,192,0,255,129,255,0,3,254,7,252,0,15,248,31,240,0,63,224,127,192,0,255,129,255,0,3,254,7,252,0,15,248,31,240,0,63,224,127,192,0,255,129,255,0,3,254,3,252,0,15,240,15,248,0,127,192,63,224,1,255,0,255,128,7,252,3,254,0,31,240,7,252,0,255,128,31,248,7,254,0,63,240,63,240,0,255,255,255,192,1,255,255,254,0,7,255,255,240,0,15,255,255,192,0,15,255,252,0,0,31,255,224,0,0,15,252,0,0,0,0,3,128,0,0,0,126,0,0,0,7,248,0,0,0,127,224,0,0,15,255,128,0,1,255,254,0,0,63,255,248,0,0,255,255,224,0,3,255,255,128,0,15,255,254,0,0,63,255,248,0,0,255,255,224,0,0,0,255,128,0,0,3,254,0,0,0,15,248,0,0,0,63,224,0,0,0,255,128,0,0,3,254,0,0,0,15,248,0,0,0,63,224,0,0,0,255,128,0,0,3,254,0,0,0,15,248,0,0,0,63,224,0,0,0,255,128,0,0,3,254,0,0,0,15,248,0,0,0,63,224,0,0,0,255,128,0,0,3,254,0,0,0,15,248,0,0,0,63,224,0,0,0,255,128,0,0,3,254,0,0,0,15,248,0,0,0,63,224,0,0,0,255,128,0,0,3,254,0,0,63,255,255,254,0,255,255,255,248,3,255,255,255,224,15,255,255,255,128,63,255,255,254,0,255,255,255,248,3,255,255,255,224,0,1,255,128,0,0,127,255,192,0,7,255,255,192,0,63,255,255,128,3,255,255,255,0,15,255,255,254,0,127,255,255,248,3,255,192,255,240,15,252,0,255,192,63,224,1,255,1,255,128,7,254,7,252,0,15,248,31,240,0,63,224,127,192,0,255,129,255,0,3,254,7,252,0,31,248,0,0,0,127,224,0,0,3,255,0,0,0,15,252,0,0,0,127,224,0,0,3,255,128,0,0,31,252,0,0,0,255,224,0,0,15,255,128,0,0,127,252,0,0,3,255,224,0,0,63,254,0,0,1,255,240,0,0,15,255,128,0,0,127,252,0,0,7,255,192,0,0,63,254,0,0,1,255,224,0,0,15,255,0,0,0,63,248,0,0,1,255,128,0,0,15,254,0,0,0,63,255,255,255,128,255,255,255,254,7,255,255,255,248,31,255,255,255,224,127,255,255,255,129,255,255,255,254,7,255,255,255,248,31,255,255,255,224,0,3,255,0,0,0,127,255,128,0,7,255,255,128,0,63,255,255,0,1,255,255,254,0,15,255,255,252,0,127,255,255,248,1,255,192,255,224,15,254,1,255,192,63,240,3,255,0,255,128,7,252,3,254,0,31,240,15,248,0,127,192,63,224,1,255,0,0,0,7,252,0,0,0,31,240,0,0,0,255,128,0,0,7,254,0,0,0,63,240,0,0,127,255,128,0,1,255,252,0,0,7,255,192,0,0,31,255,0,0,0,127,255,0,0,1,255,254,0,0,7,255,252,0,0,0,63,248,0,0,0,63,240,0,0,0,127,192,0,0,0,255,128,0,0,3,254,0,0,0,15,248,31,240,0,63,224,127,192,0,255,129,255,0,3,254,7,254,0,31,248,31,248,0,127,224,63,240,3,255,0,255,240,63,252,1,255,255,255,224,7,255,255,255,128,15,255,255,252,0,31,255,255,224,0,31,255,254,0,0,31,255,224,0,0,15,252,0,0,0,3,254,0,0,0,15,248,0,0,0,127,224,0,0,1,255,0,0,0,7,252,0,0,0,31,240,0,0,0,255,128,0,0,3,254,7,128,0,15,248,62,0,0,127,192,248,0,1,255,3,224,0,7,248,31,128,0,63,224,126,0,0,255,131,248,0,7,252,15,224,0,31,240,127,128,0,127,129,254,0,3,254,15,248,0,15,240,63,224,0,127,192,255,128,1,254,3,254,0,15,240,15,248,0,63,192,63,224,1,254,0,255,128,7,248,3,254,0,63,192,15,248,1,254,0,63,224,7,248,0,255,128,63,192,3,254,0,255,255,255,255,195,255,255,255,255,15,255,255,255,252,63,255,255,255,240,255,255,255,255,195,255,255,255,255,15,255,255,255,252,0,0,3,254,0,0,0,15,248,0,0,0,63,224,0,0,0,255,128,0,0,3,254,0,0,0,15,248,0,0,0,63,224,0,0,0,255,128,0,0,3,254,0,0,0,15,248,0,3,255,255,255,128,15,255,255,254,0,63,255,255,248,0,255,255,255,224,3,255,255,255,128,15,255,255,254,0,63,255,255,248,0,255,255,255,224,3,252,0,0,0,15,240,0,0,0,63,192,0,0,0,255,0,0,0,3,252,0,0,0,31,240,0,0,0,127,192,0,0,1,255,15,240,0,7,252,255,240,0,31,255,255,240,0,127,255,255,224,1,255,255,255,192,7,255,255,255,128,31,255,255,254,0,127,240,63,252,1,255,128,63,240,7,248,0,127,192,0,0,1,255,128,0,0,7,254,0,0,0,15,248,0,0,0,63,224,0,0,0,255,129,255,0,3,254,7,252,0,15,248,31,240,0,63,224,127,224,1,255,129,255,128,7,252,3,255,0,63,240,15,254,1,255,192,31,252,15,254,0,127,255,255,248,0,255,255,255,192,1,255,255,254,0,3,255,255,240,0,7,255,255,128,0,7,255,248,0,0,3,255,0,0,0,1,255,0,0,0,63,255,128,0,3,255,255,128,0,31,255,255,0,0,255,255,254,0,7,255,255,252,0,63,255,255,240,0,255,192,255,224,7,254,1,255,128,31,240,3,255,0,127,128,7,252,3,254,0,31,240,15,248,0,127,192,63,224,1,255,0,255,0,0,0,3,252,0,0,0,31,240,63,128,0,127,195,255,192,1,255,63,255,192,7,253,255,255,128,31,255,255,255,0,127,255,255,254,1,255,255,255,248,7,255,224,255,240,31,254,0,255,192,127,240,3,255,1,255,128,7,254,7,254,0,31,248,31,248,0,63,224,127,224,0,255,129,255,128,3,254,3,254,0,15,248,15,248,0,63,224,63,224,0,255,128,255,128,7,254,3,254,0,31,240,7,252,0,255,192,31,248,3,255,0,63,248,63,248,0,255,255,255,224,1,255,255,255,0,3,255,255,248,0,7,255,255,192,0,15,255,254,0,0,15,255,224,0,0,7,252,0,0,31,255,255,255,240,63,255,255,255,224,127,255,255,255,192,255,255,255,255,129,255,255,255,255,3,255,255,255,254,7,255,255,255,248,15,255,255,255,224,0,0,0,255,128,0,0,3,255,0,0,0,7,252,0,0,0,31,240,0,0,0,127,192,0,0,1,255,128,0,0,3,254,0,0,0,15,248,0,0,0,63,240,0,0,0,127,192,0,0,1,255,0,0,0,7,254,0,0,0,15,248,0,0,0,63,240,0,0,0,127,192,0,0,1,255,128,0,0,3,254,0,0,0,15,252,0,0,0,31,240,0,0,0,63,224,0,0,0,255,128,0,0,1,255,0,0,0,7,254,0,0,0,15,248,0,0,0,31,240,0,0,0,127,224,0,0,0,255,192,0,0,1,255,128,0,0,3,254,0,0,0,7,252,0,0,0,31,248,0,0,0,63,240,0,0,0,127,224,0,0,0,255,192,0,0,1,255,128,0,0,3,255,0,0,0,3,254,0,0,0,0,1,255,0,0,0,63,255,224,0,1,255,255,240,0,7,255,255,240,0,31,255,255,240,0,127,255,255,240,0,255,255,255,224,3,255,192,255,224,7,254,0,255,192,31,248,0,255,192,63,240,0,255,128,127,192,1,255,0,255,128,3,254,1,255,0,7,252,3,254,0,15,248,7,254,0,31,240,7,252,0,127,192,15,252,1,255,128,15,254,15,254,0,31,255,255,252,0,31,255,255,240,0,15,255,255,128,0,15,255,254,0,0,127,255,255,0,1,255,255,255,0,7,255,255,255,0,31,252,7,255,0,127,224,3,255,0,255,128,3,254,3,255,0,7,254,7,252,0,7,252,15,248,0,15,248,31,240,0,31,240,63,224,0,63,224,127,192,0,127,192,255,192,1,255,129,255,128,3,255,1,255,128,15,252,3,255,192,127,248,3,255,255,255,224,7,255,255,255,192,7,255,255,255,0,7,255,255,252,0,3,255,255,224,0,1,255,255,0,0,0,127,240,0,0,0,3,254,0,0,0,127,255,0,0,7,255,255,0,0,63,255,254,0,1,255,255,252,0,15,255,255,248,0,127,255,255,240,1,255,192,255,192,15,252,1,255,128,63,240,3,254,1,255,128,7,252,7,254,0,31,240,31,240,0,127,192,127,192,0,255,1,255,0,3,254,7,252,0,15,248,31,240,0,127,224,127,224,1,255,129,255,128,7,254,3,255,0,63,248,15,252,1,255,224,63,252,15,255,128,127,255,255,254,1,255,255,255,248,3,255,255,255,224,7,255,254,255,128,15,255,243,254,0,15,255,143,248,0,15,240,63,224,0,0,0,255,128,0,0,3,252,0,0,0,15,240,15,248,0,127,192,63,224,1,255,0,255,128,7,252,3,255,0,31,224,7,252,0,255,128,31,248,7,254,0,127,240,63,240,0,255,255,255,192,3,255,255,254,0,7,255,255,240,0,15,255,255,128,0,31,255,252,0,0,31,255,192,0,0,15,248,0,0,0,15,224,0,0,0,0,0,1,255,240,0,0,126,0,0,15,255,224,0,3,240,0,0,127,255,192,0,31,128,0,3,255,255,128,0,126,0,0,15,240,254,0,3,240,0,0,63,129,248,0,31,128,0,1,254,3,240,0,126,0,0,7,240,15,192,3,240,0,0,31,192,63,0,31,128,0,0,127,0,252,0,126,0,0,1,252,3,240,3,240,0,0,7,240,15,192,31,128,0,0,31,192,63,0,126,0,0,0,127,0,252,3,240,0,0,1,252,3,240,31,128,0,0,3,248,31,128,254,0,0,0,15,240,254,3,240,0,0,0,63,255,248,31,128,0,0,0,127,255,192,252,0,0,0,0,255,254,3,240,0,0,0,1,255,240,31,128,0,0,0,0,255,0,252,3,252,0,0,0,0,3,240,127,254,0,0,0,0,31,131,255,252,0,0,0,0,252,31,255,248,0,0,0,3,240,127,255,224,0,0,0,31,131,252,63,192,0,0,0,252,15,224,127,0,0,0,3,240,127,129,254,0,0,0,31,129,252,3,248,0,0,0,252,7,240,15,224,0,0,3,240,31,192,63,128,0,0,31,128,127,0,254,0,0,0,252,1,252,3,248,0,0,7,224,7,240,15,224,0,0,31,128,31,192,63,128,0,0,252,0,127,0,254,0,0,7,224,1,254,7,248,0,0,31,128,3,248,31,192,0,0,252,0,15,240,255,0,0,7,224,0,31,255,248,0,0,31,128,0,127,255,224,0,0,252,0,0,255,255,0,0,7,224,0,0,255,248,0,0,0,0,0,0,255,0,0,3,254,0,31,240,0,255,128,7,252,0,63,224,1,255,0,15,248,0,127,192,3,254,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,240,0,255,128,7,252,0,63,224,1,255,0,15,248,0,127,192,3,254,0,31,240,0,31,255,224,127,255,129,255,254,7,255,248,31,255,224,127,255,129,255,254,7,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,252,0,255,128,31,240,3,254,0,127,192,15,248,1,255,0,63,224,7,252,0}; +static const Glyph f0_64_glyphs[]={{0,38,46,0,0,38,48},{219,38,45,0,0,38,49},{433,38,45,0,0,38,50},{647,38,46,0,0,38,51},{866,38,46,0,-1,38,52},{1085,38,45,0,1,38,53},{1299,38,46,0,0,38,54},{1518,39,45,0,0,39,55},{1738,39,46,0,0,39,56},{1963,38,46,0,0,38,57},{2182,62,46,0,0,62,37},{2539,21,34,0,11,21,58},{2629,22,22,0,23,22,45},{2690,19,9,0,36,19,46},{2712,13,0,0,45,13,32}}; +static const uint8_t f0_72_bits[]={0,0,127,128,0,0,1,255,254,0,0,1,255,255,224,0,0,255,255,252,0,0,127,255,255,128,0,63,255,255,240,0,31,255,255,254,0,7,255,255,255,128,3,255,192,255,240,0,255,224,15,252,0,127,240,3,255,128,31,248,0,127,224,15,254,0,31,248,3,255,0,3,255,0,255,192,0,255,192,63,240,0,63,240,15,252,0,15,252,3,255,0,3,255,1,255,128,0,127,224,127,224,0,31,248,31,248,0,7,254,7,254,0,1,255,129,255,128,0,127,224,127,224,0,31,248,31,248,0,7,254,7,254,0,1,255,129,255,128,0,127,224,127,224,0,31,248,31,248,0,7,254,7,254,0,1,255,129,255,128,0,127,224,127,224,0,31,248,31,248,0,7,254,7,254,0,1,255,129,255,128,0,255,224,63,240,0,63,240,15,252,0,15,252,3,255,0,3,255,0,255,192,0,255,192,63,240,0,63,240,7,254,0,31,248,1,255,128,7,254,0,127,240,3,255,128,15,255,3,255,192,3,255,255,255,240,0,127,255,255,248,0,15,255,255,252,0,1,255,255,254,0,0,63,255,255,0,0,7,255,255,128,0,0,127,255,128,0,0,3,255,0,0,0,0,0,248,0,0,0,0,127,0,0,0,0,63,224,0,0,0,63,252,0,0,0,31,255,128,0,0,63,255,240,0,0,127,255,254,0,0,15,255,255,192,0,1,255,255,248,0,0,63,255,255,0,0,7,255,255,224,0,0,255,255,252,0,0,31,255,255,128,0,0,0,63,240,0,0,0,7,254,0,0,0,0,255,192,0,0,0,31,248,0,0,0,3,255,0,0,0,0,127,224,0,0,0,15,252,0,0,0,1,255,128,0,0,0,63,240,0,0,0,7,254,0,0,0,0,255,192,0,0,0,31,248,0,0,0,3,255,0,0,0,0,127,224,0,0,0,15,252,0,0,0,1,255,128,0,0,0,63,240,0,0,0,7,254,0,0,0,0,255,192,0,0,0,31,248,0,0,0,3,255,0,0,0,0,127,224,0,0,0,15,252,0,0,0,1,255,128,0,0,0,63,240,0,0,0,7,254,0,0,0,0,255,192,0,0,0,31,248,0,0,0,3,255,0,0,7,255,255,255,252,0,255,255,255,255,128,31,255,255,255,240,3,255,255,255,254,0,127,255,255,255,192,15,255,255,255,248,1,255,255,255,255,0,63,255,255,255,224,0,0,31,192,0,0,0,127,255,128,0,0,63,255,252,0,0,31,255,255,224,0,7,255,255,254,0,1,255,255,255,224,0,127,255,255,254,0,31,255,255,255,192,3,255,224,127,252,0,255,240,3,255,128,31,252,0,63,248,7,255,0,7,255,0,255,224,0,127,224,31,248,0,15,252,3,255,0,1,255,128,127,224,0,63,240,15,252,0,7,254,1,255,128,0,255,192,0,0,0,63,248,0,0,0,7,255,0,0,0,1,255,192,0,0,0,127,248,0,0,0,31,254,0,0,0,7,255,192,0,0,1,255,240,0,0,0,127,252,0,0,0,31,255,128,0,0,7,255,224,0,0,3,255,248,0,0,0,255,254,0,0,0,63,255,128,0,0,15,255,192,0,0,7,255,240,0,0,1,255,252,0,0,0,127,254,0,0,0,31,255,128,0,0,7,255,224,0,0,1,255,240,0,0,0,127,252,0,0,0,15,255,0,0,0,3,255,192,0,0,0,127,240,0,0,0,31,255,255,255,252,3,255,255,255,255,128,255,255,255,255,240,31,255,255,255,254,3,255,255,255,255,192,127,255,255,255,248,15,255,255,255,255,1,255,255,255,255,224,63,255,255,255,252,0,0,0,63,128,0,0,0,255,255,0,0,0,127,255,248,0,0,63,255,255,192,0,15,255,255,252,0,3,255,255,255,192,0,255,255,255,252,0,31,255,255,255,192,7,255,192,255,248,0,255,224,7,255,0,63,248,0,127,240,7,255,0,15,254,0,255,192,0,255,192,31,248,0,31,248,3,255,0,3,255,0,127,224,0,127,224,0,0,0,15,252,0,0,0,1,255,128,0,0,0,127,224,0,0,0,15,252,0,0,0,3,255,0,0,0,1,255,224,0,0,31,255,248,0,0,3,255,252,0,0,0,127,255,0,0,0,15,255,192,0,0,1,255,254,0,0,0,63,255,224,0,0,7,255,254,0,0,0,255,255,224,0,0,0,31,254,0,0,0,0,255,224,0,0,0,15,252,0,0,0,1,255,192,0,0,0,31,248,0,0,0,3,255,1,255,128,0,127,224,63,240,0,15,252,7,254,0,1,255,128,255,192,0,63,240,31,252,0,15,254,1,255,128,1,255,192,63,248,0,127,240,7,255,192,63,254,0,127,255,255,255,128,15,255,255,255,240,0,255,255,255,252,0,15,255,255,255,0,0,255,255,255,192,0,7,255,255,224,0,0,63,255,240,0,0,0,127,240,0,0,0,0,255,128,0,0,0,127,224,0,0,0,31,248,0,0,0,15,254,0,0,0,3,255,0,0,0,0,255,192,0,0,0,63,240,0,0,0,31,248,31,0,0,7,254,7,192,0,1,255,129,240,0,0,255,192,252,0,0,63,240,63,0,0,15,248,31,192,0,7,254,7,240,0,1,255,129,252,0,0,255,192,255,0,0,63,240,63,192,0,15,248,31,240,0,7,254,7,252,0,1,255,3,255,0,0,255,192,255,192,0,63,224,63,240,0,31,248,15,252,0,7,252,3,255,0,3,254,0,255,192,0,255,128,63,240,0,127,192,15,252,0,31,240,3,255,0,15,248,0,255,192,3,252,0,63,240,1,255,0,15,252,0,255,128,3,255,0,63,255,255,255,255,15,255,255,255,255,195,255,255,255,255,240,255,255,255,255,252,63,255,255,255,255,15,255,255,255,255,195,255,255,255,255,240,255,255,255,255,252,0,0,0,255,192,0,0,0,63,240,0,0,0,15,252,0,0,0,3,255,0,0,0,0,255,192,0,0,0,63,240,0,0,0,15,252,0,0,0,3,255,0,0,0,0,255,192,0,0,0,63,240,0,0,0,15,252,0,1,255,255,255,240,0,63,255,255,254,0,7,255,255,255,192,0,255,255,255,248,0,31,255,255,255,0,3,255,255,255,224,0,255,255,255,252,0,31,255,255,255,128,3,255,255,255,240,0,127,192,0,0,0,15,248,0,0,0,1,255,0,0,0,0,63,224,0,0,0,7,252,0,0,0,0,255,128,0,0,0,31,240,0,0,0,3,254,0,0,0,0,127,192,124,0,0,15,248,255,248,0,3,255,127,255,192,0,127,255,255,252,0,15,255,255,255,192,1,255,255,255,252,0,63,255,255,255,192,7,255,255,255,248,0,255,248,31,255,128,31,252,0,255,240,3,255,0,15,254,0,3,192,0,255,224,0,0,0,31,252,0,0,0,1,255,128,0,0,0,63,240,0,0,0,7,254,0,0,0,0,255,192,127,224,0,31,248,15,252,0,3,255,1,255,128,0,127,224,63,248,0,31,252,7,255,0,3,255,128,127,224,0,127,224,15,254,0,31,252,1,255,224,7,255,128,31,255,1,255,224,3,255,255,255,252,0,63,255,255,255,0,3,255,255,255,192,0,63,255,255,240,0,3,255,255,252,0,0,31,255,255,0,0,0,255,255,0,0,0,3,255,0,0,0,0,0,31,192,0,0,0,63,255,128,0,0,31,255,252,0,0,15,255,255,224,0,3,255,255,254,0,0,255,255,255,224,0,31,255,255,252,0,7,255,255,255,192,1,255,224,127,252,0,63,240,3,255,128,15,254,0,127,240,1,255,128,7,255,0,63,240,0,255,224,7,252,0,15,252,1,255,128,1,255,128,63,240,0,63,240,7,254,0,0,0,0,255,128,0,0,0,31,240,0,0,0,7,254,3,252,0,0,255,195,255,240,0,31,248,255,255,128,3,255,127,255,248,0,127,255,255,255,128,15,255,255,255,248,1,255,255,255,255,128,63,255,255,255,240,7,255,240,31,255,0,255,248,1,255,224,31,254,0,31,252,3,255,192,1,255,192,127,240,0,63,248,15,254,0,3,255,1,255,192,0,127,224,63,248,0,15,252,3,255,0,1,255,128,127,224,0,63,240,15,252,0,7,254,1,255,128,0,255,192,63,240,0,63,248,3,255,0,7,255,0,127,224,1,255,192,15,254,0,127,248,0,255,240,31,254,0,31,255,255,255,192,1,255,255,255,240,0,31,255,255,252,0,1,255,255,255,0,0,31,255,255,192,0,1,255,255,240,0,0,15,255,248,0,0,0,31,248,0,0,31,255,255,255,255,3,255,255,255,255,224,127,255,255,255,252,15,255,255,255,255,129,255,255,255,255,240,63,255,255,255,254,7,255,255,255,255,192,255,255,255,255,240,31,255,255,255,252,0,0,0,3,255,0,0,0,0,255,192,0,0,0,63,240,0,0,0,15,254,0,0,0,1,255,128,0,0,0,127,224,0,0,0,31,252,0,0,0,3,255,0,0,0,0,255,192,0,0,0,63,248,0,0,0,7,254,0,0,0,1,255,128,0,0,0,127,240,0,0,0,15,252,0,0,0,3,255,128,0,0,0,127,224,0,0,0,31,248,0,0,0,3,255,0,0,0,0,255,192,0,0,0,31,248,0,0,0,7,255,0,0,0,0,255,192,0,0,0,31,248,0,0,0,7,254,0,0,0,0,255,192,0,0,0,63,248,0,0,0,7,254,0,0,0,0,255,192,0,0,0,31,248,0,0,0,7,255,0,0,0,0,255,224,0,0,0,31,248,0,0,0,3,255,0,0,0,0,127,224,0,0,0,31,252,0,0,0,3,255,128,0,0,0,127,240,0,0,0,15,254,0,0,0,1,255,192,0,0,0,63,248,0,0,0,3,255,0,0,0,0,0,127,0,0,0,0,255,255,0,0,0,127,255,248,0,0,63,255,255,128,0,15,255,255,252,0,3,255,255,255,192,0,255,255,255,248,0,31,255,255,255,128,7,255,192,255,248,0,255,224,7,255,0,31,252,0,255,224,7,255,0,15,254,0,255,224,1,255,192,31,248,0,31,248,3,255,0,3,255,0,127,224,0,127,224,15,252,0,15,252,1,255,192,3,255,128,31,248,0,127,224,3,255,0,31,252,0,127,240,3,255,128,7,255,129,255,224,0,127,255,255,248,0,7,255,255,255,0,0,127,255,255,192,0,7,255,255,224,0,0,255,255,254,0,0,127,255,255,240,0,31,255,255,255,0,7,255,255,255,240,1,255,224,15,255,0,127,248,0,255,240,15,254,0,15,254,1,255,128,0,255,192,127,240,0,31,252,15,252,0,1,255,129,255,128,0,63,240,63,240,0,7,254,7,254,0,0,255,192,255,224,0,63,248,31,252,0,7,255,1,255,192,1,255,192,63,252,0,127,248,7,255,192,31,255,0,127,255,255,255,192,15,255,255,255,248,0,255,255,255,254,0,15,255,255,255,128,0,255,255,255,224,0,7,255,255,240,0,0,63,255,248,0,0,0,127,240,0,0,0,0,127,0,0,0,0,255,254,0,0,0,127,255,240,0,0,63,255,255,128,0,15,255,255,248,0,3,255,255,255,128,0,255,255,255,248,0,31,255,255,255,128,7,255,192,255,240,0,255,224,7,255,0,63,248,0,127,224,7,255,0,15,252,0,255,192,0,255,192,63,248,0,31,248,7,254,0,3,255,0,255,192,0,127,224,31,248,0,7,254,3,255,0,0,255,192,127,224,0,63,248,15,252,0,7,255,1,255,192,0,255,224,63,248,0,31,252,3,255,128,7,255,128,127,240,0,255,240,15,255,0,63,254,0,255,248,31,255,192,31,255,255,255,248,1,255,255,255,255,0,63,255,255,255,224,3,255,255,255,252,0,63,255,253,255,128,1,255,254,63,240,0,15,255,7,254,0,0,63,0,255,192,0,0,0,31,248,0,0,0,3,254,0,0,0,0,127,192,31,248,0,31,248,3,255,0,3,255,0,127,224,0,127,192,15,254,0,31,248,1,255,192,3,255,0,31,252,0,255,192,3,255,192,63,248,0,63,255,255,254,0,7,255,255,255,192,0,127,255,255,240,0,7,255,255,252,0,0,127,255,255,0,0,7,255,255,192,0,0,63,255,224,0,0,0,255,192,0,0,0,3,252,0,0,0,0,0,0,0,127,254,0,0,1,252,0,0,3,255,254,0,0,15,224,0,0,31,255,248,0,0,127,0,0,0,255,255,240,0,1,252,0,0,7,252,63,224,0,15,224,0,0,31,192,63,128,0,127,0,0,0,127,0,254,0,1,248,0,0,3,248,1,252,0,15,224,0,0,15,224,7,240,0,127,0,0,0,63,128,31,192,3,248,0,0,0,254,0,127,0,15,224,0,0,3,248,1,252,0,127,0,0,0,15,224,7,240,3,248,0,0,0,63,128,31,192,15,224,0,0,0,254,0,127,0,127,0,0,0,3,248,1,252,3,248,0,0,0,7,240,15,224,31,192,0,0,0,31,192,63,128,127,0,0,0,0,127,195,254,3,248,0,0,0,0,255,255,240,31,192,0,0,0,1,255,255,128,127,0,0,0,0,3,255,252,3,248,0,0,0,0,7,255,224,31,192,0,0,0,0,3,252,0,255,0,0,0,0,0,0,0,3,248,3,254,0,0,0,0,0,31,192,63,254,0,0,0,0,0,254,3,255,254,0,0,0,0,3,248,15,255,252,0,0,0,0,31,192,127,255,240,0,0,0,0,254,3,252,31,224,0,0,0,7,248,15,224,63,128,0,0,0,31,192,63,128,254,0,0,0,0,254,1,252,1,252,0,0,0,7,248,7,240,7,240,0,0,0,31,192,31,192,31,192,0,0,0,254,0,127,0,127,0,0,0,7,240,1,252,1,252,0,0,0,63,192,7,240,7,240,0,0,0,254,0,31,192,31,192,0,0,7,240,0,127,0,127,0,0,0,63,192,1,252,1,252,0,0,0,254,0,7,240,7,240,0,0,7,240,0,15,224,63,128,0,0,63,192,0,63,128,254,0,0,1,254,0,0,255,7,248,0,0,7,240,0,1,255,255,192,0,0,63,128,0,3,255,255,0,0,1,254,0,0,7,255,248,0,0,7,240,0,0,15,255,128,0,0,0,0,0,0,15,248,0,0,1,255,128,1,255,128,1,255,128,1,255,128,1,255,128,1,255,128,1,255,128,1,255,128,1,255,128,1,255,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,255,128,1,255,128,1,255,128,1,255,128,1,255,128,1,255,128,1,255,128,1,255,128,1,255,128,1,255,128,31,255,248,31,255,248,31,255,248,31,255,248,31,255,248,31,255,248,31,255,248,31,255,248,31,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,255,0,15,252,0,63,240,0,255,192,3,255,0,15,252,0,63,240,0,255,192,3,255,0,15,252,0}; +static const Glyph f0_72_glyphs[]={{0,42,52,0,0,42,48},{273,43,50,0,1,43,49},{542,43,51,0,0,43,50},{817,43,52,0,0,43,51},{1097,42,51,0,0,42,52},{1365,43,51,0,1,43,53},{1640,43,52,0,0,43,54},{1920,43,50,0,1,43,55},{2189,43,52,0,0,43,56},{2469,43,52,0,0,43,57},{2749,70,51,0,1,70,37},{3196,24,38,0,13,24,58},{3310,24,24,0,27,24,45},{3382,22,10,0,41,22,46},{3410,14,0,0,51,14,32}}; +static const uint8_t f0_96_bits[]={0,0,0,0,0,0,0,0,0,0,255,254,0,0,0,0,3,255,255,240,0,0,0,3,255,255,255,0,0,0,1,255,255,255,224,0,0,1,255,255,255,254,0,0,0,255,255,255,255,192,0,0,63,255,255,255,240,0,0,31,255,255,255,254,0,0,15,255,255,255,255,192,0,3,255,255,255,255,240,0,1,255,255,3,255,254,0,0,127,255,0,63,255,128,0,63,255,0,3,255,240,0,15,255,192,0,255,252,0,7,255,224,0,31,255,128,1,255,248,0,7,255,224,0,127,252,0,0,255,248,0,31,255,0,0,63,254,0,15,255,192,0,15,255,192,3,255,224,0,1,255,240,0,255,248,0,0,127,252,0,63,254,0,0,31,255,0,15,255,128,0,7,255,192,3,255,224,0,1,255,240,1,255,248,0,0,127,254,0,127,254,0,0,31,255,128,31,255,128,0,7,255,224,7,255,224,0,1,255,248,1,255,248,0,0,127,254,0,127,254,0,0,31,255,128,31,255,128,0,7,255,224,7,255,224,0,1,255,248,1,255,248,0,0,127,254,0,127,254,0,0,31,255,128,31,255,128,0,7,255,224,7,255,224,0,1,255,248,1,255,248,0,0,127,254,0,127,254,0,0,31,255,128,31,255,128,0,7,255,224,7,255,224,0,1,255,248,1,255,248,0,0,127,254,0,127,254,0,0,31,255,128,31,255,128,0,7,255,224,7,255,224,0,1,255,248,0,255,248,0,0,127,252,0,63,254,0,0,31,255,0,15,255,128,0,7,255,192,3,255,224,0,1,255,240,0,255,252,0,0,127,252,0,63,255,0,0,63,255,0,7,255,192,0,15,255,128,1,255,240,0,3,255,224,0,127,254,0,1,255,248,0,31,255,128,0,127,252,0,3,255,240,0,63,255,0,0,255,254,0,31,255,192,0,31,255,192,15,255,224,0,7,255,254,31,255,248,0,0,255,255,255,255,252,0,0,63,255,255,255,255,0,0,7,255,255,255,255,128,0,0,255,255,255,255,192,0,0,31,255,255,255,224,0,0,3,255,255,255,240,0,0,0,127,255,255,248,0,0,0,7,255,255,248,0,0,0,0,127,255,248,0,0,0,0,1,255,224,0,0,0,0,0,0,0,126,0,0,0,0,0,0,127,128,0,0,0,0,0,127,224,0,0,0,0,0,127,248,0,0,0,0,0,127,254,0,0,0,0,0,127,255,128,0,0,0,0,255,255,224,0,0,0,1,255,255,248,0,0,0,15,255,255,254,0,0,0,15,255,255,255,128,0,0,3,255,255,255,224,0,0,0,255,255,255,248,0,0,0,63,255,255,254,0,0,0,15,255,255,255,128,0,0,3,255,255,255,224,0,0,0,255,255,255,248,0,0,0,63,255,255,254,0,0,0,0,0,31,255,128,0,0,0,0,7,255,224,0,0,0,0,1,255,248,0,0,0,0,0,127,254,0,0,0,0,0,31,255,128,0,0,0,0,7,255,224,0,0,0,0,1,255,248,0,0,0,0,0,127,254,0,0,0,0,0,31,255,128,0,0,0,0,7,255,224,0,0,0,0,1,255,248,0,0,0,0,0,127,254,0,0,0,0,0,31,255,128,0,0,0,0,7,255,224,0,0,0,0,1,255,248,0,0,0,0,0,127,254,0,0,0,0,0,31,255,128,0,0,0,0,7,255,224,0,0,0,0,1,255,248,0,0,0,0,0,127,254,0,0,0,0,0,31,255,128,0,0,0,0,7,255,224,0,0,0,0,1,255,248,0,0,0,0,0,127,254,0,0,0,0,0,31,255,128,0,0,0,0,7,255,224,0,0,0,0,1,255,248,0,0,0,0,0,127,254,0,0,0,0,0,31,255,128,0,0,0,0,7,255,224,0,0,0,0,1,255,248,0,0,0,0,0,127,254,0,0,0,0,0,31,255,128,0,0,0,0,7,255,224,0,0,0,0,1,255,248,0,0,0,0,0,127,254,0,0,0,0,0,31,255,128,0,0,0,0,7,255,224,0,0,0,0,1,255,248,0,0,0,0,0,127,254,0,0,0,15,255,255,255,255,255,0,3,255,255,255,255,255,192,0,255,255,255,255,255,240,0,63,255,255,255,255,252,0,15,255,255,255,255,255,0,3,255,255,255,255,255,192,0,255,255,255,255,255,240,0,63,255,255,255,255,252,0,15,255,255,255,255,255,0,3,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,255,255,0,0,0,0,1,255,255,248,0,0,0,3,255,255,255,128,0,0,3,255,255,255,248,0,0,1,255,255,255,255,0,0,1,255,255,255,255,224,0,0,255,255,255,255,252,0,0,63,255,255,255,255,128,0,31,255,255,255,255,240,0,15,255,255,255,255,252,0,7,255,255,1,255,255,128,1,255,254,0,31,255,224,0,127,255,0,1,255,248,0,63,255,128,0,127,255,0,15,255,192,0,15,255,192,3,255,240,0,3,255,240,0,255,248,0,0,127,252,0,63,254,0,0,31,255,128,31,255,128,0,7,255,224,7,255,224,0,1,255,248,1,255,248,0,0,127,254,0,127,254,0,0,31,255,0,15,255,128,0,15,255,192,0,0,0,0,3,255,240,0,0,0,0,0,255,252,0,0,0,0,0,127,255,0,0,0,0,0,63,255,128,0,0,0,0,15,255,224,0,0,0,0,7,255,240,0,0,0,0,3,255,252,0,0,0,0,1,255,254,0,0,0,0,0,255,255,128,0,0,0,0,127,255,192,0,0,0,0,127,255,224,0,0,0,0,63,255,240,0,0,0,0,31,255,252,0,0,0,0,15,255,254,0,0,0,0,15,255,255,0,0,0,0,7,255,255,128,0,0,0,3,255,255,128,0,0,0,3,255,255,192,0,0,0,1,255,255,224,0,0,0,0,255,255,240,0,0,0,0,127,255,240,0,0,0,0,127,255,248,0,0,0,0,63,255,248,0,0,0,0,31,255,252,0,0,0,0,15,255,254,0,0,0,0,7,255,254,0,0,0,0,3,255,255,0,0,0,0,0,255,255,128,0,0,0,0,127,255,128,0,0,0,0,63,255,192,0,0,0,0,15,255,224,0,0,0,0,7,255,240,0,0,0,0,1,255,255,255,255,255,248,0,255,255,255,255,255,254,0,63,255,255,255,255,255,128,15,255,255,255,255,255,224,7,255,255,255,255,255,248,1,255,255,255,255,255,254,0,127,255,255,255,255,255,128,31,255,255,255,255,255,224,7,255,255,255,255,255,248,1,255,255,255,255,255,254,0,127,255,255,255,255,255,128,31,255,255,255,255,255,224,0,0,0,0,0,0,0,0,0,7,255,248,0,0,0,0,31,255,255,128,0,0,0,63,255,255,248,0,0,0,127,255,255,254,0,0,0,255,255,255,255,192,0,0,255,255,255,255,240,0,0,255,255,255,255,252,0,0,255,255,255,255,255,0,0,127,255,255,255,255,128,0,127,255,255,255,255,224,0,63,255,224,63,255,248,0,63,255,192,3,255,252,0,31,255,128,0,255,254,0,15,255,192,0,63,255,128,15,255,192,0,31,255,192,7,255,192,0,7,255,224,3,255,224,0,3,255,240,1,255,240,0,1,255,248,0,255,248,0,0,255,252,0,127,252,0,0,127,254,0,0,0,0,0,63,255,0,0,0,0,0,31,255,128,0,0,0,0,15,255,128,0,0,0,0,15,255,192,0,0,0,0,7,255,192,0,0,0,0,7,255,224,0,0,0,0,15,255,224,0,0,0,0,31,255,240,0,0,0,31,255,255,240,0,0,0,15,255,255,240,0,0,0,7,255,255,224,0,0,0,3,255,255,224,0,0,0,1,255,255,192,0,0,0,0,255,255,248,0,0,0,0,127,255,255,0,0,0,0,63,255,255,192,0,0,0,31,255,255,240,0,0,0,15,255,255,252,0,0,0,0,7,255,255,0,0,0,0,0,63,255,192,0,0,0,0,7,255,240,0,0,0,0,1,255,248,0,0,0,0,0,127,254,0,0,0,0,0,63,255,0,0,0,0,0,15,255,128,0,0,0,0,7,255,192,0,0,0,0,3,255,224,15,255,128,0,1,255,248,7,255,192,0,0,255,252,3,255,224,0,0,127,254,1,255,240,0,0,63,255,0,255,248,0,0,63,255,0,127,254,0,0,31,255,128,31,255,0,0,15,255,192,15,255,192,0,15,255,224,7,255,240,0,15,255,224,1,255,254,0,31,255,240,0,255,255,240,255,255,248,0,63,255,255,255,255,248,0,31,255,255,255,255,248,0,7,255,255,255,255,252,0,1,255,255,255,255,252,0,0,127,255,255,255,248,0,0,15,255,255,255,248,0,0,3,255,255,255,248,0,0,0,127,255,255,240,0,0,0,7,255,255,192,0,0,0,0,63,254,0,0,0,0,0,7,255,192,0,0,0,0,7,255,224,0,0,0,0,3,255,240,0,0,0,0,1,255,248,0,0,0,0,0,255,248,0,0,0,0,0,255,252,0,0,0,0,0,127,254,0,0,0,0,0,63,254,0,0,0,0,0,31,255,0,0,0,0,0,31,255,128,0,0,0,0,15,255,128,127,0,0,0,7,255,192,63,128,0,0,7,255,224,31,192,0,0,3,255,224,15,224,0,0,1,255,240,15,240,0,0,1,255,240,7,248,0,0,0,255,248,7,252,0,0,0,255,252,3,254,0,0,0,127,252,3,255,0,0,0,63,254,1,255,128,0,0,63,254,0,255,192,0,0,31,255,0,255,224,0,0,31,255,0,127,240,0,0,15,255,128,127,248,0,0,7,255,128,63,252,0,0,7,255,192,63,254,0,0,3,255,192,31,255,0,0,3,255,224,31,255,128,0,1,255,224,15,255,192,0,1,255,240,7,255,224,0,0,255,240,3,255,240,0,0,255,248,1,255,248,0,0,127,248,0,255,252,0,0,127,248,0,127,254,0,0,63,252,0,63,255,0,0,63,252,0,31,255,128,0,31,254,0,15,255,192,0,31,254,0,7,255,224,0,15,254,0,3,255,240,0,15,255,0,1,255,248,0,15,255,0,0,255,252,0,7,255,0,0,127,254,0,7,255,0,0,63,255,0,3,255,255,255,255,255,255,129,255,255,255,255,255,255,192,255,255,255,255,255,255,224,127,255,255,255,255,255,240,63,255,255,255,255,255,248,31,255,255,255,255,255,252,15,255,255,255,255,255,254,7,255,255,255,255,255,255,3,255,255,255,255,255,255,129,255,255,255,255,255,255,192,0,0,0,7,255,224,0,0,0,0,3,255,240,0,0,0,0,1,255,248,0,0,0,0,0,255,252,0,0,0,0,0,127,254,0,0,0,0,0,63,255,0,0,0,0,0,31,255,128,0,0,0,0,15,255,192,0,0,0,0,7,255,224,0,0,0,0,3,255,240,0,0,0,0,1,255,248,0,0,0,0,0,255,252,0,0,0,0,0,127,254,0,0,0,0,0,63,255,0,0,0,0,0,31,255,128,0,0,127,255,255,255,255,128,0,63,255,255,255,255,192,0,31,255,255,255,255,224,0,15,255,255,255,255,240,0,7,255,255,255,255,248,0,3,255,255,255,255,252,0,1,255,255,255,255,254,0,0,255,255,255,255,255,0,0,127,255,255,255,255,128,0,63,255,255,255,255,192,0,63,255,255,255,255,224,0,31,255,255,255,255,240,0,15,255,0,0,0,0,0,7,255,128,0,0,0,0,3,255,192,0,0,0,0,1,255,224,0,0,0,0,0,255,240,0,0,0,0,0,127,248,0,0,0,0,0,63,252,0,0,0,0,0,31,254,0,0,0,0,0,15,255,0,0,0,0,0,7,255,128,0,0,0,0,3,255,192,0,0,0,0,1,255,224,0,0,0,0,1,255,240,31,254,0,0,0,255,248,127,255,224,0,0,127,252,255,255,252,0,0,63,255,255,255,255,0,0,31,255,255,255,255,192,0,15,255,255,255,255,240,0,7,255,255,255,255,252,0,3,255,255,255,255,255,0,1,255,255,255,255,255,192,0,255,255,255,255,255,224,0,127,255,224,63,255,248,0,63,255,192,7,255,252,0,31,255,128,0,255,254,0,15,255,128,0,63,255,128,0,31,128,0,31,255,192,0,0,0,0,7,255,224,0,0,0,0,3,255,240,0,0,0,0,0,255,248,0,0,0,0,0,127,252,0,0,0,0,0,63,255,0,0,0,0,0,31,255,128,0,0,0,0,15,255,192,63,254,0,0,7,255,224,31,255,128,0,3,255,240,15,255,192,0,1,255,240,7,255,224,0,0,255,248,3,255,240,0,0,255,252,0,255,252,0,0,127,254,0,127,254,0,0,127,255,0,63,255,128,0,63,255,0,31,255,192,0,63,255,128,7,255,248,0,63,255,192,3,255,254,0,127,255,192,0,255,255,225,255,255,224,0,127,255,255,255,255,224,0,31,255,255,255,255,224,0,7,255,255,255,255,224,0,1,255,255,255,255,240,0,0,127,255,255,255,224,0,0,31,255,255,255,224,0,0,3,255,255,255,224,0,0,0,127,255,255,192,0,0,0,15,255,255,0,0,0,0,0,63,248,0,0,0,0,0,0,0,0,0,0,0,0,0,255,254,0,0,0,0,1,255,255,240,0,0,0,1,255,255,255,0,0,0,1,255,255,255,224,0,0,0,255,255,255,254,0,0,0,127,255,255,255,192,0,0,63,255,255,255,248,0,0,31,255,255,255,254,0,0,7,255,255,255,255,192,0,3,255,255,255,255,248,0,0,255,255,1,255,254,0,0,127,255,0,31,255,192,0,31,255,0,3,255,240,0,15,255,192,0,255,252,0,3,255,224,0,31,255,0,1,255,240,0,7,255,224,0,127,252,0,0,255,248,0,31,255,0,0,63,254,0,7,255,192,0,15,255,128,3,255,224,0,3,255,224,0,255,248,0,0,0,0,0,63,254,0,0,0,0,0,15,255,128,0,0,0,0,3,255,224,0,0,0,0,0,255,248,3,255,0,0,0,63,254,7,255,252,0,0,31,255,135,255,255,192,0,7,255,227,255,255,248,0,1,255,251,255,255,255,128,0,127,255,255,255,255,240,0,31,255,255,255,255,254,0,7,255,255,255,255,255,192,1,255,255,255,255,255,240,0,127,255,255,255,255,254,0,31,255,255,195,255,255,128,7,255,255,128,31,255,240,1,255,255,128,3,255,252,0,127,255,192,0,127,255,0,31,255,224,0,15,255,224,7,255,248,0,1,255,248,1,255,252,0,0,127,254,0,127,255,0,0,31,255,128,31,255,192,0,3,255,224,7,255,224,0,0,255,252,1,255,248,0,0,63,255,0,63,254,0,0,15,255,192,15,255,128,0,3,255,240,3,255,224,0,0,255,252,0,255,248,0,0,63,254,0,63,255,0,0,15,255,128,7,255,192,0,7,255,224,1,255,240,0,1,255,248,0,127,254,0,0,127,254,0,31,255,128,0,63,255,0,3,255,240,0,31,255,192,0,255,254,0,15,255,240,0,31,255,192,7,255,248,0,7,255,255,15,255,254,0,0,255,255,255,255,255,0,0,31,255,255,255,255,128,0,7,255,255,255,255,192,0,0,255,255,255,255,240,0,0,31,255,255,255,240,0,0,3,255,255,255,248,0,0,0,63,255,255,252,0,0,0,3,255,255,252,0,0,0,0,63,255,252,0,0,0,0,0,255,240,0,0,0,15,255,255,255,255,255,248,7,255,255,255,255,255,252,3,255,255,255,255,255,254,1,255,255,255,255,255,255,0,255,255,255,255,255,255,128,127,255,255,255,255,255,192,63,255,255,255,255,255,224,31,255,255,255,255,255,240,15,255,255,255,255,255,248,7,255,255,255,255,255,248,3,255,255,255,255,255,248,1,255,255,255,255,255,248,0,0,0,0,0,127,248,0,0,0,0,0,127,252,0,0,0,0,0,127,252,0,0,0,0,0,127,252,0,0,0,0,0,127,252,0,0,0,0,0,63,254,0,0,0,0,0,63,254,0,0,0,0,0,63,254,0,0,0,0,0,63,255,0,0,0,0,0,31,255,0,0,0,0,0,31,255,0,0,0,0,0,31,255,128,0,0,0,0,15,255,128,0,0,0,0,15,255,192,0,0,0,0,7,255,192,0,0,0,0,7,255,192,0,0,0,0,7,255,224,0,0,0,0,3,255,224,0,0,0,0,3,255,240,0,0,0,0,1,255,240,0,0,0,0,1,255,248,0,0,0,0,0,255,248,0,0,0,0,0,255,252,0,0,0,0,0,127,252,0,0,0,0,0,127,254,0,0,0,0,0,63,254,0,0,0,0,0,63,255,0,0,0,0,0,31,255,128,0,0,0,0,31,255,128,0,0,0,0,15,255,192,0,0,0,0,7,255,192,0,0,0,0,7,255,224,0,0,0,0,3,255,240,0,0,0,0,1,255,240,0,0,0,0,1,255,248,0,0,0,0,0,255,252,0,0,0,0,0,127,254,0,0,0,0,0,127,254,0,0,0,0,0,63,255,0,0,0,0,0,31,255,128,0,0,0,0,15,255,192,0,0,0,0,15,255,224,0,0,0,0,7,255,224,0,0,0,0,3,255,240,0,0,0,0,1,255,248,0,0,0,0,0,255,252,0,0,0,0,0,127,254,0,0,0,0,0,127,255,0,0,0,0,0,63,255,128,0,0,0,0,31,255,192,0,0,0,0,15,255,224,0,0,0,0,7,255,240,0,0,0,0,3,255,248,0,0,0,0,1,255,252,0,0,0,0,0,127,254,0,0,0,0,0,0,1,255,192,0,0,0,0,15,255,254,0,0,0,0,63,255,255,224,0,0,0,127,255,255,252,0,0,0,127,255,255,255,0,0,0,127,255,255,255,192,0,0,127,255,255,255,240,0,0,127,255,255,255,252,0,0,127,255,255,255,255,0,0,127,255,255,255,255,192,0,63,255,248,255,255,224,0,63,255,192,7,255,248,0,31,255,192,1,255,252,0,15,255,192,0,127,254,0,15,255,192,0,31,255,128,7,255,224,0,15,255,192,3,255,224,0,3,255,224,1,255,240,0,1,255,240,0,255,248,0,0,255,248,0,127,252,0,0,127,252,0,63,254,0,0,63,254,0,31,255,0,0,31,255,0,15,255,128,0,15,255,128,3,255,224,0,15,255,128,1,255,240,0,7,255,192,0,255,252,0,7,255,224,0,63,255,0,7,255,224,0,31,255,192,7,255,240,0,7,255,252,63,255,240,0,1,255,255,255,255,240,0,0,127,255,255,255,240,0,0,31,255,255,255,240,0,0,7,255,255,255,240,0,0,1,255,255,255,240,0,0,1,255,255,255,252,0,0,3,255,255,255,255,128,0,3,255,255,255,255,224,0,3,255,255,255,255,248,0,3,255,255,255,255,254,0,3,255,255,1,255,255,128,3,255,252,0,31,255,224,1,255,252,0,7,255,240,1,255,252,0,1,255,252,0,255,252,0,0,127,254,0,127,252,0,0,31,255,0,127,254,0,0,15,255,192,63,255,0,0,7,255,224,31,255,128,0,3,255,240,15,255,192,0,1,255,248,7,255,224,0,0,255,252,3,255,240,0,0,127,254,1,255,248,0,0,63,255,0,255,252,0,0,31,255,128,127,255,0,0,31,255,192,31,255,128,0,15,255,192,15,255,224,0,15,255,224,7,255,248,0,15,255,240,1,255,255,0,31,255,240,0,255,255,240,127,255,248,0,63,255,255,255,255,248,0,31,255,255,255,255,252,0,7,255,255,255,255,252,0,1,255,255,255,255,252,0,0,127,255,255,255,252,0,0,31,255,255,255,252,0,0,3,255,255,255,248,0,0,0,127,255,255,240,0,0,0,15,255,255,224,0,0,0,0,63,254,0,0,0,0,0,0,0,0,0,0,0,0,7,255,240,0,0,0,0,31,255,255,0,0,0,0,63,255,255,240,0,0,0,127,255,255,252,0,0,0,127,255,255,255,128,0,0,127,255,255,255,224,0,0,127,255,255,255,248,0,0,127,255,255,255,254,0,0,127,255,255,255,255,0,0,127,255,255,255,255,192,0,63,255,224,31,255,240,0,63,255,192,3,255,248,0,31,255,192,0,255,252,0,31,255,192,0,63,255,0,15,255,192,0,15,255,128,7,255,224,0,7,255,224,3,255,224,0,1,255,240,3,255,240,0,0,255,248,1,255,248,0,0,127,252,0,255,252,0,0,63,255,0,127,254,0,0,31,255,128,63,255,0,0,15,255,192,31,255,128,0,7,255,224,15,255,192,0,3,255,240,7,255,224,0,1,255,248,3,255,240,0,0,255,252,1,255,248,0,0,127,254,0,127,254,0,0,127,255,128,63,255,0,0,63,255,192,31,255,192,0,63,255,224,15,255,240,0,63,255,240,3,255,252,0,63,255,248,1,255,255,128,127,255,252,0,127,255,255,255,255,254,0,63,255,255,255,255,255,0,15,255,255,255,255,255,128,3,255,255,255,255,255,192,1,255,255,255,255,255,192,0,127,255,255,251,255,224,0,15,255,255,249,255,240,0,3,255,255,240,255,248,0,0,127,255,224,127,252,0,0,7,255,192,63,254,0,0,0,0,0,31,255,0,0,0,0,0,15,255,128,0,0,0,0,7,255,192,0,0,0,0,7,255,192,3,255,224,0,3,255,224,1,255,248,0,1,255,240,0,255,252,0,0,255,248,0,127,254,0,0,127,252,0,63,255,0,0,127,252,0,15,255,128,0,63,254,0,7,255,224,0,63,255,0,3,255,240,0,31,255,0,1,255,252,0,31,255,128,0,127,255,128,31,255,128,0,63,255,248,255,255,192,0,15,255,255,255,255,192,0,7,255,255,255,255,224,0,1,255,255,255,255,224,0,0,127,255,255,255,224,0,0,31,255,255,255,224,0,0,7,255,255,255,224,0,0,1,255,255,255,192,0,0,0,63,255,255,192,0,0,0,7,255,255,0,0,0,0,0,63,252,0,0,0,0,0,31,248,0,0,0,0,0,0,0,0,0,3,255,252,0,0,0,0,255,128,0,0,0,63,255,252,0,0,0,7,252,0,0,0,1,255,255,248,0,0,0,63,224,0,0,0,15,255,255,240,0,0,1,255,128,0,0,0,127,255,255,224,0,0,7,252,0,0,0,3,255,255,255,192,0,0,63,224,0,0,0,15,255,15,255,0,0,1,255,0,0,0,0,127,240,15,254,0,0,7,252,0,0,0,1,255,128,31,248,0,0,63,224,0,0,0,7,254,0,127,224,0,1,255,0,0,0,0,31,248,1,255,128,0,7,252,0,0,0,0,255,192,3,255,0,0,63,224,0,0,0,3,255,0,15,252,0,1,255,0,0,0,0,15,252,0,63,240,0,15,252,0,0,0,0,63,240,0,255,192,0,63,224,0,0,0,0,255,192,3,255,0,1,255,0,0,0,0,3,255,0,15,252,0,15,248,0,0,0,0,15,252,0,63,240,0,63,224,0,0,0,0,63,240,0,255,192,1,255,0,0,0,0,0,255,192,3,255,0,15,248,0,0,0,0,3,255,0,15,252,0,63,224,0,0,0,0,15,252,0,63,240,1,255,0,0,0,0,0,31,248,1,255,128,15,248,0,0,0,0,0,127,224,7,254,0,127,224,0,0,0,0,1,255,128,31,248,1,255,0,0,0,0,0,7,255,0,255,224,15,248,0,0,0,0,0,15,254,7,255,0,127,192,0,0,0,0,0,63,255,255,252,1,255,0,0,0,0,0,0,127,255,255,224,15,248,0,0,0,0,0,0,255,255,255,0,127,192,0,0,0,0,0,1,255,255,248,1,255,0,0,0,0,0,0,3,255,255,192,15,248,0,0,0,0,0,0,3,255,252,0,127,192,0,0,0,0,0,0,1,255,128,1,255,0,7,254,0,0,0,0,0,0,0,15,248,0,255,255,0,0,0,0,0,0,0,127,192,15,255,255,0,0,0,0,0,0,3,254,0,127,255,254,0,0,0,0,0,0,15,248,3,255,255,252,0,0,0,0,0,0,127,192,31,255,255,248,0,0,0,0,0,3,254,0,255,255,255,240,0,0,0,0,0,15,248,3,255,193,255,192,0,0,0,0,0,127,192,31,252,3,255,128,0,0,0,0,3,254,0,127,224,7,254,0,0,0,0,0,15,248,1,255,128,31,248,0,0,0,0,0,127,192,7,254,0,127,224,0,0,0,0,3,254,0,63,240,0,255,192,0,0,0,0,31,240,0,255,192,3,255,0,0,0,0,0,127,192,3,255,0,15,252,0,0,0,0,3,254,0,15,252,0,63,240,0,0,0,0,31,240,0,63,240,0,255,192,0,0,0,0,127,192,0,255,192,3,255,0,0,0,0,3,254,0,3,255,0,15,252,0,0,0,0,31,240,0,15,252,0,63,240,0,0,0,0,127,192,0,63,240,0,255,192,0,0,0,3,254,0,0,255,192,3,255,0,0,0,0,31,240,0,3,255,0,15,252,0,0,0,0,127,192,0,7,254,0,127,224,0,0,0,3,254,0,0,31,248,1,255,128,0,0,0,31,240,0,0,127,224,7,254,0,0,0,0,255,128,0,1,255,192,63,248,0,0,0,3,254,0,0,3,255,193,255,192,0,0,0,31,240,0,0,15,255,255,255,0,0,0,0,255,128,0,0,31,255,255,248,0,0,0,3,254,0,0,0,63,255,255,192,0,0,0,31,240,0,0,0,127,255,254,0,0,0,0,255,128,0,0,0,255,255,240,0,0,0,3,252,0,0,0,0,255,255,0,0,0,0,0,0,0,0,0,0,127,224,0,0,0,127,254,0,0,127,254,0,0,127,254,0,0,127,254,0,0,127,254,0,0,127,254,0,0,127,254,0,0,127,254,0,0,127,254,0,0,127,254,0,0,127,254,0,0,127,254,0,0,127,254,0,0,127,254,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,254,0,0,127,254,0,0,127,254,0,0,127,254,0,0,127,254,0,0,127,254,0,0,127,254,0,0,127,254,0,0,127,254,0,0,127,254,0,0,127,254,0,0,127,254,0,0,127,254,0,0,127,254,0,15,255,255,240,15,255,255,240,15,255,255,240,15,255,255,240,15,255,255,240,15,255,255,240,15,255,255,240,15,255,255,240,15,255,255,240,15,255,255,240,15,255,255,240,15,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,252,0,7,255,224,0,63,255,0,1,255,248,0,15,255,192,0,127,254,0,3,255,240,0,31,255,128,0,255,252,0,7,255,224,0,63,255,0,1,255,248,0,15,255,192,0,127,254,0}; +static const Glyph f0_96_glyphs[]={{0,58,69,0,0,58,48},{501,58,67,0,1,58,49},{987,58,68,0,0,58,50},{1480,57,69,0,0,57,51},{1972,57,68,0,0,57,52},{2457,57,68,0,1,57,53},{2942,58,69,0,0,58,54},{3443,57,67,0,2,57,55},{3921,57,69,0,0,57,56},{4413,57,69,0,0,57,57},{4905,94,69,0,0,94,37},{5716,32,51,0,17,32,58},{5920,32,32,0,36,32,45},{6048,29,14,0,54,29,46},{6099,19,0,0,68,19,32}}; +static const uint8_t f0_120_bits[]={0,0,0,3,255,192,0,0,0,0,0,0,127,255,254,0,0,0,0,0,3,255,255,255,192,0,0,0,0,15,255,255,255,240,0,0,0,0,31,255,255,255,248,0,0,0,0,127,255,255,255,254,0,0,0,0,255,255,255,255,255,0,0,0,1,255,255,255,255,255,128,0,0,3,255,255,255,255,255,192,0,0,3,255,255,255,255,255,192,0,0,7,255,255,255,255,255,224,0,0,15,255,255,255,255,255,240,0,0,15,255,255,255,255,255,240,0,0,31,255,255,0,255,255,248,0,0,31,255,252,0,63,255,248,0,0,63,255,248,0,15,255,252,0,0,63,255,240,0,7,255,252,0,0,127,255,224,0,7,255,254,0,0,127,255,192,0,3,255,254,0,0,127,255,192,0,3,255,254,0,0,255,255,128,0,1,255,255,0,0,255,255,128,0,1,255,255,0,0,255,255,128,0,1,255,255,0,0,255,255,0,0,0,255,255,0,0,255,255,0,0,0,255,255,0,1,255,255,0,0,0,255,255,128,1,255,255,0,0,0,255,255,128,1,255,255,0,0,0,255,255,128,1,255,255,0,0,0,255,255,128,1,255,254,0,0,0,127,255,128,1,255,254,0,0,0,127,255,128,1,255,254,0,0,0,127,255,128,3,255,254,0,0,0,127,255,192,3,255,254,0,0,0,127,255,192,3,255,254,0,0,0,127,255,192,3,255,254,0,0,0,127,255,192,3,255,254,0,0,0,127,255,192,3,255,254,0,0,0,127,255,192,3,255,254,0,0,0,127,255,192,3,255,254,0,0,0,127,255,192,3,255,254,0,0,0,127,255,192,3,255,254,0,0,0,127,255,192,3,255,254,0,0,0,127,255,192,3,255,254,0,0,0,127,255,192,3,255,254,0,0,0,127,255,192,3,255,254,0,0,0,127,255,192,3,255,254,0,0,0,127,255,192,3,255,254,0,0,0,127,255,192,3,255,254,0,0,0,127,255,192,3,255,254,0,0,0,127,255,192,3,255,254,0,0,0,127,255,192,3,255,254,0,0,0,127,255,192,3,255,254,0,0,0,127,255,192,3,255,254,0,0,0,127,255,192,1,255,254,0,0,0,127,255,128,1,255,254,0,0,0,127,255,128,1,255,254,0,0,0,127,255,128,1,255,255,0,0,0,255,255,128,1,255,255,0,0,0,255,255,128,1,255,255,0,0,0,255,255,128,1,255,255,0,0,0,255,255,128,0,255,255,0,0,0,255,255,0,0,255,255,0,0,0,255,255,0,0,255,255,128,0,1,255,255,0,0,255,255,128,0,1,255,255,0,0,255,255,128,0,1,255,255,0,0,127,255,192,0,3,255,254,0,0,127,255,192,0,3,255,254,0,0,127,255,224,0,7,255,254,0,0,63,255,224,0,7,255,252,0,0,63,255,240,0,15,255,252,0,0,31,255,252,0,63,255,248,0,0,31,255,255,0,255,255,248,0,0,15,255,255,255,255,255,240,0,0,15,255,255,255,255,255,240,0,0,7,255,255,255,255,255,224,0,0,3,255,255,255,255,255,192,0,0,3,255,255,255,255,255,192,0,0,1,255,255,255,255,255,128,0,0,0,255,255,255,255,255,0,0,0,0,127,255,255,255,254,0,0,0,0,31,255,255,255,248,0,0,0,0,15,255,255,255,240,0,0,0,0,3,255,255,255,192,0,0,0,0,0,127,255,254,0,0,0,0,0,0,3,255,224,0,0,0,0,0,0,0,0,127,128,0,0,0,0,0,0,1,255,128,0,0,0,0,0,0,7,255,128,0,0,0,0,0,0,31,255,128,0,0,0,0,0,0,127,255,128,0,0,0,0,0,3,255,255,128,0,0,0,0,0,31,255,255,128,0,0,0,0,0,255,255,255,128,0,0,0,0,7,255,255,255,128,0,0,0,0,127,255,255,255,128,0,0,0,15,255,255,255,255,128,0,0,0,15,255,255,255,255,128,0,0,0,15,255,255,255,255,128,0,0,0,15,255,255,255,255,128,0,0,0,15,255,255,255,255,128,0,0,0,15,255,255,255,255,128,0,0,0,15,255,255,255,255,128,0,0,0,15,255,255,255,255,128,0,0,0,15,255,255,255,255,128,0,0,0,15,255,255,255,255,128,0,0,0,15,255,255,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,15,255,255,255,255,255,255,224,0,15,255,255,255,255,255,255,224,0,15,255,255,255,255,255,255,224,0,15,255,255,255,255,255,255,224,0,15,255,255,255,255,255,255,224,0,15,255,255,255,255,255,255,224,0,15,255,255,255,255,255,255,224,0,15,255,255,255,255,255,255,224,0,15,255,255,255,255,255,255,224,0,15,255,255,255,255,255,255,224,0,15,255,255,255,255,255,255,224,0,15,255,255,255,255,255,255,224,0,15,255,255,255,255,255,255,224,0,0,0,3,255,240,0,0,0,0,0,0,127,255,255,0,0,0,0,0,3,255,255,255,224,0,0,0,0,31,255,255,255,248,0,0,0,0,127,255,255,255,254,0,0,0,0,255,255,255,255,255,0,0,0,1,255,255,255,255,255,128,0,0,7,255,255,255,255,255,192,0,0,15,255,255,255,255,255,224,0,0,31,255,255,255,255,255,240,0,0,31,255,255,255,255,255,248,0,0,63,255,255,255,255,255,252,0,0,127,255,255,255,255,255,252,0,0,127,255,254,0,127,255,254,0,0,255,255,248,0,31,255,254,0,0,255,255,224,0,15,255,254,0,1,255,255,192,0,7,255,255,0,1,255,255,128,0,3,255,255,0,1,255,255,128,0,1,255,255,0,1,255,255,0,0,1,255,255,128,3,255,255,0,0,0,255,255,128,3,255,254,0,0,0,255,255,128,3,255,254,0,0,0,255,255,128,3,255,254,0,0,0,255,255,128,3,255,254,0,0,0,255,255,128,3,255,254,0,0,0,255,255,128,3,255,254,0,0,0,255,255,128,3,255,254,0,0,1,255,255,128,3,255,254,0,0,1,255,255,128,1,255,254,0,0,1,255,255,128,0,0,0,0,0,3,255,255,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,7,255,255,0,0,0,0,0,0,7,255,254,0,0,0,0,0,0,15,255,254,0,0,0,0,0,0,31,255,254,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,127,255,252,0,0,0,0,0,0,255,255,248,0,0,0,0,0,1,255,255,240,0,0,0,0,0,3,255,255,240,0,0,0,0,0,7,255,255,224,0,0,0,0,0,15,255,255,192,0,0,0,0,0,63,255,255,128,0,0,0,0,0,127,255,255,0,0,0,0,0,0,255,255,254,0,0,0,0,0,1,255,255,252,0,0,0,0,0,7,255,255,248,0,0,0,0,0,15,255,255,240,0,0,0,0,0,31,255,255,224,0,0,0,0,0,63,255,255,192,0,0,0,0,0,255,255,255,128,0,0,0,0,1,255,255,254,0,0,0,0,0,3,255,255,252,0,0,0,0,0,15,255,255,248,0,0,0,0,0,31,255,255,224,0,0,0,0,0,63,255,255,192,0,0,0,0,0,127,255,255,128,0,0,0,0,0,255,255,254,0,0,0,0,0,1,255,255,252,0,0,0,0,0,3,255,255,240,0,0,0,0,0,7,255,255,224,0,0,0,0,0,15,255,255,192,0,0,0,0,0,15,255,255,0,0,0,0,0,0,31,255,254,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,63,255,248,0,0,0,0,0,0,127,255,240,0,0,0,0,0,0,255,255,224,0,0,0,0,0,0,255,255,255,255,255,255,255,192,0,255,255,255,255,255,255,255,192,1,255,255,255,255,255,255,255,192,1,255,255,255,255,255,255,255,192,1,255,255,255,255,255,255,255,192,3,255,255,255,255,255,255,255,192,3,255,255,255,255,255,255,255,192,3,255,255,255,255,255,255,255,192,3,255,255,255,255,255,255,255,192,3,255,255,255,255,255,255,255,192,3,255,255,255,255,255,255,255,192,3,255,255,255,255,255,255,255,192,3,255,255,255,255,255,255,255,192,3,255,255,255,255,255,255,255,192,3,255,255,255,255,255,255,255,192,0,0,0,3,255,192,0,0,0,0,0,0,127,255,254,0,0,0,0,0,3,255,255,255,192,0,0,0,0,31,255,255,255,240,0,0,0,0,63,255,255,255,252,0,0,0,0,255,255,255,255,254,0,0,0,1,255,255,255,255,255,128,0,0,3,255,255,255,255,255,192,0,0,15,255,255,255,255,255,224,0,0,15,255,255,255,255,255,240,0,0,31,255,255,255,255,255,240,0,0,63,255,255,255,255,255,248,0,0,63,255,255,255,255,255,252,0,0,127,255,254,1,255,255,252,0,0,127,255,240,0,63,255,252,0,0,255,255,224,0,31,255,254,0,0,255,255,192,0,15,255,254,0,0,255,255,128,0,7,255,254,0,1,255,255,0,0,3,255,255,0,1,255,255,0,0,3,255,255,0,1,255,254,0,0,3,255,255,0,1,255,254,0,0,1,255,255,0,1,255,254,0,0,1,255,255,0,1,255,254,0,0,1,255,255,0,1,255,254,0,0,1,255,255,0,1,255,254,0,0,1,255,255,0,0,0,0,0,0,1,255,255,0,0,0,0,0,0,1,255,255,0,0,0,0,0,0,3,255,254,0,0,0,0,0,0,3,255,254,0,0,0,0,0,0,3,255,254,0,0,0,0,0,0,7,255,252,0,0,0,0,0,0,15,255,252,0,0,0,0,0,0,31,255,248,0,0,0,0,0,0,127,255,240,0,0,0,0,0,1,255,255,224,0,0,0,0,15,255,255,255,224,0,0,0,0,15,255,255,255,128,0,0,0,0,15,255,255,255,0,0,0,0,0,15,255,255,254,0,0,0,0,0,15,255,255,248,0,0,0,0,0,15,255,255,224,0,0,0,0,0,15,255,255,240,0,0,0,0,0,15,255,255,254,0,0,0,0,0,15,255,255,255,128,0,0,0,0,15,255,255,255,192,0,0,0,0,15,255,255,255,224,0,0,0,0,15,255,255,255,240,0,0,0,0,15,255,255,255,248,0,0,0,0,0,0,255,255,252,0,0,0,0,0,0,31,255,254,0,0,0,0,0,0,7,255,254,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,1,255,255,0,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,127,255,192,0,0,0,0,0,0,127,255,192,7,255,248,0,0,0,127,255,192,7,255,248,0,0,0,127,255,192,7,255,248,0,0,0,127,255,192,7,255,248,0,0,0,127,255,192,7,255,248,0,0,0,127,255,192,7,255,248,0,0,0,127,255,192,7,255,252,0,0,0,255,255,192,3,255,252,0,0,0,255,255,192,3,255,252,0,0,1,255,255,128,3,255,254,0,0,1,255,255,128,3,255,255,0,0,3,255,255,128,1,255,255,128,0,7,255,255,128,1,255,255,224,0,31,255,255,0,0,255,255,252,0,255,255,255,0,0,255,255,255,255,255,255,254,0,0,127,255,255,255,255,255,252,0,0,127,255,255,255,255,255,252,0,0,63,255,255,255,255,255,248,0,0,31,255,255,255,255,255,240,0,0,15,255,255,255,255,255,224,0,0,7,255,255,255,255,255,192,0,0,1,255,255,255,255,255,0,0,0,0,255,255,255,255,254,0,0,0,0,63,255,255,255,248,0,0,0,0,7,255,255,255,224,0,0,0,0,0,255,255,255,0,0,0,0,0,0,7,255,224,0,0,0,0,0,0,63,255,192,0,0,0,0,0,0,255,255,128,0,0,0,0,0,1,255,255,0,0,0,0,0,0,7,255,254,0,0,0,0,0,0,15,255,248,0,0,0,0,0,0,31,255,240,0,0,0,0,0,0,63,255,224,0,0,0,0,0,0,255,255,192,0,0,0,0,0,1,255,255,0,0,0,0,0,0,3,255,254,0,0,0,0,0,0,7,255,252,0,0,0,0,0,0,31,255,240,0,0,0,0,0,0,63,255,224,7,248,0,0,0,0,127,255,192,15,240,0,0,0,1,255,255,0,63,224,0,0,0,3,255,254,0,127,192,0,0,0,7,255,248,0,255,128,0,0,0,31,255,240,3,255,0,0,0,0,63,255,224,7,254,0,0,0,0,127,255,128,31,252,0,0,0,1,255,255,0,63,248,0,0,0,3,255,252,0,127,240,0,0,0,15,255,248,1,255,224,0,0,0,31,255,224,3,255,192,0,0,0,63,255,192,15,255,128,0,0,0,255,255,128,31,255,0,0,0,1,255,254,0,127,254,0,0,0,7,255,252,0,255,252,0,0,0,15,255,240,1,255,248,0,0,0,31,255,224,7,255,240,0,0,0,127,255,128,15,255,224,0,0,0,255,255,0,63,255,192,0,0,3,255,252,0,127,255,128,0,0,7,255,248,1,255,255,0,0,0,31,255,224,3,255,254,0,0,0,63,255,192,7,255,252,0,0,0,255,255,0,15,255,248,0,0,1,255,252,0,31,255,240,0,0,7,255,248,0,63,255,224,0,0,15,255,224,0,127,255,192,0,0,63,255,192,0,255,255,128,0,0,127,255,0,1,255,255,0,0,1,255,254,0,3,255,254,0,0,3,255,248,0,7,255,252,0,0,15,255,224,0,15,255,248,0,0,31,255,192,0,31,255,240,0,0,127,255,0,0,63,255,224,0,0,255,254,0,0,127,255,192,0,3,255,248,0,0,255,255,128,0,7,255,224,0,1,255,255,0,0,31,255,192,0,3,255,254,0,0,127,255,0,0,7,255,252,0,0,255,252,0,0,15,255,248,0,3,255,240,0,0,31,255,240,0,7,255,255,255,255,255,255,255,248,15,255,255,255,255,255,255,255,240,31,255,255,255,255,255,255,255,224,63,255,255,255,255,255,255,255,192,127,255,255,255,255,255,255,255,128,255,255,255,255,255,255,255,255,1,255,255,255,255,255,255,255,254,3,255,255,255,255,255,255,255,252,7,255,255,255,255,255,255,255,248,15,255,255,255,255,255,255,255,240,31,255,255,255,255,255,255,255,224,63,255,255,255,255,255,255,255,192,127,255,255,255,255,255,255,255,128,0,0,0,0,7,255,252,0,0,0,0,0,0,15,255,248,0,0,0,0,0,0,31,255,240,0,0,0,0,0,0,63,255,224,0,0,0,0,0,0,127,255,192,0,0,0,0,0,0,255,255,128,0,0,0,0,0,1,255,255,0,0,0,0,0,0,3,255,254,0,0,0,0,0,0,7,255,252,0,0,0,0,0,0,15,255,248,0,0,0,0,0,0,31,255,240,0,0,0,0,0,0,63,255,224,0,0,0,0,0,0,127,255,192,0,0,0,0,0,0,255,255,128,0,0,0,0,0,1,255,255,0,0,0,0,0,0,3,255,254,0,0,0,0,0,0,7,255,252,0,0,0,0,0,0,15,255,248,0,0,0,15,255,255,255,255,255,240,0,0,63,255,255,255,255,255,224,0,0,127,255,255,255,255,255,192,0,0,255,255,255,255,255,255,128,0,1,255,255,255,255,255,255,0,0,3,255,255,255,255,255,254,0,0,7,255,255,255,255,255,252,0,0,15,255,255,255,255,255,248,0,0,31,255,255,255,255,255,240,0,0,63,255,255,255,255,255,224,0,0,127,255,255,255,255,255,192,0,0,255,255,255,255,255,255,128,0,1,255,255,255,255,255,255,0,0,3,255,255,255,255,255,254,0,0,15,255,255,255,255,255,252,0,0,31,255,224,0,0,0,0,0,0,63,255,192,0,0,0,0,0,0,127,255,128,0,0,0,0,0,0,255,254,0,0,0,0,0,0,1,255,252,0,0,0,0,0,0,3,255,248,0,0,0,0,0,0,7,255,240,0,0,0,0,0,0,15,255,224,0,0,0,0,0,0,31,255,192,0,0,0,0,0,0,63,255,128,0,0,0,0,0,0,127,255,0,0,0,0,0,0,0,255,254,0,0,0,0,0,0,3,255,252,0,0,0,0,0,0,7,255,248,0,255,128,0,0,0,15,255,240,31,255,240,0,0,0,31,255,225,255,255,252,0,0,0,63,255,207,255,255,252,0,0,0,127,255,255,255,255,254,0,0,0,255,255,255,255,255,254,0,0,1,255,255,255,255,255,254,0,0,3,255,255,255,255,255,254,0,0,7,255,255,255,255,255,254,0,0,15,255,255,255,255,255,254,0,0,31,255,255,255,255,255,254,0,0,63,255,255,255,255,255,252,0,0,127,255,255,255,255,255,252,0,1,255,255,252,3,255,255,248,0,3,255,255,224,0,255,255,248,0,7,255,255,0,0,255,255,240,0,15,255,252,0,0,255,255,240,0,31,255,240,0,0,255,255,224,0,0,255,192,0,0,255,255,192,0,0,1,0,0,1,255,255,128,0,0,0,0,0,1,255,255,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,7,255,254,0,0,0,0,0,0,7,255,252,0,0,0,0,0,0,15,255,248,0,0,0,0,0,0,31,255,240,0,0,0,0,0,0,63,255,224,0,0,0,0,0,0,127,255,192,0,0,0,0,0,0,255,255,128,15,255,248,0,0,1,255,255,0,31,255,240,0,0,3,255,254,0,63,255,224,0,0,7,255,252,0,127,255,192,0,0,31,255,248,0,255,255,192,0,0,63,255,224,0,255,255,128,0,0,127,255,192,1,255,255,0,0,0,255,255,128,3,255,255,0,0,3,255,255,0,7,255,254,0,0,7,255,254,0,7,255,254,0,0,31,255,248,0,15,255,254,0,0,127,255,240,0,31,255,254,0,1,255,255,192,0,31,255,255,0,7,255,255,128,0,63,255,255,128,127,255,254,0,0,63,255,255,255,255,255,252,0,0,63,255,255,255,255,255,240,0,0,127,255,255,255,255,255,224,0,0,127,255,255,255,255,255,128,0,0,127,255,255,255,255,254,0,0,0,127,255,255,255,255,248,0,0,0,127,255,255,255,255,224,0,0,0,63,255,255,255,255,0,0,0,0,63,255,255,255,252,0,0,0,0,31,255,255,255,224,0,0,0,0,15,255,255,255,0,0,0,0,0,3,255,255,240,0,0,0,0,0,0,63,254,0,0,0,0,0,0,0,1,255,224,0,0,0,0,0,0,63,255,254,0,0,0,0,0,1,255,255,255,192,0,0,0,0,7,255,255,255,240,0,0,0,0,15,255,255,255,252,0,0,0,0,63,255,255,255,254,0,0,0,0,127,255,255,255,255,0,0,0,0,255,255,255,255,255,128,0,0,1,255,255,255,255,255,192,0,0,3,255,255,255,255,255,224,0,0,3,255,255,255,255,255,240,0,0,7,255,255,255,255,255,240,0,0,15,255,255,255,255,255,248,0,0,15,255,255,128,127,255,248,0,0,31,255,252,0,31,255,252,0,0,31,255,248,0,15,255,252,0,0,63,255,240,0,7,255,254,0,0,63,255,224,0,3,255,254,0,0,63,255,192,0,1,255,254,0,0,127,255,192,0,1,255,254,0,0,127,255,128,0,1,255,255,0,0,127,255,128,0,0,255,255,0,0,255,255,128,0,0,255,255,0,0,255,255,0,0,0,255,255,0,0,255,255,0,0,0,255,255,0,0,255,255,0,0,0,255,255,0,0,255,255,0,0,0,0,0,0,1,255,255,0,0,0,0,0,0,1,255,255,0,0,0,0,0,0,1,255,254,0,0,0,0,0,0,1,255,254,0,0,0,0,0,0,1,255,254,0,15,252,0,0,0,1,255,254,0,127,255,224,0,0,1,255,254,3,255,255,248,0,0,3,255,254,15,255,255,254,0,0,3,255,254,31,255,255,255,128,0,3,255,254,127,255,255,255,192,0,3,255,254,255,255,255,255,224,0,3,255,255,255,255,255,255,240,0,3,255,255,255,255,255,255,248,0,3,255,255,255,255,255,255,252,0,3,255,255,255,255,255,255,254,0,3,255,255,255,255,255,255,254,0,3,255,255,255,255,255,255,255,0,3,255,255,255,128,127,255,255,0,3,255,255,252,0,15,255,255,128,3,255,255,248,0,7,255,255,128,3,255,255,240,0,3,255,255,128,3,255,255,224,0,1,255,255,192,3,255,255,192,0,0,255,255,192,3,255,255,192,0,0,255,255,192,3,255,255,128,0,0,127,255,192,3,255,255,128,0,0,127,255,224,3,255,255,0,0,0,127,255,224,3,255,255,0,0,0,63,255,224,3,255,255,0,0,0,63,255,224,1,255,255,0,0,0,63,255,224,1,255,255,0,0,0,63,255,224,1,255,255,0,0,0,63,255,224,1,255,255,0,0,0,63,255,224,1,255,255,0,0,0,63,255,224,1,255,255,0,0,0,63,255,224,0,255,255,0,0,0,63,255,224,0,255,255,0,0,0,127,255,224,0,255,255,128,0,0,127,255,192,0,255,255,128,0,0,127,255,192,0,127,255,192,0,0,255,255,192,0,127,255,192,0,0,255,255,192,0,127,255,224,0,1,255,255,128,0,63,255,240,0,3,255,255,128,0,63,255,248,0,7,255,255,128,0,31,255,252,0,15,255,255,0,0,31,255,255,128,127,255,254,0,0,15,255,255,255,255,255,254,0,0,15,255,255,255,255,255,252,0,0,7,255,255,255,255,255,248,0,0,3,255,255,255,255,255,248,0,0,1,255,255,255,255,255,240,0,0,0,255,255,255,255,255,224,0,0,0,127,255,255,255,255,192,0,0,0,63,255,255,255,255,0,0,0,0,15,255,255,255,254,0,0,0,0,7,255,255,255,248,0,0,0,0,1,255,255,255,224,0,0,0,0,0,63,255,255,0,0,0,0,0,0,1,255,240,0,0,0,3,255,255,255,255,255,255,255,224,3,255,255,255,255,255,255,255,224,3,255,255,255,255,255,255,255,224,3,255,255,255,255,255,255,255,224,3,255,255,255,255,255,255,255,224,3,255,255,255,255,255,255,255,224,3,255,255,255,255,255,255,255,224,3,255,255,255,255,255,255,255,224,3,255,255,255,255,255,255,255,224,3,255,255,255,255,255,255,255,224,3,255,255,255,255,255,255,255,224,3,255,255,255,255,255,255,255,192,3,255,255,255,255,255,255,255,128,3,255,255,255,255,255,255,255,0,3,255,255,255,255,255,255,254,0,0,0,0,0,0,3,255,252,0,0,0,0,0,0,7,255,252,0,0,0,0,0,0,15,255,248,0,0,0,0,0,0,15,255,240,0,0,0,0,0,0,31,255,224,0,0,0,0,0,0,63,255,224,0,0,0,0,0,0,127,255,192,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,1,255,255,0,0,0,0,0,0,3,255,254,0,0,0,0,0,0,3,255,252,0,0,0,0,0,0,7,255,252,0,0,0,0,0,0,15,255,248,0,0,0,0,0,0,15,255,240,0,0,0,0,0,0,31,255,240,0,0,0,0,0,0,63,255,224,0,0,0,0,0,0,63,255,224,0,0,0,0,0,0,127,255,192,0,0,0,0,0,0,127,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,1,255,255,0,0,0,0,0,0,1,255,255,0,0,0,0,0,0,3,255,254,0,0,0,0,0,0,3,255,254,0,0,0,0,0,0,7,255,252,0,0,0,0,0,0,7,255,252,0,0,0,0,0,0,15,255,248,0,0,0,0,0,0,15,255,248,0,0,0,0,0,0,31,255,240,0,0,0,0,0,0,31,255,240,0,0,0,0,0,0,63,255,224,0,0,0,0,0,0,63,255,224,0,0,0,0,0,0,127,255,192,0,0,0,0,0,0,127,255,192,0,0,0,0,0,0,127,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,1,255,255,0,0,0,0,0,0,1,255,255,0,0,0,0,0,0,1,255,255,0,0,0,0,0,0,3,255,254,0,0,0,0,0,0,3,255,254,0,0,0,0,0,0,3,255,254,0,0,0,0,0,0,7,255,252,0,0,0,0,0,0,7,255,252,0,0,0,0,0,0,7,255,252,0,0,0,0,0,0,7,255,252,0,0,0,0,0,0,15,255,248,0,0,0,0,0,0,15,255,248,0,0,0,0,0,0,15,255,248,0,0,0,0,0,0,15,255,248,0,0,0,0,0,0,15,255,248,0,0,0,0,0,0,31,255,248,0,0,0,0,0,0,31,255,240,0,0,0,0,0,0,31,255,240,0,0,0,0,0,0,31,255,240,0,0,0,0,0,0,31,255,240,0,0,0,0,0,0,31,255,240,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,0,7,255,128,0,0,0,0,0,0,255,255,248,0,0,0,0,0,15,255,255,254,0,0,0,0,0,255,255,255,255,0,0,0,0,3,255,255,255,255,128,0,0,0,31,255,255,255,255,192,0,0,0,127,255,255,255,255,192,0,0,1,255,255,255,255,255,192,0,0,7,255,255,255,255,255,192,0,0,31,255,255,255,255,255,192,0,0,127,255,255,255,255,255,192,0,1,255,255,255,255,255,255,192,0,3,255,255,255,255,255,255,128,0,15,255,255,192,31,255,255,128,0,31,255,254,0,15,255,255,0,0,127,255,240,0,7,255,255,0,0,255,255,192,0,7,255,254,0,1,255,255,0,0,7,255,252,0,3,255,252,0,0,7,255,248,0,15,255,248,0,0,15,255,248,0,31,255,240,0,0,31,255,240,0,63,255,192,0,0,31,255,224,0,127,255,128,0,0,63,255,192,0,255,255,0,0,0,127,255,128,1,255,254,0,0,0,255,255,0,3,255,252,0,0,1,255,254,0,7,255,248,0,0,3,255,252,0,15,255,240,0,0,7,255,248,0,15,255,240,0,0,31,255,224,0,31,255,224,0,0,63,255,192,0,63,255,192,0,0,127,255,128,0,127,255,192,0,1,255,255,0,0,127,255,192,0,7,255,252,0,0,255,255,192,0,31,255,248,0,0,255,255,224,0,255,255,224,0,0,255,255,240,7,255,255,128,0,1,255,255,255,255,255,255,0,0,1,255,255,255,255,255,252,0,0,1,255,255,255,255,255,240,0,0,1,255,255,255,255,255,192,0,0,1,255,255,255,255,255,0,0,0,0,255,255,255,255,248,0,0,0,0,255,255,255,255,224,0,0,0,7,255,255,255,255,240,0,0,0,63,255,255,255,255,248,0,0,0,255,255,255,255,255,248,0,0,3,255,255,255,255,255,248,0,0,15,255,255,255,255,255,252,0,0,63,255,255,255,255,255,248,0,0,255,255,252,1,255,255,248,0,3,255,255,192,0,127,255,248,0,7,255,254,0,0,63,255,248,0,31,255,248,0,0,63,255,240,0,63,255,224,0,0,63,255,240,0,255,255,192,0,0,127,255,224,1,255,255,0,0,0,127,255,192,3,255,254,0,0,0,255,255,128,15,255,248,0,0,0,255,255,128,31,255,240,0,0,1,255,255,0,63,255,224,0,0,3,255,254,0,127,255,192,0,0,7,255,252,0,255,255,128,0,0,15,255,248,1,255,255,0,0,0,31,255,240,3,255,254,0,0,0,63,255,224,7,255,252,0,0,0,127,255,192,15,255,252,0,0,1,255,255,128,31,255,248,0,0,3,255,255,0,31,255,248,0,0,15,255,252,0,63,255,240,0,0,31,255,248,0,127,255,240,0,0,127,255,240,0,127,255,240,0,1,255,255,224,0,255,255,248,0,15,255,255,128,1,255,255,254,0,255,255,255,0,1,255,255,255,255,255,255,252,0,3,255,255,255,255,255,255,248,0,3,255,255,255,255,255,255,224,0,3,255,255,255,255,255,255,128,0,3,255,255,255,255,255,254,0,0,3,255,255,255,255,255,248,0,0,3,255,255,255,255,255,224,0,0,3,255,255,255,255,255,128,0,0,1,255,255,255,255,252,0,0,0,0,255,255,255,255,224,0,0,0,0,127,255,255,255,0,0,0,0,0,31,255,255,240,0,0,0,0,0,1,255,252,0,0,0,0,0,0,0,7,255,128,0,0,0,0,0,0,255,255,248,0,0,0,0,0,7,255,255,255,0,0,0,0,0,31,255,255,255,224,0,0,0,0,127,255,255,255,240,0,0,0,0,255,255,255,255,252,0,0,0,3,255,255,255,255,254,0,0,0,7,255,255,255,255,255,0,0,0,15,255,255,255,255,255,128,0,0,31,255,255,255,255,255,192,0,0,63,255,255,255,255,255,224,0,0,63,255,255,255,255,255,240,0,0,127,255,255,255,255,255,240,0,0,255,255,254,1,255,255,248,0,0,255,255,248,0,63,255,248,0,1,255,255,224,0,31,255,252,0,1,255,255,192,0,15,255,252,0,1,255,255,128,0,7,255,254,0,3,255,255,0,0,3,255,254,0,3,255,255,0,0,1,255,254,0,3,255,254,0,0,1,255,255,0,3,255,254,0,0,1,255,255,0,7,255,254,0,0,0,255,255,0,7,255,252,0,0,0,255,255,0,7,255,252,0,0,0,255,255,128,7,255,252,0,0,0,255,255,128,7,255,252,0,0,0,255,255,128,7,255,252,0,0,0,255,255,128,7,255,252,0,0,0,255,255,128,7,255,252,0,0,0,255,255,128,7,255,252,0,0,0,255,255,128,7,255,252,0,0,0,255,255,192,7,255,254,0,0,0,255,255,192,7,255,254,0,0,1,255,255,192,3,255,254,0,0,1,255,255,192,3,255,255,0,0,1,255,255,192,3,255,255,0,0,3,255,255,192,3,255,255,128,0,7,255,255,192,1,255,255,192,0,7,255,255,192,1,255,255,224,0,31,255,255,192,1,255,255,240,0,63,255,255,192,0,255,255,254,0,255,255,255,192,0,255,255,255,255,255,255,255,192,0,127,255,255,255,255,255,255,192,0,127,255,255,255,255,255,255,192,0,63,255,255,255,255,255,255,192,0,31,255,255,255,255,255,255,192,0,15,255,255,255,255,255,255,192,0,7,255,255,255,255,127,255,192,0,3,255,255,255,254,127,255,192,0,1,255,255,255,248,127,255,192,0,0,127,255,255,240,127,255,192,0,0,31,255,255,192,127,255,128,0,0,7,255,255,0,127,255,128,0,0,0,127,240,0,127,255,128,0,0,0,0,0,0,127,255,128,0,0,0,0,0,0,127,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,255,255,0,0,255,255,0,0,0,255,255,0,0,255,255,0,0,0,255,255,0,0,255,255,0,0,0,255,255,0,0,255,255,0,0,1,255,255,0,0,255,255,0,0,1,255,254,0,0,255,255,128,0,1,255,254,0,0,127,255,128,0,3,255,254,0,0,127,255,128,0,3,255,252,0,0,127,255,192,0,7,255,252,0,0,127,255,224,0,15,255,252,0,0,63,255,240,0,31,255,248,0,0,63,255,248,0,63,255,248,0,0,31,255,254,0,255,255,240,0,0,31,255,255,255,255,255,240,0,0,15,255,255,255,255,255,224,0,0,15,255,255,255,255,255,192,0,0,7,255,255,255,255,255,192,0,0,3,255,255,255,255,255,128,0,0,1,255,255,255,255,255,0,0,0,0,255,255,255,255,254,0,0,0,0,127,255,255,255,252,0,0,0,0,63,255,255,255,240,0,0,0,0,15,255,255,255,224,0,0,0,0,3,255,255,255,128,0,0,0,0,0,127,255,252,0,0,0,0,0,0,7,255,128,0,0,0,0,0,0,255,240,0,0,0,0,0,0,0,0,0,0,0,0,127,255,224,0,0,0,0,3,255,128,0,0,0,0,31,255,255,192,0,0,0,0,127,248,0,0,0,0,7,255,255,254,0,0,0,0,7,255,0,0,0,0,0,255,255,255,240,0,0,0,0,255,224,0,0,0,0,31,255,255,255,128,0,0,0,31,254,0,0,0,0,3,255,255,255,252,0,0,0,1,255,192,0,0,0,0,127,255,255,255,224,0,0,0,63,248,0,0,0,0,7,255,255,255,254,0,0,0,7,255,128,0,0,0,0,255,254,7,255,240,0,0,0,127,240,0,0,0,0,15,255,128,31,255,0,0,0,15,254,0,0,0,0,1,255,240,0,255,248,0,0,1,255,224,0,0,0,0,31,255,0,15,255,128,0,0,63,252,0,0,0,0,1,255,224,0,127,248,0,0,3,255,128,0,0,0,0,31,254,0,7,255,128,0,0,127,248,0,0,0,0,3,255,224,0,127,252,0,0,15,255,0,0,0,0,0,63,252,0,3,255,192,0,0,255,224,0,0,0,0,3,255,192,0,63,252,0,0,31,252,0,0,0,0,0,63,252,0,3,255,192,0,3,255,192,0,0,0,0,3,255,192,0,63,252,0,0,127,248,0,0,0,0,0,63,252,0,3,255,192,0,7,255,0,0,0,0,0,3,255,192,0,63,252,0,0,255,240,0,0,0,0,0,63,252,0,3,255,192,0,31,254,0,0,0,0,0,3,255,192,0,63,252,0,1,255,192,0,0,0,0,0,63,252,0,3,255,192,0,63,252,0,0,0,0,0,3,255,192,0,63,252,0,7,255,128,0,0,0,0,0,63,252,0,3,255,192,0,127,240,0,0,0,0,0,3,255,192,0,63,252,0,15,254,0,0,0,0,0,0,63,254,0,7,255,192,1,255,224,0,0,0,0,0,1,255,224,0,127,248,0,63,252,0,0,0,0,0,0,31,254,0,7,255,128,3,255,128,0,0,0,0,0,1,255,240,0,255,248,0,127,248,0,0,0,0,0,0,31,255,0,15,255,128,15,255,0,0,0,0,0,0,0,255,248,1,255,240,0,255,224,0,0,0,0,0,0,15,255,224,127,255,0,31,254,0,0,0,0,0,0,0,127,255,255,255,224,3,255,192,0,0,0,0,0,0,7,255,255,255,254,0,63,248,0,0,0,0,0,0,0,63,255,255,255,192,7,255,0,0,0,0,0,0,0,1,255,255,255,248,0,255,240,0,0,0,0,0,0,0,15,255,255,255,0,31,254,0,0,0,0,0,0,0,0,127,255,255,224,1,255,192,0,0,0,0,0,0,0,1,255,255,252,0,63,252,0,0,0,0,0,0,0,0,7,255,254,0,7,255,128,0,63,248,0,0,0,0,0,15,255,0,0,127,240,0,31,255,248,0,0,0,0,0,0,0,0,15,255,0,15,255,255,224,0,0,0,0,0,0,0,1,255,224,1,255,255,255,0,0,0,0,0,0,0,0,63,252,0,63,255,255,252,0,0,0,0,0,0,0,3,255,192,7,255,255,255,192,0,0,0,0,0,0,0,127,248,0,255,255,255,254,0,0,0,0,0,0,0,15,255,0,31,255,255,255,240,0,0,0,0,0,0,0,255,224,1,255,255,255,255,0,0,0,0,0,0,0,31,254,0,63,255,131,255,248,0,0,0,0,0,0,3,255,192,3,255,224,15,255,128,0,0,0,0,0,0,63,248,0,127,252,0,127,252,0,0,0,0,0,0,7,255,128,7,255,192,3,255,192,0,0,0,0,0,0,255,240,0,127,248,0,63,252,0,0,0,0,0,0,31,254,0,7,255,128,3,255,192,0,0,0,0,0,1,255,224,0,255,248,0,63,254,0,0,0,0,0,0,63,252,0,15,255,0,1,255,224,0,0,0,0,0,7,255,128,0,255,240,0,31,254,0,0,0,0,0,0,127,240,0,15,255,0,1,255,224,0,0,0,0,0,15,255,0,0,255,240,0,31,254,0,0,0,0,0,1,255,224,0,15,255,0,1,255,224,0,0,0,0,0,31,252,0,0,255,240,0,31,254,0,0,0,0,0,3,255,192,0,15,255,0,1,255,224,0,0,0,0,0,127,248,0,0,255,240,0,31,254,0,0,0,0,0,15,255,0,0,15,255,0,1,255,224,0,0,0,0,0,255,240,0,0,255,240,0,31,254,0,0,0,0,0,31,254,0,0,15,255,0,1,255,224,0,0,0,0,3,255,192,0,0,255,240,0,31,254,0,0,0,0,0,63,248,0,0,15,255,128,3,255,224,0,0,0,0,7,255,128,0,0,127,248,0,63,252,0,0,0,0,0,255,240,0,0,7,255,128,3,255,192,0,0,0,0,31,254,0,0,0,127,252,0,63,252,0,0,0,0,1,255,224,0,0,7,255,192,7,255,192,0,0,0,0,63,252,0,0,0,63,254,0,255,248,0,0,0,0,7,255,128,0,0,3,255,248,63,255,128,0,0,0,0,127,248,0,0,0,31,255,255,255,240,0,0,0,0,15,255,0,0,0,1,255,255,255,255,0,0,0,0,1,255,224,0,0,0,15,255,255,255,224,0,0,0,0,31,252,0,0,0,0,127,255,255,252,0,0,0,0,3,255,192,0,0,0,3,255,255,255,192,0,0,0,0,127,248,0,0,0,0,31,255,255,240,0,0,0,0,15,255,0,0,0,0,0,255,255,254,0,0,0,0,0,255,224,0,0,0,0,1,255,255,128,0,0,0,0,0,0,0,0,0,0,0,3,255,128,0,0,0,31,255,240,0,0,63,255,224,0,0,127,255,192,0,0,255,255,128,0,1,255,255,0,0,3,255,254,0,0,7,255,252,0,0,15,255,248,0,0,31,255,240,0,0,63,255,224,0,0,127,255,192,0,0,255,255,128,0,1,255,255,0,0,3,255,254,0,0,7,255,252,0,0,15,255,248,0,0,31,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,255,252,0,0,15,255,248,0,0,31,255,240,0,0,63,255,224,0,0,127,255,192,0,0,255,255,128,0,1,255,255,0,0,3,255,254,0,0,7,255,252,0,0,15,255,248,0,0,31,255,240,0,0,63,255,224,0,0,127,255,192,0,0,255,255,128,0,1,255,255,0,0,3,255,254,0,0,7,255,252,0,0,7,255,255,255,224,7,255,255,255,224,7,255,255,255,224,7,255,255,255,224,7,255,255,255,224,7,255,255,255,224,7,255,255,255,224,7,255,255,255,224,7,255,255,255,224,7,255,255,255,224,7,255,255,255,224,7,255,255,255,224,7,255,255,255,224,7,255,255,255,224,7,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,224,0,1,255,255,0,0,15,255,248,0,0,127,255,192,0,3,255,254,0,0,31,255,240,0,0,255,255,128,0,7,255,252,0,0,63,255,224,0,1,255,255,0,0,15,255,248,0,0,127,255,192,0,3,255,254,0,0,31,255,240,0,0,255,255,128,0,7,255,252,0,0,63,255,224,0}; +static const Glyph f0_120_glyphs[]={{0,72,86,0,0,72,48},{774,72,83,0,2,72,49},{1521,72,84,0,1,72,50},{2277,72,86,0,0,72,51},{3051,71,85,0,0,71,52},{3806,71,84,0,2,71,53},{4552,72,86,0,0,72,54},{5326,72,83,0,2,72,55},{6073,71,86,0,0,71,56},{6837,72,86,0,0,72,57},{7611,116,86,0,0,116,37},{8858,39,63,0,22,39,58},{9166,40,40,0,45,40,45},{9366,37,17,0,68,37,46},{9445,24,0,0,85,24,32}}; +static const uint8_t f0_124_bits[]={0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,0,0,0,0,0,0,7,255,255,248,0,0,0,0,0,15,255,255,255,192,0,0,0,0,15,255,255,255,252,0,0,0,0,7,255,255,255,255,128,0,0,0,7,255,255,255,255,248,0,0,0,3,255,255,255,255,255,0,0,0,1,255,255,255,255,255,224,0,0,0,127,255,255,255,255,248,0,0,0,63,255,255,255,255,255,0,0,0,31,255,255,255,255,255,224,0,0,15,255,255,255,255,255,252,0,0,3,255,255,255,255,255,255,0,0,1,255,255,248,7,255,255,224,0,0,127,255,248,0,127,255,248,0,0,63,255,248,0,7,255,255,0,0,15,255,252,0,0,255,255,192,0,3,255,255,0,0,63,255,240,0,1,255,255,128,0,7,255,254,0,0,127,255,192,0,0,255,255,128,0,31,255,240,0,0,63,255,224,0,15,255,252,0,0,15,255,252,0,3,255,254,0,0,1,255,255,0,0,255,255,128,0,0,127,255,192,0,63,255,224,0,0,31,255,240,0,31,255,248,0,0,7,255,254,0,7,255,252,0,0,1,255,255,128,1,255,255,0,0,0,63,255,224,0,127,255,192,0,0,15,255,248,0,31,255,240,0,0,3,255,254,0,7,255,252,0,0,0,255,255,128,1,255,255,0,0,0,63,255,224,0,255,255,192,0,0,15,255,252,0,63,255,240,0,0,3,255,255,0,15,255,252,0,0,0,255,255,192,3,255,255,0,0,0,63,255,240,0,255,255,192,0,0,15,255,252,0,63,255,240,0,0,3,255,255,0,15,255,252,0,0,0,255,255,192,3,255,255,0,0,0,63,255,240,0,255,255,192,0,0,15,255,252,0,63,255,240,0,0,3,255,255,0,15,255,252,0,0,0,255,255,192,3,255,255,0,0,0,63,255,240,0,255,255,192,0,0,15,255,252,0,63,255,240,0,0,3,255,255,0,15,255,252,0,0,0,255,255,192,3,255,255,0,0,0,63,255,240,0,255,255,192,0,0,15,255,252,0,63,255,240,0,0,3,255,255,0,15,255,252,0,0,0,255,255,192,3,255,255,0,0,0,63,255,240,0,255,255,192,0,0,15,255,252,0,63,255,240,0,0,3,255,255,0,15,255,252,0,0,0,255,255,192,1,255,255,0,0,0,63,255,224,0,127,255,192,0,0,15,255,248,0,31,255,240,0,0,3,255,254,0,7,255,252,0,0,0,255,255,128,1,255,255,0,0,0,63,255,224,0,127,255,192,0,0,15,255,248,0,31,255,248,0,0,7,255,254,0,3,255,254,0,0,1,255,255,0,0,255,255,128,0,0,127,255,192,0,63,255,224,0,0,31,255,240,0,15,255,248,0,0,7,255,252,0,3,255,255,0,0,3,255,255,0,0,127,255,192,0,0,255,255,128,0,31,255,248,0,0,127,255,224,0,7,255,254,0,0,31,255,248,0,0,255,255,192,0,15,255,252,0,0,63,255,248,0,3,255,255,0,0,15,255,255,0,3,255,255,192,0,1,255,255,224,1,255,255,224,0,0,127,255,255,135,255,255,248,0,0,15,255,255,255,255,255,252,0,0,1,255,255,255,255,255,254,0,0,0,127,255,255,255,255,255,128,0,0,15,255,255,255,255,255,192,0,0,1,255,255,255,255,255,224,0,0,0,63,255,255,255,255,240,0,0,0,7,255,255,255,255,248,0,0,0,0,255,255,255,255,252,0,0,0,0,31,255,255,255,254,0,0,0,0,1,255,255,255,254,0,0,0,0,0,31,255,255,254,0,0,0,0,0,0,255,255,252,0,0,0,0,0,0,3,255,240,0,0,0,0,0,0,0,0,0,31,192,0,0,0,0,0,0,0,63,240,0,0,0,0,0,0,0,31,252,0,0,0,0,0,0,0,31,255,0,0,0,0,0,0,0,63,255,192,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,127,255,252,0,0,0,0,0,0,127,255,255,0,0,0,0,0,0,255,255,255,192,0,0,0,0,3,255,255,255,240,0,0,0,0,15,255,255,255,252,0,0,0,0,63,255,255,255,255,0,0,0,0,15,255,255,255,255,192,0,0,0,3,255,255,255,255,240,0,0,0,0,255,255,255,255,252,0,0,0,0,63,255,255,255,255,0,0,0,0,15,255,255,255,255,192,0,0,0,3,255,255,255,255,240,0,0,0,0,255,255,255,255,252,0,0,0,0,63,255,255,255,255,0,0,0,0,15,255,255,255,255,192,0,0,0,3,255,255,255,255,240,0,0,0,0,0,0,15,255,252,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,0,255,255,192,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,15,255,252,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,0,255,255,192,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,15,255,252,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,0,255,255,192,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,15,255,252,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,0,255,255,192,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,15,255,252,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,0,255,255,192,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,15,255,252,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,0,255,255,192,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,15,255,252,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,0,255,255,192,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,15,255,252,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,0,255,255,192,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,15,255,252,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,0,255,255,192,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,15,255,252,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,0,255,255,192,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,15,255,252,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,0,255,255,192,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,15,255,252,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,0,255,255,192,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,15,255,252,0,0,0,0,0,0,3,255,255,0,0,0,0,15,255,255,255,255,255,255,248,0,3,255,255,255,255,255,255,254,0,0,255,255,255,255,255,255,255,128,0,63,255,255,255,255,255,255,224,0,15,255,255,255,255,255,255,248,0,3,255,255,255,255,255,255,254,0,0,255,255,255,255,255,255,255,128,0,63,255,255,255,255,255,255,224,0,15,255,255,255,255,255,255,248,0,3,255,255,255,255,255,255,254,0,0,255,255,255,255,255,255,255,128,0,63,255,255,255,255,255,255,224,0,15,255,255,255,255,255,255,248,0,3,255,255,255,255,255,255,254,0,0,0,0,1,255,248,0,0,0,0,0,0,15,255,255,224,0,0,0,0,0,31,255,255,255,0,0,0,0,0,31,255,255,255,240,0,0,0,0,63,255,255,255,255,0,0,0,0,31,255,255,255,255,224,0,0,0,31,255,255,255,255,254,0,0,0,15,255,255,255,255,255,192,0,0,7,255,255,255,255,255,248,0,0,3,255,255,255,255,255,255,0,0,1,255,255,255,255,255,255,192,0,0,127,255,255,255,255,255,248,0,0,63,255,255,255,255,255,255,0,0,31,255,255,248,63,255,255,192,0,7,255,255,224,1,255,255,248,0,3,255,255,224,0,31,255,254,0,0,255,255,240,0,3,255,255,128,0,63,255,248,0,0,127,255,240,0,31,255,252,0,0,15,255,252,0,7,255,254,0,0,3,255,255,0,1,255,255,128,0,0,127,255,224,0,127,255,224,0,0,31,255,248,0,31,255,240,0,0,7,255,254,0,7,255,252,0,0,1,255,255,128,3,255,255,0,0,0,127,255,224,0,255,255,192,0,0,31,255,248,0,63,255,240,0,0,7,255,254,0,15,255,252,0,0,1,255,255,128,3,255,255,0,0,0,127,255,224,0,127,255,192,0,0,31,255,248,0,31,255,240,0,0,15,255,254,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,1,255,255,192,0,0,0,0,0,0,127,255,240,0,0,0,0,0,0,63,255,248,0,0,0,0,0,0,31,255,254,0,0,0,0,0,0,7,255,255,128,0,0,0,0,0,3,255,255,192,0,0,0,0,0,1,255,255,240,0,0,0,0,0,0,255,255,248,0,0,0,0,0,0,127,255,254,0,0,0,0,0,0,63,255,255,0,0,0,0,0,0,63,255,255,128,0,0,0,0,0,31,255,255,224,0,0,0,0,0,15,255,255,240,0,0,0,0,0,7,255,255,248,0,0,0,0,0,3,255,255,252,0,0,0,0,0,3,255,255,254,0,0,0,0,0,1,255,255,255,0,0,0,0,0,0,255,255,255,128,0,0,0,0,0,255,255,255,192,0,0,0,0,0,127,255,255,224,0,0,0,0,0,63,255,255,240,0,0,0,0,0,31,255,255,248,0,0,0,0,0,31,255,255,248,0,0,0,0,0,15,255,255,252,0,0,0,0,0,7,255,255,254,0,0,0,0,0,3,255,255,254,0,0,0,0,0,1,255,255,255,0,0,0,0,0,1,255,255,255,128,0,0,0,0,0,255,255,255,128,0,0,0,0,0,63,255,255,192,0,0,0,0,0,31,255,255,224,0,0,0,0,0,15,255,255,224,0,0,0,0,0,7,255,255,240,0,0,0,0,0,3,255,255,240,0,0,0,0,0,1,255,255,248,0,0,0,0,0,0,127,255,252,0,0,0,0,0,0,63,255,254,0,0,0,0,0,0,31,255,255,0,0,0,0,0,0,7,255,255,128,0,0,0,0,0,3,255,255,192,0,0,0,0,0,0,255,255,255,255,255,255,255,240,0,63,255,255,255,255,255,255,252,0,31,255,255,255,255,255,255,255,0,7,255,255,255,255,255,255,255,192,1,255,255,255,255,255,255,255,240,0,255,255,255,255,255,255,255,252,0,63,255,255,255,255,255,255,255,0,15,255,255,255,255,255,255,255,192,3,255,255,255,255,255,255,255,240,0,255,255,255,255,255,255,255,252,0,63,255,255,255,255,255,255,255,0,15,255,255,255,255,255,255,255,192,3,255,255,255,255,255,255,255,240,0,255,255,255,255,255,255,255,252,0,63,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,7,255,252,0,0,0,0,0,0,63,255,255,224,0,0,0,0,0,255,255,255,254,0,0,0,0,1,255,255,255,255,192,0,0,0,3,255,255,255,255,248,0,0,0,3,255,255,255,255,255,0,0,0,3,255,255,255,255,255,192,0,0,7,255,255,255,255,255,240,0,0,7,255,255,255,255,255,252,0,0,3,255,255,255,255,255,255,0,0,3,255,255,255,255,255,255,128,0,3,255,255,255,255,255,255,224,0,1,255,255,255,255,255,255,248,0,1,255,255,252,3,255,255,252,0,0,255,255,240,0,63,255,254,0,0,255,255,240,0,7,255,255,128,0,127,255,240,0,1,255,255,192,0,63,255,240,0,0,127,255,224,0,63,255,240,0,0,63,255,248,0,31,255,248,0,0,15,255,252,0,15,255,248,0,0,7,255,254,0,7,255,252,0,0,3,255,255,0,3,255,254,0,0,1,255,255,128,1,255,255,0,0,0,255,255,192,0,255,255,128,0,0,127,255,224,0,127,255,192,0,0,63,255,240,0,0,0,0,0,0,31,255,248,0,0,0,0,0,0,15,255,248,0,0,0,0,0,0,7,255,252,0,0,0,0,0,0,7,255,254,0,0,0,0,0,0,3,255,254,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,3,255,255,128,0,0,0,0,0,3,255,255,128,0,0,0,0,0,15,255,255,128,0,0,0,0,63,255,255,255,128,0,0,0,0,31,255,255,255,128,0,0,0,0,15,255,255,255,128,0,0,0,0,7,255,255,255,0,0,0,0,0,3,255,255,255,0,0,0,0,0,1,255,255,254,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,127,255,255,240,0,0,0,0,0,63,255,255,252,0,0,0,0,0,31,255,255,255,128,0,0,0,0,15,255,255,255,224,0,0,0,0,7,255,255,255,248,0,0,0,0,3,255,255,255,254,0,0,0,0,0,0,127,255,255,128,0,0,0,0,0,3,255,255,224,0,0,0,0,0,0,127,255,248,0,0,0,0,0,0,31,255,252,0,0,0,0,0,0,7,255,255,0,0,0,0,0,0,1,255,255,128,0,0,0,0,0,0,127,255,192,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,15,255,248,0,0,0,0,0,0,7,255,252,0,0,0,0,0,0,3,255,254,0,63,255,224,0,0,1,255,255,0,31,255,240,0,0,0,255,255,192,15,255,248,0,0,0,127,255,224,7,255,252,0,0,0,63,255,240,3,255,254,0,0,0,31,255,248,1,255,255,0,0,0,15,255,248,0,255,255,192,0,0,15,255,252,0,127,255,224,0,0,7,255,254,0,31,255,240,0,0,3,255,255,0,15,255,252,0,0,3,255,255,128,7,255,255,0,0,3,255,255,192,1,255,255,192,0,3,255,255,192,0,255,255,240,0,3,255,255,224,0,127,255,254,0,7,255,255,224,0,31,255,255,240,63,255,255,240,0,15,255,255,255,255,255,255,240,0,3,255,255,255,255,255,255,248,0,0,255,255,255,255,255,255,248,0,0,63,255,255,255,255,255,248,0,0,15,255,255,255,255,255,248,0,0,3,255,255,255,255,255,248,0,0,0,255,255,255,255,255,248,0,0,0,63,255,255,255,255,240,0,0,0,7,255,255,255,255,240,0,0,0,0,255,255,255,255,224,0,0,0,0,31,255,255,255,192,0,0,0,0,1,255,255,255,0,0,0,0,0,0,7,255,240,0,0,0,0,0,0,0,31,255,240,0,0,0,0,0,0,15,255,252,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,0,255,255,192,0,0,0,0,0,0,63,255,224,0,0,0,0,0,0,31,255,248,0,0,0,0,0,0,7,255,254,0,0,0,0,0,0,1,255,255,128,0,0,0,0,0,0,127,255,192,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,15,255,252,0,0,0,0,0,0,3,255,254,0,0,0,0,0,0,1,255,255,128,31,240,0,0,0,0,127,255,192,7,252,0,0,0,0,31,255,240,1,255,0,0,0,0,15,255,252,0,255,192,0,0,0,3,255,254,0,63,240,0,0,0,0,255,255,128,15,252,0,0,0,0,127,255,224,7,255,0,0,0,0,31,255,240,1,255,192,0,0,0,7,255,252,0,255,240,0,0,0,3,255,254,0,63,252,0,0,0,0,255,255,128,15,255,0,0,0,0,127,255,192,7,255,192,0,0,0,31,255,240,1,255,240,0,0,0,7,255,248,0,255,252,0,0,0,3,255,254,0,63,255,0,0,0,0,255,255,0,31,255,192,0,0,0,127,255,192,7,255,240,0,0,0,31,255,224,3,255,252,0,0,0,7,255,248,0,255,255,0,0,0,3,255,252,0,127,255,192,0,0,0,255,255,0,31,255,240,0,0,0,127,255,128,15,255,252,0,0,0,31,255,224,3,255,255,0,0,0,15,255,240,0,255,255,192,0,0,3,255,252,0,63,255,240,0,0,1,255,254,0,15,255,252,0,0,0,127,255,128,3,255,255,0,0,0,63,255,192,0,255,255,192,0,0,15,255,240,0,63,255,240,0,0,7,255,248,0,15,255,252,0,0,1,255,252,0,3,255,255,0,0,0,255,255,0,0,255,255,192,0,0,63,255,128,0,63,255,240,0,0,31,255,224,0,15,255,252,0,0,7,255,240,0,3,255,255,0,0,3,255,248,0,0,255,255,192,0,0,255,254,0,0,63,255,240,0,0,127,255,0,0,15,255,252,0,0,63,255,128,0,3,255,255,0,0,15,255,224,0,0,255,255,192,0,7,255,240,0,0,63,255,240,0,1,255,248,0,0,15,255,252,0,0,255,254,0,0,3,255,255,0,0,63,255,255,255,255,255,255,255,240,15,255,255,255,255,255,255,255,252,3,255,255,255,255,255,255,255,255,0,255,255,255,255,255,255,255,255,192,63,255,255,255,255,255,255,255,240,15,255,255,255,255,255,255,255,252,3,255,255,255,255,255,255,255,255,0,255,255,255,255,255,255,255,255,192,63,255,255,255,255,255,255,255,240,15,255,255,255,255,255,255,255,252,3,255,255,255,255,255,255,255,255,0,255,255,255,255,255,255,255,255,192,63,255,255,255,255,255,255,255,240,0,0,0,0,0,63,255,240,0,0,0,0,0,0,15,255,252,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,0,255,255,192,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,15,255,252,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,0,255,255,192,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,15,255,252,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,0,255,255,192,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,15,255,252,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,0,255,255,192,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,15,255,252,0,0,0,0,0,0,3,255,255,0,0,0,15,255,255,255,255,255,254,0,0,15,255,255,255,255,255,255,0,0,7,255,255,255,255,255,255,128,0,3,255,255,255,255,255,255,192,0,1,255,255,255,255,255,255,224,0,0,255,255,255,255,255,255,240,0,0,127,255,255,255,255,255,248,0,0,63,255,255,255,255,255,252,0,0,31,255,255,255,255,255,254,0,0,15,255,255,255,255,255,255,0,0,7,255,255,255,255,255,255,128,0,3,255,255,255,255,255,255,192,0,1,255,255,255,255,255,255,224,0,0,255,255,255,255,255,255,240,0,0,127,255,255,255,255,255,248,0,0,127,255,128,0,0,0,0,0,0,63,255,192,0,0,0,0,0,0,31,255,224,0,0,0,0,0,0,15,255,240,0,0,0,0,0,0,7,255,248,0,0,0,0,0,0,3,255,252,0,0,0,0,0,0,1,255,254,0,0,0,0,0,0,0,255,254,0,0,0,0,0,0,0,127,255,0,0,0,0,0,0,0,63,255,128,0,0,0,0,0,0,31,255,192,0,0,0,0,0,0,15,255,224,0,0,0,0,0,0,7,255,240,0,0,0,0,0,0,3,255,248,0,0,0,0,0,0,3,255,252,0,0,0,0,0,0,1,255,254,0,255,254,0,0,0,0,255,255,3,255,255,224,0,0,0,127,255,135,255,255,252,0,0,0,63,255,207,255,255,255,128,0,0,31,255,255,255,255,255,224,0,0,15,255,255,255,255,255,252,0,0,7,255,255,255,255,255,255,0,0,3,255,255,255,255,255,255,192,0,1,255,255,255,255,255,255,240,0,0,255,255,255,255,255,255,248,0,0,127,255,255,255,255,255,254,0,0,63,255,255,255,255,255,255,128,0,63,255,255,255,255,255,255,192,0,31,255,255,224,15,255,255,240,0,15,255,255,192,1,255,255,248,0,7,255,255,128,0,63,255,254,0,3,255,255,128,0,15,255,255,0,1,255,255,128,0,3,255,255,128,0,7,255,128,0,0,255,255,224,0,0,3,128,0,0,127,255,240,0,0,0,0,0,0,31,255,248,0,0,0,0,0,0,15,255,252,0,0,0,0,0,0,7,255,254,0,0,0,0,0,0,1,255,255,0,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,127,255,224,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,31,255,248,0,0,0,0,0,0,15,255,252,0,255,255,128,0,0,7,255,254,0,127,255,192,0,0,3,255,254,0,63,255,224,0,0,1,255,255,0,31,255,240,0,0,0,255,255,128,15,255,252,0,0,0,255,255,192,7,255,254,0,0,0,127,255,224,1,255,255,0,0,0,63,255,240,0,255,255,192,0,0,63,255,248,0,127,255,224,0,0,31,255,248,0,63,255,248,0,0,31,255,252,0,15,255,254,0,0,31,255,254,0,7,255,255,128,0,31,255,254,0,1,255,255,224,0,31,255,255,0,0,255,255,252,0,63,255,255,0,0,63,255,255,224,255,255,255,128,0,31,255,255,255,255,255,255,128,0,7,255,255,255,255,255,255,128,0,1,255,255,255,255,255,255,192,0,0,127,255,255,255,255,255,192,0,0,31,255,255,255,255,255,192,0,0,7,255,255,255,255,255,192,0,0,1,255,255,255,255,255,192,0,0,0,127,255,255,255,255,128,0,0,0,15,255,255,255,255,128,0,0,0,1,255,255,255,255,0,0,0,0,0,63,255,255,254,0,0,0,0,0,3,255,255,248,0,0,0,0,0,0,15,255,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,0,0,0,0,0,0,3,255,255,252,0,0,0,0,0,7,255,255,255,192,0,0,0,0,7,255,255,255,252,0,0,0,0,3,255,255,255,255,192,0,0,0,3,255,255,255,255,248,0,0,0,1,255,255,255,255,255,0,0,0,0,255,255,255,255,255,224,0,0,0,127,255,255,255,255,252,0,0,0,31,255,255,255,255,255,128,0,0,15,255,255,255,255,255,240,0,0,7,255,255,255,255,255,252,0,0,1,255,255,255,255,255,255,128,0,0,255,255,252,7,255,255,224,0,0,127,255,248,0,63,255,252,0,0,31,255,248,0,7,255,255,0,0,7,255,252,0,0,255,255,192,0,3,255,255,0,0,31,255,248,0,0,255,255,128,0,7,255,254,0,0,127,255,192,0,0,255,255,128,0,31,255,240,0,0,63,255,224,0,7,255,252,0,0,15,255,252,0,1,255,254,0,0,1,255,255,0,0,255,255,128,0,0,127,255,192,0,63,255,224,0,0,31,255,240,0,15,255,248,0,0,7,255,252,0,3,255,252,0,0,1,255,255,0,1,255,255,0,0,0,0,0,0,0,127,255,192,0,0,0,0,0,0,31,255,240,0,0,0,0,0,0,7,255,252,0,0,0,0,0,0,1,255,255,0,0,0,0,0,0,0,127,255,192,3,255,240,0,0,0,31,255,240,7,255,255,128,0,0,15,255,252,15,255,255,248,0,0,3,255,255,15,255,255,255,128,0,0,255,255,199,255,255,255,248,0,0,63,255,243,255,255,255,255,0,0,15,255,255,255,255,255,255,224,0,3,255,255,255,255,255,255,252,0,0,255,255,255,255,255,255,255,128,0,63,255,255,255,255,255,255,240,0,15,255,255,255,255,255,255,254,0,3,255,255,255,255,255,255,255,128,0,255,255,255,255,255,255,255,240,0,63,255,255,252,3,255,255,252,0,15,255,255,248,0,31,255,255,128,3,255,255,252,0,3,255,255,224,0,255,255,252,0,0,127,255,248,0,63,255,255,0,0,15,255,255,0,15,255,255,128,0,1,255,255,192,3,255,255,192,0,0,63,255,240,0,255,255,240,0,0,15,255,252,0,63,255,248,0,0,3,255,255,128,15,255,254,0,0,0,127,255,224,3,255,255,128,0,0,31,255,248,0,255,255,224,0,0,7,255,254,0,31,255,248,0,0,1,255,255,128,7,255,254,0,0,0,127,255,224,1,255,255,0,0,0,31,255,248,0,127,255,192,0,0,7,255,254,0,31,255,248,0,0,1,255,255,128,7,255,254,0,0,0,127,255,224,0,255,255,128,0,0,31,255,248,0,63,255,224,0,0,7,255,254,0,15,255,248,0,0,1,255,255,0,3,255,255,0,0,0,255,255,192,0,127,255,192,0,0,63,255,240,0,31,255,240,0,0,31,255,252,0,7,255,254,0,0,7,255,254,0,0,255,255,192,0,3,255,255,128,0,63,255,248,0,1,255,255,224,0,7,255,255,0,0,255,255,240,0,1,255,255,240,0,255,255,252,0,0,63,255,255,131,255,255,254,0,0,15,255,255,255,255,255,255,128,0,1,255,255,255,255,255,255,192,0,0,63,255,255,255,255,255,224,0,0,15,255,255,255,255,255,240,0,0,1,255,255,255,255,255,248,0,0,0,63,255,255,255,255,252,0,0,0,7,255,255,255,255,254,0,0,0,0,127,255,255,255,255,0,0,0,0,15,255,255,255,255,0,0,0,0,0,255,255,255,255,0,0,0,0,0,15,255,255,255,0,0,0,0,0,0,127,255,254,0,0,0,0,0,0,0,255,248,0,0,0,0,3,255,255,255,255,255,255,255,248,0,255,255,255,255,255,255,255,254,0,63,255,255,255,255,255,255,255,128,15,255,255,255,255,255,255,255,224,3,255,255,255,255,255,255,255,248,0,255,255,255,255,255,255,255,254,0,63,255,255,255,255,255,255,255,128,15,255,255,255,255,255,255,255,224,3,255,255,255,255,255,255,255,248,0,255,255,255,255,255,255,255,254,0,63,255,255,255,255,255,255,255,128,15,255,255,255,255,255,255,255,192,3,255,255,255,255,255,255,255,224,0,255,255,255,255,255,255,255,240,0,63,255,255,255,255,255,255,248,0,0,0,0,0,0,3,255,254,0,0,0,0,0,0,1,255,255,0,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,127,255,192,0,0,0,0,0,0,31,255,240,0,0,0,0,0,0,15,255,248,0,0,0,0,0,0,7,255,252,0,0,0,0,0,0,3,255,254,0,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,127,255,192,0,0,0,0,0,0,63,255,224,0,0,0,0,0,0,15,255,248,0,0,0,0,0,0,7,255,252,0,0,0,0,0,0,3,255,254,0,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,127,255,192,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,15,255,248,0,0,0,0,0,0,7,255,252,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,127,255,224,0,0,0,0,0,0,31,255,240,0,0,0,0,0,0,15,255,252,0,0,0,0,0,0,3,255,254,0,0,0,0,0,0,1,255,255,0,0,0,0,0,0,0,127,255,192,0,0,0,0,0,0,63,255,224,0,0,0,0,0,0,15,255,248,0,0,0,0,0,0,7,255,254,0,0,0,0,0,0,1,255,255,0,0,0,0,0,0,0,255,255,192,0,0,0,0,0,0,63,255,224,0,0,0,0,0,0,31,255,248,0,0,0,0,0,0,7,255,252,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,0,255,255,128,0,0,0,0,0,0,63,255,224,0,0,0,0,0,0,31,255,248,0,0,0,0,0,0,7,255,252,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,0,255,255,192,0,0,0,0,0,0,63,255,224,0,0,0,0,0,0,31,255,248,0,0,0,0,0,0,7,255,254,0,0,0,0,0,0,1,255,255,128,0,0,0,0,0,0,255,255,192,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,15,255,252,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,1,255,255,128,0,0,0,0,0,0,127,255,224,0,0,0,0,0,0,31,255,248,0,0,0,0,0,0,7,255,254,0,0,0,0,0,0,3,255,255,128,0,0,0,0,0,0,255,255,192,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,15,255,252,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,0,255,255,192,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,31,255,252,0,0,0,0,0,0,7,255,255,0,0,0,0,0,0,1,255,255,192,0,0,0,0,0,0,127,255,240,0,0,0,0,0,0,31,255,252,0,0,0,0,0,0,7,255,255,0,0,0,0,0,0,1,255,255,192,0,0,0,0,0,0,127,255,240,0,0,0,0,0,0,31,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,255,252,0,0,0,0,0,0,63,255,255,224,0,0,0,0,0,255,255,255,254,0,0,0,0,1,255,255,255,255,192,0,0,0,3,255,255,255,255,248,0,0,0,3,255,255,255,255,254,0,0,0,3,255,255,255,255,255,128,0,0,3,255,255,255,255,255,224,0,0,7,255,255,255,255,255,248,0,0,3,255,255,255,255,255,254,0,0,3,255,255,255,255,255,255,128,0,3,255,255,255,255,255,255,224,0,1,255,255,255,255,255,255,240,0,1,255,255,252,1,255,255,252,0,0,255,255,248,0,63,255,254,0,0,255,255,240,0,7,255,255,128,0,127,255,240,0,1,255,255,192,0,63,255,240,0,0,127,255,224,0,31,255,240,0,0,31,255,240,0,31,255,248,0,0,15,255,252,0,15,255,252,0,0,7,255,254,0,7,255,252,0,0,1,255,255,0,3,255,254,0,0,0,255,255,128,1,255,255,0,0,0,127,255,192,0,255,255,128,0,0,63,255,224,0,127,255,192,0,0,31,255,240,0,63,255,224,0,0,15,255,248,0,31,255,240,0,0,7,255,252,0,7,255,252,0,0,7,255,252,0,3,255,254,0,0,3,255,254,0,1,255,255,0,0,1,255,255,0,0,255,255,192,0,1,255,255,0,0,63,255,240,0,1,255,255,128,0,31,255,252,0,1,255,255,192,0,7,255,255,0,3,255,255,192,0,1,255,255,240,7,255,255,192,0,0,255,255,255,255,255,255,224,0,0,63,255,255,255,255,255,224,0,0,15,255,255,255,255,255,224,0,0,3,255,255,255,255,255,224,0,0,0,127,255,255,255,255,192,0,0,0,31,255,255,255,255,192,0,0,0,7,255,255,255,255,192,0,0,0,7,255,255,255,255,240,0,0,0,15,255,255,255,255,254,0,0,0,15,255,255,255,255,255,192,0,0,31,255,255,255,255,255,240,0,0,31,255,255,255,255,255,252,0,0,31,255,255,255,255,255,255,0,0,31,255,255,240,127,255,255,192,0,15,255,255,128,3,255,255,240,0,15,255,255,0,0,127,255,248,0,15,255,255,0,0,31,255,254,0,7,255,255,0,0,7,255,255,0,7,255,255,0,0,1,255,255,192,3,255,255,0,0,0,127,255,224,1,255,255,128,0,0,63,255,240,0,255,255,128,0,0,15,255,248,0,255,255,192,0,0,7,255,254,0,127,255,224,0,0,3,255,255,0,63,255,240,0,0,1,255,255,128,31,255,248,0,0,0,255,255,192,15,255,252,0,0,0,127,255,224,7,255,254,0,0,0,63,255,240,3,255,255,0,0,0,31,255,248,1,255,255,128,0,0,15,255,252,0,255,255,192,0,0,7,255,254,0,127,255,240,0,0,7,255,255,0,31,255,248,0,0,3,255,255,0,15,255,254,0,0,3,255,255,128,7,255,255,128,0,3,255,255,192,3,255,255,224,0,3,255,255,224,0,255,255,248,0,3,255,255,224,0,127,255,255,0,7,255,255,240,0,31,255,255,248,63,255,255,240,0,15,255,255,255,255,255,255,248,0,3,255,255,255,255,255,255,248,0,0,255,255,255,255,255,255,248,0,0,127,255,255,255,255,255,252,0,0,31,255,255,255,255,255,252,0,0,7,255,255,255,255,255,252,0,0,0,255,255,255,255,255,248,0,0,0,63,255,255,255,255,248,0,0,0,15,255,255,255,255,240,0,0,0,1,255,255,255,255,240,0,0,0,0,31,255,255,255,192,0,0,0,0,1,255,255,255,0,0,0,0,0,0,7,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,255,252,0,0,0,0,0,0,31,255,255,240,0,0,0,0,0,31,255,255,255,0,0,0,0,0,31,255,255,255,240,0,0,0,0,31,255,255,255,255,0,0,0,0,15,255,255,255,255,240,0,0,0,15,255,255,255,255,254,0,0,0,7,255,255,255,255,255,192,0,0,3,255,255,255,255,255,248,0,0,1,255,255,255,255,255,255,0,0,0,127,255,255,255,255,255,192,0,0,63,255,255,255,255,255,248,0,0,31,255,255,255,255,255,255,0,0,7,255,255,240,15,255,255,192,0,3,255,255,224,0,127,255,248,0,0,255,255,240,0,15,255,254,0,0,127,255,248,0,1,255,255,192,0,31,255,252,0,0,63,255,240,0,15,255,254,0,0,7,255,252,0,3,255,255,128,0,1,255,255,128,0,255,255,192,0,0,63,255,224,0,63,255,240,0,0,15,255,248,0,31,255,248,0,0,1,255,255,0,7,255,254,0,0,0,127,255,192,1,255,255,128,0,0,31,255,240,0,127,255,224,0,0,7,255,252,0,31,255,248,0,0,1,255,255,0,7,255,254,0,0,0,127,255,224,1,255,255,128,0,0,31,255,248,0,127,255,224,0,0,7,255,254,0,31,255,248,0,0,1,255,255,128,7,255,254,0,0,0,127,255,224,1,255,255,128,0,0,31,255,248,0,127,255,224,0,0,7,255,254,0,15,255,252,0,0,3,255,255,128,3,255,255,0,0,0,255,255,224,0,255,255,192,0,0,127,255,248,0,63,255,248,0,0,31,255,255,0,15,255,255,0,0,15,255,255,192,1,255,255,224,0,7,255,255,240,0,127,255,252,0,3,255,255,252,0,15,255,255,128,1,255,255,255,0,3,255,255,252,3,255,255,255,192,0,127,255,255,255,255,255,255,240,0,31,255,255,255,255,255,255,252,0,3,255,255,255,255,255,255,255,0,0,255,255,255,255,255,255,255,192,0,31,255,255,255,255,255,255,224,0,3,255,255,255,255,255,255,248,0,0,127,255,255,255,251,255,254,0,0,15,255,255,255,252,255,255,128,0,0,255,255,255,252,63,255,224,0,0,31,255,255,254,15,255,248,0,0,1,255,255,254,3,255,254,0,0,0,15,255,252,0,255,255,128,0,0,0,63,248,0,63,255,224,0,0,0,0,0,0,15,255,248,0,0,0,0,0,0,3,255,254,0,0,0,0,0,0,0,255,255,0,0,0,0,0,0,0,127,255,192,0,0,0,0,0,0,31,255,240,0,15,255,248,0,0,7,255,252,0,3,255,254,0,0,1,255,255,0,0,255,255,128,0,0,127,255,128,0,63,255,224,0,0,31,255,224,0,15,255,248,0,0,15,255,248,0,3,255,255,0,0,3,255,254,0,0,127,255,192,0,0,255,255,0,0,31,255,240,0,0,127,255,192,0,7,255,254,0,0,31,255,240,0,1,255,255,128,0,15,255,248,0,0,63,255,240,0,7,255,254,0,0,15,255,254,0,3,255,255,0,0,3,255,255,224,3,255,255,192,0,0,127,255,255,7,255,255,224,0,0,31,255,255,255,255,255,248,0,0,3,255,255,255,255,255,252,0,0,0,127,255,255,255,255,255,0,0,0,31,255,255,255,255,255,128,0,0,3,255,255,255,255,255,192,0,0,0,127,255,255,255,255,224,0,0,0,15,255,255,255,255,240,0,0,0,1,255,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,3,255,255,255,252,0,0,0,0,0,63,255,255,252,0,0,0,0,0,1,255,255,248,0,0,0,0,0,0,3,255,192,0,0,0,0,0,0,0,127,248,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,192,0,0,0,0,3,255,128,0,0,0,0,7,255,255,248,0,0,0,0,3,255,192,0,0,0,0,15,255,255,255,0,0,0,0,3,255,192,0,0,0,0,15,255,255,255,192,0,0,0,3,255,192,0,0,0,0,15,255,255,255,240,0,0,0,1,255,224,0,0,0,0,15,255,255,255,252,0,0,0,1,255,224,0,0,0,0,15,255,255,255,255,0,0,0,1,255,224,0,0,0,0,7,255,255,255,255,128,0,0,1,255,240,0,0,0,0,7,255,248,31,255,224,0,0,0,255,240,0,0,0,0,3,255,240,3,255,240,0,0,0,255,240,0,0,0,0,3,255,240,0,255,252,0,0,0,255,240,0,0,0,0,1,255,248,0,127,254,0,0,0,127,248,0,0,0,0,0,255,248,0,31,255,0,0,0,127,248,0,0,0,0,0,127,252,0,15,255,128,0,0,127,248,0,0,0,0,0,63,254,0,7,255,192,0,0,63,252,0,0,0,0,0,63,254,0,1,255,240,0,0,63,252,0,0,0,0,0,31,255,0,0,255,248,0,0,63,252,0,0,0,0,0,15,255,128,0,127,252,0,0,63,254,0,0,0,0,0,7,255,192,0,63,254,0,0,31,254,0,0,0,0,0,3,255,224,0,31,255,0,0,31,254,0,0,0,0,0,1,255,240,0,15,255,128,0,31,254,0,0,0,0,0,0,255,248,0,7,255,192,0,15,255,0,0,0,0,0,0,127,252,0,3,255,224,0,15,255,0,0,0,0,0,0,63,254,0,1,255,240,0,15,255,0,0,0,0,0,0,31,255,0,0,255,248,0,15,255,128,0,0,0,0,0,15,255,128,0,127,252,0,7,255,128,0,0,0,0,0,7,255,192,0,63,254,0,7,255,128,0,0,0,0,0,1,255,240,0,63,254,0,7,255,128,0,0,0,0,0,0,255,248,0,31,255,0,3,255,192,0,0,0,0,0,0,127,252,0,15,255,128,3,255,192,0,0,0,0,0,0,63,255,0,15,255,192,3,255,192,0,0,0,0,0,0,31,255,128,7,255,224,1,255,224,0,0,0,0,0,0,7,255,224,7,255,224,1,255,224,0,0,0,0,0,0,3,255,252,15,255,240,1,255,224,0,0,0,0,0,0,0,255,255,255,255,240,1,255,240,0,0,0,0,0,0,0,127,255,255,255,248,0,255,240,0,0,0,0,0,0,0,31,255,255,255,248,0,255,240,0,0,0,0,0,0,0,7,255,255,255,248,0,255,240,0,0,0,0,0,0,0,1,255,255,255,248,0,127,248,0,0,0,0,0,0,0,0,127,255,255,248,0,127,248,0,0,0,0,0,0,0,0,15,255,255,240,0,127,248,0,0,0,0,0,0,0,0,1,255,255,224,0,63,252,0,0,0,0,0,0,0,0,0,15,255,0,0,63,252,0,1,255,224,0,0,0,0,0,0,0,0,0,63,252,0,15,255,255,0,0,0,0,0,0,0,0,0,63,254,0,31,255,255,224,0,0,0,0,0,0,0,0,31,254,0,63,255,255,252,0,0,0,0,0,0,0,0,31,254,0,63,255,255,255,0,0,0,0,0,0,0,0,31,254,0,63,255,255,255,192,0,0,0,0,0,0,0,15,255,0,63,255,255,255,240,0,0,0,0,0,0,0,15,255,0,31,255,255,255,252,0,0,0,0,0,0,0,15,255,0,31,255,255,255,254,0,0,0,0,0,0,0,7,255,128,31,255,224,127,255,128,0,0,0,0,0,0,7,255,128,15,255,192,15,255,192,0,0,0,0,0,0,7,255,128,7,255,192,3,255,224,0,0,0,0,0,0,7,255,192,7,255,224,1,255,248,0,0,0,0,0,0,3,255,192,3,255,224,0,127,252,0,0,0,0,0,0,3,255,192,1,255,240,0,63,254,0,0,0,0,0,0,3,255,192,0,255,248,0,31,255,0,0,0,0,0,0,1,255,224,0,255,248,0,7,255,192,0,0,0,0,0,1,255,224,0,127,252,0,3,255,224,0,0,0,0,0,1,255,224,0,63,254,0,1,255,240,0,0,0,0,0,0,255,240,0,31,255,0,0,255,248,0,0,0,0,0,0,255,240,0,15,255,128,0,127,252,0,0,0,0,0,0,255,240,0,7,255,192,0,63,254,0,0,0,0,0,0,255,240,0,3,255,224,0,31,255,0,0,0,0,0,0,127,248,0,1,255,240,0,15,255,128,0,0,0,0,0,127,248,0,0,255,248,0,7,255,192,0,0,0,0,0,127,248,0,0,127,252,0,3,255,224,0,0,0,0,0,63,252,0,0,63,254,0,1,255,240,0,0,0,0,0,63,252,0,0,31,255,0,0,255,248,0,0,0,0,0,63,252,0,0,15,255,128,0,127,252,0,0,0,0,0,31,254,0,0,3,255,224,0,127,252,0,0,0,0,0,31,254,0,0,1,255,240,0,63,254,0,0,0,0,0,31,254,0,0,0,255,248,0,31,255,0,0,0,0,0,31,254,0,0,0,127,254,0,31,255,128,0,0,0,0,15,255,0,0,0,31,255,0,15,255,128,0,0,0,0,15,255,0,0,0,15,255,192,15,255,192,0,0,0,0,15,255,0,0,0,7,255,248,31,255,224,0,0,0,0,7,255,128,0,0,1,255,255,255,255,224,0,0,0,0,7,255,128,0,0,0,127,255,255,255,240,0,0,0,0,7,255,128,0,0,0,63,255,255,255,240,0,0,0,0,3,255,192,0,0,0,15,255,255,255,240,0,0,0,0,3,255,192,0,0,0,3,255,255,255,240,0,0,0,0,3,255,192,0,0,0,0,127,255,255,240,0,0,0,0,3,255,192,0,0,0,0,31,255,255,224,0,0,0,0,1,255,192,0,0,0,0,1,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,31,254,0,0,0,0,15,255,252,0,0,7,255,254,0,0,3,255,255,0,0,1,255,255,128,0,0,255,255,192,0,0,127,255,224,0,0,63,255,240,0,0,31,255,248,0,0,15,255,252,0,0,7,255,254,0,0,3,255,255,0,0,1,255,255,128,0,0,255,255,192,0,0,127,255,224,0,0,63,255,240,0,0,31,255,248,0,0,15,255,252,0,0,7,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,252,0,0,7,255,254,0,0,3,255,255,0,0,1,255,255,128,0,0,255,255,192,0,0,127,255,224,0,0,63,255,240,0,0,31,255,248,0,0,15,255,252,0,0,7,255,254,0,0,3,255,255,0,0,1,255,255,128,0,0,255,255,192,0,0,127,255,224,0,0,63,255,240,0,0,31,255,248,0,0,15,255,252,0,0,7,255,254,0,0,7,255,255,255,240,3,255,255,255,248,1,255,255,255,252,0,255,255,255,254,0,127,255,255,255,0,63,255,255,255,128,31,255,255,255,192,15,255,255,255,224,7,255,255,255,240,3,255,255,255,248,1,255,255,255,252,0,255,255,255,254,0,127,255,255,255,0,63,255,255,255,128,31,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,240,0,0,255,255,192,0,3,255,255,0,0,15,255,252,0,0,63,255,240,0,0,255,255,192,0,3,255,255,0,0,15,255,252,0,0,63,255,240,0,0,255,255,192,0,3,255,255,0,0,15,255,252,0,0,63,255,240,0,0,255,255,192,0,3,255,255,0,0,15,255,252,0,0,63,255,240,0,0,255,255,192,0}; +static const Glyph f0_124_glyphs[]={{0,74,89,0,0,74,48},{824,74,86,0,2,74,49},{1620,74,87,0,1,74,50},{2425,73,89,0,0,73,51},{3238,74,87,0,1,74,52},{4043,73,87,0,2,73,53},{4837,74,89,0,0,74,54},{5661,74,85,0,3,74,55},{6448,73,89,0,0,73,56},{7261,74,89,0,0,74,57},{8085,121,88,0,1,121,37},{9416,41,66,0,22,41,58},{9755,41,41,0,47,41,45},{9966,38,18,0,70,38,46},{10052,24,0,0,88,24,32}}; +static const uint8_t f0_132_bits[]={0,0,0,0,255,252,0,0,0,0,0,0,0,127,255,255,128,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,255,255,255,255,192,0,0,0,0,7,255,255,255,255,128,0,0,0,0,127,255,255,255,255,128,0,0,0,3,255,255,255,255,255,0,0,0,0,31,255,255,255,255,254,0,0,0,0,255,255,255,255,255,252,0,0,0,7,255,255,255,255,255,248,0,0,0,63,255,255,255,255,255,240,0,0,1,255,255,255,255,255,255,192,0,0,7,255,255,255,255,255,255,128,0,0,63,255,255,255,255,255,255,0,0,0,255,255,255,3,255,255,252,0,0,7,255,255,224,1,255,255,248,0,0,31,255,254,0,1,255,255,224,0,0,255,255,240,0,3,255,255,192,0,3,255,255,128,0,7,255,255,0,0,15,255,254,0,0,31,255,252,0,0,127,255,240,0,0,63,255,248,0,1,255,255,192,0,0,255,255,224,0,7,255,254,0,0,1,255,255,128,0,63,255,248,0,0,7,255,255,0,0,255,255,192,0,0,31,255,252,0,3,255,255,0,0,0,63,255,240,0,15,255,252,0,0,0,255,255,192,0,63,255,240,0,0,3,255,255,0,1,255,255,192,0,0,15,255,254,0,7,255,255,0,0,0,63,255,248,0,31,255,248,0,0,0,127,255,224,0,127,255,224,0,0,1,255,255,128,1,255,255,128,0,0,7,255,254,0,7,255,254,0,0,0,31,255,248,0,31,255,248,0,0,0,127,255,224,0,255,255,224,0,0,1,255,255,192,3,255,255,128,0,0,7,255,255,0,15,255,254,0,0,0,31,255,252,0,63,255,248,0,0,0,127,255,240,0,255,255,224,0,0,1,255,255,192,3,255,255,128,0,0,7,255,255,0,15,255,254,0,0,0,31,255,252,0,63,255,248,0,0,0,127,255,240,0,255,255,224,0,0,1,255,255,192,3,255,255,128,0,0,7,255,255,0,15,255,254,0,0,0,31,255,252,0,63,255,248,0,0,0,127,255,240,0,255,255,224,0,0,1,255,255,192,3,255,255,128,0,0,7,255,255,0,15,255,254,0,0,0,31,255,252,0,63,255,248,0,0,0,127,255,240,0,255,255,224,0,0,1,255,255,192,3,255,255,128,0,0,7,255,255,0,15,255,254,0,0,0,31,255,252,0,63,255,248,0,0,0,127,255,240,0,255,255,224,0,0,1,255,255,192,3,255,255,128,0,0,7,255,255,0,15,255,254,0,0,0,31,255,252,0,63,255,248,0,0,0,127,255,240,0,127,255,224,0,0,1,255,255,128,1,255,255,128,0,0,7,255,254,0,7,255,254,0,0,0,31,255,248,0,31,255,248,0,0,0,127,255,224,0,127,255,224,0,0,1,255,255,128,1,255,255,192,0,0,15,255,254,0,7,255,255,0,0,0,63,255,248,0,15,255,252,0,0,0,255,255,192,0,63,255,240,0,0,3,255,255,0,0,255,255,192,0,0,15,255,252,0,3,255,255,0,0,0,63,255,240,0,15,255,254,0,0,1,255,255,192,0,31,255,248,0,0,7,255,254,0,0,127,255,224,0,0,31,255,248,0,1,255,255,192,0,0,255,255,224,0,3,255,255,128,0,3,255,255,0,0,15,255,254,0,0,31,255,252,0,0,63,255,252,0,0,255,255,240,0,0,127,255,248,0,7,255,255,128,0,1,255,255,248,0,127,255,254,0,0,3,255,255,252,7,255,255,240,0,0,15,255,255,255,255,255,255,192,0,0,31,255,255,255,255,255,254,0,0,0,63,255,255,255,255,255,240,0,0,0,255,255,255,255,255,255,192,0,0,1,255,255,255,255,255,254,0,0,0,3,255,255,255,255,255,240,0,0,0,7,255,255,255,255,255,128,0,0,0,15,255,255,255,255,252,0,0,0,0,31,255,255,255,255,224,0,0,0,0,31,255,255,255,254,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,31,255,255,255,0,0,0,0,0,0,31,255,255,224,0,0,0,0,0,0,3,255,240,0,0,0,0,0,0,0,0,0,7,248,0,0,0,0,0,0,0,0,127,224,0,0,0,0,0,0,0,7,255,128,0,0,0,0,0,0,0,127,254,0,0,0,0,0,0,0,7,255,248,0,0,0,0,0,0,0,127,255,224,0,0,0,0,0,0,15,255,255,128,0,0,0,0,0,0,255,255,254,0,0,0,0,0,0,31,255,255,248,0,0,0,0,0,3,255,255,255,224,0,0,0,0,0,127,255,255,255,128,0,0,0,0,63,255,255,255,254,0,0,0,0,7,255,255,255,255,248,0,0,0,0,31,255,255,255,255,224,0,0,0,0,127,255,255,255,255,128,0,0,0,1,255,255,255,255,254,0,0,0,0,7,255,255,255,255,248,0,0,0,0,31,255,255,255,255,224,0,0,0,0,127,255,255,255,255,128,0,0,0,1,255,255,255,255,254,0,0,0,0,7,255,255,255,255,248,0,0,0,0,31,255,255,255,255,224,0,0,0,0,127,255,255,255,255,128,0,0,0,0,0,0,15,255,254,0,0,0,0,0,0,0,63,255,248,0,0,0,0,0,0,0,255,255,224,0,0,0,0,0,0,3,255,255,128,0,0,0,0,0,0,15,255,254,0,0,0,0,0,0,0,63,255,248,0,0,0,0,0,0,0,255,255,224,0,0,0,0,0,0,3,255,255,128,0,0,0,0,0,0,15,255,254,0,0,0,0,0,0,0,63,255,248,0,0,0,0,0,0,0,255,255,224,0,0,0,0,0,0,3,255,255,128,0,0,0,0,0,0,15,255,254,0,0,0,0,0,0,0,63,255,248,0,0,0,0,0,0,0,255,255,224,0,0,0,0,0,0,3,255,255,128,0,0,0,0,0,0,15,255,254,0,0,0,0,0,0,0,63,255,248,0,0,0,0,0,0,0,255,255,224,0,0,0,0,0,0,3,255,255,128,0,0,0,0,0,0,15,255,254,0,0,0,0,0,0,0,63,255,248,0,0,0,0,0,0,0,255,255,224,0,0,0,0,0,0,3,255,255,128,0,0,0,0,0,0,15,255,254,0,0,0,0,0,0,0,63,255,248,0,0,0,0,0,0,0,255,255,224,0,0,0,0,0,0,3,255,255,128,0,0,0,0,0,0,15,255,254,0,0,0,0,0,0,0,63,255,248,0,0,0,0,0,0,0,255,255,224,0,0,0,0,0,0,3,255,255,128,0,0,0,0,0,0,15,255,254,0,0,0,0,0,0,0,63,255,248,0,0,0,0,0,0,0,255,255,224,0,0,0,0,0,0,3,255,255,128,0,0,0,0,0,0,15,255,254,0,0,0,0,0,0,0,63,255,248,0,0,0,0,0,0,0,255,255,224,0,0,0,0,0,0,3,255,255,128,0,0,0,0,0,0,15,255,254,0,0,0,0,0,0,0,63,255,248,0,0,0,0,0,0,0,255,255,224,0,0,0,0,0,0,3,255,255,128,0,0,0,0,0,0,15,255,254,0,0,0,0,0,0,0,63,255,248,0,0,0,0,0,0,0,255,255,224,0,0,0,0,0,0,3,255,255,128,0,0,0,0,0,0,15,255,254,0,0,0,0,0,0,0,63,255,248,0,0,0,0,0,0,0,255,255,224,0,0,0,0,0,0,3,255,255,128,0,0,0,0,0,0,15,255,254,0,0,0,0,7,255,255,255,255,255,255,255,128,0,31,255,255,255,255,255,255,254,0,0,127,255,255,255,255,255,255,248,0,1,255,255,255,255,255,255,255,224,0,7,255,255,255,255,255,255,255,128,0,31,255,255,255,255,255,255,254,0,0,127,255,255,255,255,255,255,248,0,1,255,255,255,255,255,255,255,224,0,7,255,255,255,255,255,255,255,128,0,31,255,255,255,255,255,255,254,0,0,127,255,255,255,255,255,255,248,0,1,255,255,255,255,255,255,255,224,0,7,255,255,255,255,255,255,255,128,0,31,255,255,255,255,255,255,254,0,0,127,255,255,255,255,255,255,248,0,0,0,0,0,63,255,128,0,0,0,0,0,0,15,255,255,240,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,15,255,255,255,254,0,0,0,0,0,127,255,255,255,255,0,0,0,0,1,255,255,255,255,255,128,0,0,0,15,255,255,255,255,255,128,0,0,0,63,255,255,255,255,255,128,0,0,0,255,255,255,255,255,255,128,0,0,3,255,255,255,255,255,255,128,0,0,15,255,255,255,255,255,255,128,0,0,63,255,255,255,255,255,255,0,0,0,255,255,255,255,255,255,255,0,0,1,255,255,255,255,255,255,255,0,0,7,255,255,252,3,255,255,254,0,0,31,255,255,192,0,255,255,254,0,0,63,255,254,0,0,127,255,252,0,0,127,255,248,0,0,127,255,248,0,1,255,255,224,0,0,127,255,248,0,3,255,255,128,0,0,255,255,240,0,7,255,255,0,0,0,255,255,224,0,15,255,252,0,0,1,255,255,192,0,63,255,248,0,0,1,255,255,192,0,127,255,224,0,0,3,255,255,128,0,255,255,192,0,0,7,255,255,0,1,255,255,128,0,0,15,255,254,0,3,255,255,0,0,0,31,255,252,0,7,255,254,0,0,0,63,255,248,0,15,255,252,0,0,0,127,255,240,0,31,255,248,0,0,0,255,255,224,0,63,255,240,0,0,1,255,255,192,0,127,255,224,0,0,7,255,255,128,0,127,255,192,0,0,15,255,254,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,127,255,248,0,0,0,0,0,0,1,255,255,240,0,0,0,0,0,0,3,255,255,192,0,0,0,0,0,0,15,255,255,128,0,0,0,0,0,0,63,255,255,0,0,0,0,0,0,0,255,255,252,0,0,0,0,0,0,3,255,255,248,0,0,0,0,0,0,15,255,255,224,0,0,0,0,0,0,63,255,255,128,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,3,255,255,252,0,0,0,0,0,0,15,255,255,240,0,0,0,0,0,0,63,255,255,224,0,0,0,0,0,1,255,255,255,128,0,0,0,0,0,7,255,255,254,0,0,0,0,0,0,31,255,255,248,0,0,0,0,0,0,127,255,255,224,0,0,0,0,0,3,255,255,255,128,0,0,0,0,0,15,255,255,254,0,0,0,0,0,0,63,255,255,248,0,0,0,0,0,1,255,255,255,224,0,0,0,0,0,7,255,255,255,128,0,0,0,0,0,31,255,255,252,0,0,0,0,0,0,127,255,255,240,0,0,0,0,0,3,255,255,255,192,0,0,0,0,0,15,255,255,255,0,0,0,0,0,0,63,255,255,248,0,0,0,0,0,0,255,255,255,224,0,0,0,0,0,3,255,255,255,0,0,0,0,0,0,15,255,255,252,0,0,0,0,0,0,63,255,255,240,0,0,0,0,0,0,255,255,255,128,0,0,0,0,0,3,255,255,254,0,0,0,0,0,0,15,255,255,240,0,0,0,0,0,0,63,255,255,192,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,3,255,255,248,0,0,0,0,0,0,7,255,255,224,0,0,0,0,0,0,31,255,255,128,0,0,0,0,0,0,63,255,254,0,0,0,0,0,0,0,255,255,248,0,0,0,0,0,0,1,255,255,240,0,0,0,0,0,0,7,255,255,255,255,255,255,255,248,0,15,255,255,255,255,255,255,255,240,0,63,255,255,255,255,255,255,255,224,0,127,255,255,255,255,255,255,255,192,0,255,255,255,255,255,255,255,255,128,1,255,255,255,255,255,255,255,255,0,7,255,255,255,255,255,255,255,254,0,15,255,255,255,255,255,255,255,252,0,31,255,255,255,255,255,255,255,248,0,63,255,255,255,255,255,255,255,240,0,127,255,255,255,255,255,255,255,224,0,255,255,255,255,255,255,255,255,192,1,255,255,255,255,255,255,255,255,128,3,255,255,255,255,255,255,255,255,0,7,255,255,255,255,255,255,255,254,0,15,255,255,255,255,255,255,255,252,0,0,0,0,0,127,254,0,0,0,0,0,0,0,31,255,255,224,0,0,0,0,0,1,255,255,255,248,0,0,0,0,0,31,255,255,255,252,0,0,0,0,0,255,255,255,255,254,0,0,0,0,3,255,255,255,255,255,0,0,0,0,31,255,255,255,255,255,0,0,0,0,127,255,255,255,255,255,0,0,0,1,255,255,255,255,255,255,128,0,0,7,255,255,255,255,255,255,128,0,0,31,255,255,255,255,255,255,0,0,0,127,255,255,255,255,255,255,0,0,1,255,255,255,255,255,255,255,0,0,3,255,255,255,255,255,255,254,0,0,15,255,255,252,7,255,255,254,0,0,31,255,255,128,1,255,255,252,0,0,127,255,254,0,0,255,255,252,0,0,255,255,240,0,0,255,255,248,0,1,255,255,192,0,0,255,255,240,0,3,255,255,128,0,0,255,255,224,0,15,255,254,0,0,1,255,255,224,0,31,255,252,0,0,1,255,255,192,0,63,255,240,0,0,3,255,255,128,0,127,255,224,0,0,7,255,255,0,0,255,255,192,0,0,15,255,254,0,1,255,255,128,0,0,31,255,252,0,3,255,255,0,0,0,63,255,248,0,7,255,254,0,0,0,127,255,240,0,0,0,0,0,0,0,255,255,224,0,0,0,0,0,0,1,255,255,192,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,0,15,255,254,0,0,0,0,0,0,0,31,255,252,0,0,0,0,0,0,0,127,255,240,0,0,0,0,0,0,0,255,255,224,0,0,0,0,0,0,3,255,255,128,0,0,0,0,0,0,15,255,254,0,0,0,0,0,0,0,127,255,252,0,0,0,0,0,0,7,255,255,240,0,0,0,0,0,255,255,255,255,192,0,0,0,0,1,255,255,255,255,0,0,0,0,0,3,255,255,255,252,0,0,0,0,0,7,255,255,255,224,0,0,0,0,0,15,255,255,255,0,0,0,0,0,0,31,255,255,252,0,0,0,0,0,0,63,255,255,240,0,0,0,0,0,0,127,255,255,248,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,1,255,255,255,254,0,0,0,0,0,3,255,255,255,254,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,15,255,255,255,254,0,0,0,0,0,31,255,255,255,254,0,0,0,0,0,0,3,255,255,254,0,0,0,0,0,0,0,127,255,254,0,0,0,0,0,0,0,63,255,254,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,63,255,248,0,0,0,0,0,0,0,63,255,248,0,0,0,0,0,0,0,127,255,240,0,0,0,0,0,0,0,255,255,224,0,0,0,0,0,0,0,255,255,192,0,0,0,0,0,0,1,255,255,128,0,0,0,0,0,0,3,255,255,128,7,255,254,0,0,0,7,255,255,0,15,255,252,0,0,0,15,255,254,0,31,255,248,0,0,0,31,255,252,0,63,255,240,0,0,0,63,255,248,0,127,255,224,0,0,0,127,255,240,0,255,255,192,0,0,1,255,255,224,1,255,255,192,0,0,3,255,255,192,1,255,255,128,0,0,7,255,255,0,3,255,255,0,0,0,31,255,254,0,7,255,255,0,0,0,63,255,252,0,15,255,255,0,0,0,255,255,248,0,15,255,255,0,0,3,255,255,224,0,31,255,255,0,0,15,255,255,192,0,63,255,255,128,0,127,255,255,128,0,63,255,255,240,15,255,255,254,0,0,63,255,255,255,255,255,255,248,0,0,127,255,255,255,255,255,255,240,0,0,127,255,255,255,255,255,255,192,0,0,127,255,255,255,255,255,255,0,0,0,127,255,255,255,255,255,252,0,0,0,127,255,255,255,255,255,240,0,0,0,127,255,255,255,255,255,192,0,0,0,127,255,255,255,255,255,0,0,0,0,127,255,255,255,255,248,0,0,0,0,63,255,255,255,255,224,0,0,0,0,31,255,255,255,255,0,0,0,0,0,7,255,255,255,240,0,0,0,0,0,1,255,255,255,0,0,0,0,0,0,0,31,255,192,0,0,0,0,0,0,0,7,255,254,0,0,0,0,0,0,0,63,255,248,0,0,0,0,0,0,0,255,255,224,0,0,0,0,0,0,3,255,255,128,0,0,0,0,0,0,15,255,252,0,0,0,0,0,0,0,127,255,240,0,0,0,0,0,0,1,255,255,192,0,0,0,0,0,0,7,255,255,0,0,0,0,0,0,0,63,255,248,0,0,0,0,0,0,0,255,255,224,0,0,0,0,0,0,3,255,255,128,0,0,0,0,0,0,31,255,252,0,0,0,0,0,0,0,127,255,240,3,254,0,0,0,0,1,255,255,192,15,248,0,0,0,0,15,255,254,0,63,224,0,0,0,0,63,255,248,0,255,128,0,0,0,0,255,255,192,7,254,0,0,0,0,7,255,255,0,31,248,0,0,0,0,31,255,252,0,127,224,0,0,0,0,127,255,224,3,255,128,0,0,0,3,255,255,128,15,254,0,0,0,0,15,255,252,0,127,248,0,0,0,0,63,255,240,1,255,224,0,0,0,1,255,255,128,7,255,128,0,0,0,7,255,254,0,63,254,0,0,0,0,63,255,240,0,255,248,0,0,0,0,255,255,192,7,255,224,0,0,0,3,255,255,0,31,255,128,0,0,0,31,255,248,0,255,254,0,0,0,0,127,255,224,3,255,248,0,0,0,3,255,255,0,15,255,224,0,0,0,15,255,252,0,127,255,128,0,0,0,127,255,224,1,255,254,0,0,0,1,255,255,128,15,255,248,0,0,0,7,255,252,0,63,255,224,0,0,0,63,255,240,1,255,255,128,0,0,0,255,255,128,7,255,254,0,0,0,7,255,252,0,31,255,248,0,0,0,31,255,240,0,127,255,224,0,0,0,255,255,128,1,255,255,128,0,0,3,255,254,0,7,255,254,0,0,0,31,255,240,0,31,255,248,0,0,0,127,255,192,0,127,255,224,0,0,3,255,254,0,1,255,255,128,0,0,15,255,240,0,7,255,254,0,0,0,127,255,192,0,31,255,248,0,0,1,255,254,0,0,127,255,224,0,0,15,255,248,0,1,255,255,128,0,0,127,255,192,0,7,255,254,0,0,1,255,254,0,0,31,255,248,0,0,15,255,248,0,0,127,255,224,0,0,63,255,192,0,1,255,255,128,0,1,255,254,0,0,7,255,254,0,0,7,255,248,0,0,31,255,248,0,0,63,255,192,0,0,127,255,224,0,1,255,254,0,0,1,255,255,128,0,7,255,248,0,0,7,255,254,0,0,63,255,192,0,0,31,255,248,0,0,255,255,255,255,255,255,255,255,252,3,255,255,255,255,255,255,255,255,240,15,255,255,255,255,255,255,255,255,192,63,255,255,255,255,255,255,255,255,0,255,255,255,255,255,255,255,255,252,3,255,255,255,255,255,255,255,255,240,15,255,255,255,255,255,255,255,255,192,63,255,255,255,255,255,255,255,255,0,255,255,255,255,255,255,255,255,252,3,255,255,255,255,255,255,255,255,240,15,255,255,255,255,255,255,255,255,192,63,255,255,255,255,255,255,255,255,0,255,255,255,255,255,255,255,255,252,3,255,255,255,255,255,255,255,255,240,0,0,0,0,0,7,255,254,0,0,0,0,0,0,0,31,255,248,0,0,0,0,0,0,0,127,255,224,0,0,0,0,0,0,1,255,255,128,0,0,0,0,0,0,7,255,254,0,0,0,0,0,0,0,31,255,248,0,0,0,0,0,0,0,127,255,224,0,0,0,0,0,0,1,255,255,128,0,0,0,0,0,0,7,255,254,0,0,0,0,0,0,0,31,255,248,0,0,0,0,0,0,0,127,255,224,0,0,0,0,0,0,1,255,255,128,0,0,0,0,0,0,7,255,254,0,0,0,0,0,0,0,31,255,248,0,0,0,0,0,0,0,127,255,224,0,0,0,0,0,0,1,255,255,128,0,0,0,0,0,0,7,255,254,0,0,0,0,0,0,0,31,255,248,0,0,0,0,0,0,0,127,255,224,0,0,0,0,0,0,1,255,255,128,0,0,7,255,255,255,255,255,255,240,0,0,15,255,255,255,255,255,255,224,0,0,31,255,255,255,255,255,255,192,0,0,63,255,255,255,255,255,255,128,0,0,127,255,255,255,255,255,255,0,0,0,255,255,255,255,255,255,254,0,0,3,255,255,255,255,255,255,252,0,0,7,255,255,255,255,255,255,248,0,0,15,255,255,255,255,255,255,240,0,0,31,255,255,255,255,255,255,224,0,0,63,255,255,255,255,255,255,192,0,0,127,255,255,255,255,255,255,128,0,0,255,255,255,255,255,255,255,0,0,1,255,255,255,255,255,255,254,0,0,3,255,255,255,255,255,255,252,0,0,7,255,255,255,255,255,255,248,0,0,15,255,240,0,0,0,0,0,0,0,31,255,224,0,0,0,0,0,0,0,63,255,192,0,0,0,0,0,0,0,127,255,128,0,0,0,0,0,0,1,255,255,0,0,0,0,0,0,0,3,255,254,0,0,0,0,0,0,0,7,255,252,0,0,0,0,0,0,0,15,255,248,0,0,0,0,0,0,0,31,255,240,0,0,0,0,0,0,0,63,255,224,0,0,0,0,0,0,0,127,255,192,0,0,0,0,0,0,0,255,255,0,0,0,0,0,0,0,1,255,254,0,0,0,0,0,0,0,3,255,252,0,0,0,0,0,0,0,7,255,248,0,0,0,0,0,0,0,15,255,240,0,255,224,0,0,0,0,31,255,224,31,255,254,0,0,0,0,63,255,193,255,255,255,0,0,0,0,255,255,143,255,255,255,128,0,0,1,255,255,127,255,255,255,192,0,0,3,255,255,255,255,255,255,192,0,0,7,255,255,255,255,255,255,224,0,0,15,255,255,255,255,255,255,224,0,0,31,255,255,255,255,255,255,224,0,0,63,255,255,255,255,255,255,192,0,0,127,255,255,255,255,255,255,192,0,0,255,255,255,255,255,255,255,192,0,1,255,255,255,255,255,255,255,192,0,3,255,255,255,255,255,255,255,128,0,7,255,255,254,3,255,255,255,128,0,15,255,255,224,0,255,255,255,0,0,63,255,255,0,0,127,255,255,0,0,127,255,252,0,0,127,255,254,0,0,255,255,224,0,0,127,255,252,0,0,127,255,128,0,0,127,255,248,0,0,0,255,0,0,0,127,255,248,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,255,255,224,0,0,0,0,0,0,1,255,255,192,0,0,0,0,0,0,3,255,255,128,0,0,0,0,0,0,3,255,255,128,0,0,0,0,0,0,7,255,255,0,0,0,0,0,0,0,15,255,254,0,0,0,0,0,0,0,31,255,252,0,0,0,0,0,0,0,63,255,248,0,0,0,0,0,0,0,127,255,240,0,255,255,192,0,0,0,255,255,224,1,255,255,128,0,0,1,255,255,192,3,255,255,0,0,0,3,255,255,128,7,255,254,0,0,0,7,255,254,0,15,255,252,0,0,0,31,255,252,0,31,255,252,0,0,0,63,255,248,0,31,255,248,0,0,0,127,255,240,0,63,255,240,0,0,1,255,255,224,0,127,255,240,0,0,3,255,255,192,0,255,255,224,0,0,15,255,255,0,0,255,255,224,0,0,31,255,254,0,1,255,255,224,0,0,127,255,252,0,3,255,255,224,0,1,255,255,240,0,3,255,255,224,0,7,255,255,224,0,7,255,255,240,0,63,255,255,128,0,7,255,255,252,3,255,255,255,0,0,15,255,255,255,255,255,255,252,0,0,15,255,255,255,255,255,255,240,0,0,15,255,255,255,255,255,255,224,0,0,15,255,255,255,255,255,255,128,0,0,15,255,255,255,255,255,254,0,0,0,15,255,255,255,255,255,248,0,0,0,15,255,255,255,255,255,224,0,0,0,15,255,255,255,255,255,128,0,0,0,15,255,255,255,255,252,0,0,0,0,7,255,255,255,255,240,0,0,0,0,3,255,255,255,255,128,0,0,0,0,0,255,255,255,248,0,0,0,0,0,0,63,255,255,128,0,0,0,0,0,0,3,255,224,0,0,0,0,0,0,0,0,127,254,0,0,0,0,0,0,0,63,255,255,128,0,0,0,0,0,3,255,255,255,192,0,0,0,0,0,127,255,255,255,192,0,0,0,0,3,255,255,255,255,192,0,0,0,0,63,255,255,255,255,192,0,0,0,1,255,255,255,255,255,128,0,0,0,15,255,255,255,255,255,0,0,0,0,127,255,255,255,255,254,0,0,0,3,255,255,255,255,255,252,0,0,0,31,255,255,255,255,255,248,0,0,0,255,255,255,255,255,255,224,0,0,3,255,255,255,255,255,255,192,0,0,31,255,255,255,255,255,255,128,0,0,255,255,255,1,255,255,254,0,0,3,255,255,224,0,255,255,248,0,0,31,255,254,0,1,255,255,240,0,0,127,255,240,0,1,255,255,192,0,1,255,255,128,0,7,255,255,128,0,15,255,254,0,0,15,255,254,0,0,63,255,240,0,0,31,255,248,0,0,255,255,128,0,0,127,255,224,0,7,255,254,0,0,1,255,255,128,0,31,255,248,0,0,7,255,255,0,0,127,255,192,0,0,15,255,252,0,3,255,255,0,0,0,63,255,240,0,15,255,252,0,0,0,255,255,192,0,63,255,240,0,0,3,255,255,0,0,255,255,192,0,0,15,255,252,0,3,255,254,0,0,0,0,0,0,0,31,255,248,0,0,0,0,0,0,0,127,255,224,0,0,0,0,0,0,1,255,255,128,0,0,0,0,0,0,7,255,254,0,0,0,0,0,0,0,31,255,248,0,31,252,0,0,0,0,127,255,224,7,255,255,128,0,0,1,255,255,128,127,255,255,192,0,0,15,255,254,7,255,255,255,192,0,0,63,255,248,127,255,255,255,128,0,0,255,255,227,255,255,255,255,128,0,3,255,255,159,255,255,255,255,0,0,15,255,255,255,255,255,255,254,0,0,63,255,255,255,255,255,255,252,0,0,255,255,255,255,255,255,255,248,0,3,255,255,255,255,255,255,255,240,0,15,255,255,255,255,255,255,255,192,0,63,255,255,255,255,255,255,255,128,0,255,255,255,255,255,255,255,254,0,3,255,255,255,240,15,255,255,252,0,15,255,255,254,0,7,255,255,240,0,63,255,255,240,0,15,255,255,224,0,255,255,255,0,0,15,255,255,128,3,255,255,248,0,0,31,255,254,0,15,255,255,224,0,0,127,255,252,0,63,255,255,0,0,0,255,255,240,0,255,255,248,0,0,1,255,255,192,3,255,255,224,0,0,7,255,255,0,15,255,255,128,0,0,31,255,254,0,63,255,252,0,0,0,63,255,248,0,255,255,240,0,0,0,255,255,224,1,255,255,192,0,0,3,255,255,128,7,255,255,0,0,0,15,255,254,0,31,255,252,0,0,0,63,255,248,0,127,255,240,0,0,0,255,255,224,1,255,255,192,0,0,3,255,255,128,7,255,255,0,0,0,15,255,254,0,31,255,252,0,0,0,63,255,248,0,63,255,240,0,0,0,255,255,224,0,255,255,192,0,0,3,255,255,128,3,255,255,0,0,0,15,255,254,0,15,255,254,0,0,0,127,255,240,0,31,255,248,0,0,1,255,255,192,0,127,255,224,0,0,15,255,255,0,1,255,255,192,0,0,63,255,252,0,3,255,255,0,0,1,255,255,224,0,15,255,254,0,0,7,255,255,128,0,63,255,252,0,0,63,255,254,0,0,127,255,248,0,3,255,255,240,0,1,255,255,248,0,31,255,255,192,0,3,255,255,252,3,255,255,254,0,0,15,255,255,255,255,255,255,240,0,0,31,255,255,255,255,255,255,192,0,0,63,255,255,255,255,255,254,0,0,0,127,255,255,255,255,255,240,0,0,1,255,255,255,255,255,255,128,0,0,3,255,255,255,255,255,252,0,0,0,7,255,255,255,255,255,224,0,0,0,7,255,255,255,255,255,0,0,0,0,15,255,255,255,255,248,0,0,0,0,15,255,255,255,255,128,0,0,0,0,31,255,255,255,248,0,0,0,0,0,15,255,255,255,128,0,0,0,0,0,7,255,255,240,0,0,0,0,0,0,0,255,248,0,0,0,0,3,255,255,255,255,255,255,255,255,128,15,255,255,255,255,255,255,255,254,0,63,255,255,255,255,255,255,255,248,0,255,255,255,255,255,255,255,255,224,3,255,255,255,255,255,255,255,255,128,15,255,255,255,255,255,255,255,254,0,63,255,255,255,255,255,255,255,248,0,255,255,255,255,255,255,255,255,224,3,255,255,255,255,255,255,255,255,128,15,255,255,255,255,255,255,255,254,0,63,255,255,255,255,255,255,255,248,0,255,255,255,255,255,255,255,255,192,3,255,255,255,255,255,255,255,254,0,15,255,255,255,255,255,255,255,248,0,63,255,255,255,255,255,255,255,192,0,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,31,255,240,0,0,0,0,0,0,0,255,255,192,0,0,0,0,0,0,7,255,254,0,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,1,255,255,128,0,0,0,0,0,0,7,255,254,0,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,1,255,255,128,0,0,0,0,0,0,15,255,252,0,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,1,255,255,128,0,0,0,0,0,0,15,255,252,0,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,1,255,255,128,0,0,0,0,0,0,15,255,252,0,0,0,0,0,0,0,127,255,240,0,0,0,0,0,0,1,255,255,128,0,0,0,0,0,0,15,255,254,0,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,1,255,255,128,0,0,0,0,0,0,15,255,254,0,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,1,255,255,192,0,0,0,0,0,0,7,255,254,0,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,0,255,255,192,0,0,0,0,0,0,7,255,254,0,0,0,0,0,0,0,63,255,248,0,0,0,0,0,0,0,255,255,192,0,0,0,0,0,0,7,255,255,0,0,0,0,0,0,0,31,255,248,0,0,0,0,0,0,0,255,255,224,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,0,31,255,252,0,0,0,0,0,0,0,127,255,240,0,0,0,0,0,0,1,255,255,128,0,0,0,0,0,0,15,255,254,0,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,1,255,255,192,0,0,0,0,0,0,7,255,254,0,0,0,0,0,0,0,31,255,248,0,0,0,0,0,0,0,255,255,224,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,0,31,255,252,0,0,0,0,0,0,0,127,255,240,0,0,0,0,0,0,1,255,255,128,0,0,0,0,0,0,15,255,254,0,0,0,0,0,0,0,63,255,248,0,0,0,0,0,0,0,255,255,224,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,0,31,255,252,0,0,0,0,0,0,0,127,255,240,0,0,0,0,0,0,1,255,255,192,0,0,0,0,0,0,15,255,254,0,0,0,0,0,0,0,63,255,248,0,0,0,0,0,0,0,255,255,224,0,0,0,0,0,0,3,255,255,128,0,0,0,0,0,0,15,255,254,0,0,0,0,0,0,0,127,255,248,0,0,0,0,0,0,1,255,255,192,0,0,0,0,0,0,7,255,255,0,0,0,0,0,0,0,31,255,252,0,0,0,0,0,0,0,127,255,240,0,0,0,0,0,0,1,255,255,192,0,0,0,0,0,0,7,255,255,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,3,255,255,192,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,3,255,255,192,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,0,127,252,0,0,0,0,0,0,0,31,255,255,192,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,31,255,255,255,252,0,0,0,0,0,255,255,255,255,254,0,0,0,0,7,255,255,255,255,254,0,0,0,0,31,255,255,255,255,255,0,0,0,0,127,255,255,255,255,255,0,0,0,1,255,255,255,255,255,255,0,0,0,7,255,255,255,255,255,255,0,0,0,31,255,255,255,255,255,255,0,0,0,127,255,255,255,255,255,255,0,0,0,255,255,255,255,255,255,254,0,0,3,255,255,255,255,255,255,254,0,0,15,255,255,252,7,255,255,252,0,0,31,255,255,192,1,255,255,252,0,0,63,255,254,0,0,255,255,248,0,0,255,255,248,0,0,255,255,248,0,1,255,255,224,0,0,255,255,240,0,3,255,255,128,0,0,255,255,224,0,7,255,254,0,0,0,255,255,192,0,31,255,252,0,0,1,255,255,192,0,63,255,248,0,0,3,255,255,128,0,127,255,224,0,0,3,255,255,0,0,255,255,192,0,0,7,255,254,0,1,255,255,128,0,0,15,255,252,0,3,255,255,0,0,0,31,255,248,0,7,255,254,0,0,0,63,255,240,0,15,255,252,0,0,0,127,255,224,0,31,255,248,0,0,0,255,255,192,0,31,255,248,0,0,3,255,255,0,0,63,255,240,0,0,7,255,254,0,0,127,255,224,0,0,15,255,252,0,0,255,255,224,0,0,63,255,248,0,0,255,255,192,0,0,127,255,224,0,1,255,255,192,0,1,255,255,192,0,1,255,255,224,0,15,255,255,0,0,3,255,255,224,0,63,255,254,0,0,3,255,255,248,3,255,255,248,0,0,3,255,255,255,255,255,255,224,0,0,3,255,255,255,255,255,255,128,0,0,7,255,255,255,255,255,254,0,0,0,3,255,255,255,255,255,248,0,0,0,3,255,255,255,255,255,224,0,0,0,3,255,255,255,255,255,128,0,0,0,1,255,255,255,255,252,0,0,0,0,3,255,255,255,255,248,0,0,0,0,31,255,255,255,255,252,0,0,0,0,255,255,255,255,255,254,0,0,0,7,255,255,255,255,255,255,0,0,0,31,255,255,255,255,255,255,0,0,0,127,255,255,255,255,255,255,0,0,1,255,255,255,255,255,255,255,0,0,7,255,255,254,3,255,255,255,0,0,15,255,255,192,0,127,255,254,0,0,63,255,254,0,0,63,255,254,0,0,255,255,248,0,0,63,255,254,0,1,255,255,224,0,0,63,255,252,0,3,255,255,128,0,0,63,255,248,0,15,255,254,0,0,0,63,255,248,0,31,255,252,0,0,0,127,255,240,0,63,255,248,0,0,0,255,255,224,0,255,255,224,0,0,0,255,255,224,1,255,255,192,0,0,1,255,255,192,3,255,255,128,0,0,3,255,255,128,7,255,255,0,0,0,7,255,255,0,15,255,254,0,0,0,15,255,254,0,31,255,252,0,0,0,31,255,252,0,63,255,248,0,0,0,63,255,248,0,127,255,240,0,0,0,127,255,240,0,255,255,224,0,0,0,255,255,224,1,255,255,224,0,0,3,255,255,192,3,255,255,192,0,0,7,255,255,128,3,255,255,128,0,0,15,255,254,0,7,255,255,128,0,0,63,255,252,0,15,255,255,128,0,0,255,255,248,0,15,255,255,128,0,3,255,255,224,0,31,255,255,128,0,15,255,255,192,0,63,255,255,192,0,127,255,255,128,0,63,255,255,248,7,255,255,254,0,0,127,255,255,255,255,255,255,252,0,0,127,255,255,255,255,255,255,240,0,0,127,255,255,255,255,255,255,192,0,0,255,255,255,255,255,255,255,128,0,0,255,255,255,255,255,255,254,0,0,0,255,255,255,255,255,255,248,0,0,0,255,255,255,255,255,255,224,0,0,0,127,255,255,255,255,255,0,0,0,0,127,255,255,255,255,252,0,0,0,0,63,255,255,255,255,224,0,0,0,0,31,255,255,255,255,0,0,0,0,0,15,255,255,255,248,0,0,0,0,0,3,255,255,255,128,0,0,0,0,0,0,31,255,192,0,0,0,0,0,0,0,1,255,240,0,0,0,0,0,0,0,255,255,254,0,0,0,0,0,0,31,255,255,255,0,0,0,0,0,1,255,255,255,255,128,0,0,0,0,31,255,255,255,255,0,0,0,0,1,255,255,255,255,255,0,0,0,0,15,255,255,255,255,254,0,0,0,0,127,255,255,255,255,252,0,0,0,3,255,255,255,255,255,248,0,0,0,31,255,255,255,255,255,240,0,0,0,255,255,255,255,255,255,224,0,0,7,255,255,255,255,255,255,192,0,0,63,255,255,255,255,255,255,128,0,0,255,255,255,255,255,255,254,0,0,7,255,255,252,3,255,255,252,0,0,63,255,255,128,1,255,255,240,0,0,255,255,252,0,3,255,255,224,0,7,255,255,224,0,3,255,255,128,0,31,255,255,0,0,7,255,255,0,0,127,255,248,0,0,31,255,252,0,3,255,255,192,0,0,63,255,240,0,15,255,255,0,0,0,255,255,224,0,63,255,248,0,0,1,255,255,128,0,255,255,224,0,0,7,255,254,0,7,255,255,128,0,0,31,255,252,0,31,255,252,0,0,0,63,255,240,0,127,255,240,0,0,0,255,255,192,1,255,255,192,0,0,3,255,255,0,7,255,255,0,0,0,15,255,254,0,31,255,252,0,0,0,63,255,248,0,127,255,240,0,0,0,255,255,224,1,255,255,192,0,0,3,255,255,128,7,255,255,0,0,0,15,255,254,0,31,255,252,0,0,0,63,255,248,0,127,255,240,0,0,0,255,255,224,1,255,255,192,0,0,3,255,255,128,7,255,255,0,0,0,31,255,254,0,15,255,254,0,0,0,127,255,252,0,63,255,248,0,0,1,255,255,240,0,255,255,240,0,0,15,255,255,192,3,255,255,192,0,0,63,255,255,0,7,255,255,128,0,1,255,255,252,0,31,255,254,0,0,7,255,255,240,0,127,255,252,0,0,63,255,255,192,0,255,255,252,0,3,255,255,255,0,3,255,255,248,0,31,255,255,252,0,7,255,255,252,3,255,255,255,240,0,31,255,255,255,255,255,255,255,192,0,63,255,255,255,255,255,255,255,0,0,255,255,255,255,255,255,255,252,0,1,255,255,255,255,255,255,255,240,0,3,255,255,255,255,255,255,255,192,0,7,255,255,255,255,255,255,255,0,0,15,255,255,255,255,223,255,248,0,0,31,255,255,255,254,127,255,224,0,0,31,255,255,255,225,255,255,128,0,0,63,255,255,254,7,255,254,0,0,0,63,255,255,224,31,255,248,0,0,0,31,255,254,0,127,255,224,0,0,0,7,255,128,1,255,255,128,0,0,0,0,0,0,7,255,254,0,0,0,0,0,0,0,31,255,248,0,0,0,0,0,0,0,127,255,224,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,0,15,255,252,0,3,255,255,0,0,0,63,255,240,0,15,255,252,0,0,0,255,255,192,0,63,255,240,0,0,3,255,255,0,0,255,255,192,0,0,15,255,248,0,3,255,255,0,0,0,127,255,224,0,15,255,254,0,0,1,255,255,128,0,31,255,248,0,0,7,255,252,0,0,127,255,224,0,0,63,255,240,0,1,255,255,128,0,0,255,255,192,0,7,255,255,0,0,7,255,254,0,0,31,255,254,0,0,31,255,248,0,0,63,255,248,0,0,255,255,224,0,0,255,255,240,0,7,255,255,0,0,1,255,255,240,0,127,255,252,0,0,7,255,255,248,15,255,255,224,0,0,31,255,255,255,255,255,255,128,0,0,63,255,255,255,255,255,252,0,0,0,127,255,255,255,255,255,224,0,0,1,255,255,255,255,255,255,128,0,0,3,255,255,255,255,255,252,0,0,0,7,255,255,255,255,255,224,0,0,0,15,255,255,255,255,255,0,0,0,0,31,255,255,255,255,248,0,0,0,0,63,255,255,255,255,192,0,0,0,0,63,255,255,255,252,0,0,0,0,0,63,255,255,255,192,0,0,0,0,0,63,255,255,252,0,0,0,0,0,0,31,255,255,128,0,0,0,0,0,0,7,255,192,0,0,0,0,0,0,0,31,254,0,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,248,0,0,0,0,0,127,254,0,0,0,0,0,31,255,255,254,0,0,0,0,0,255,252,0,0,0,0,0,63,255,255,255,0,0,0,0,0,255,248,0,0,0,0,0,127,255,255,255,128,0,0,0,1,255,248,0,0,0,0,0,255,255,255,255,192,0,0,0,3,255,240,0,0,0,0,1,255,255,255,255,224,0,0,0,7,255,224,0,0,0,0,3,255,255,255,255,240,0,0,0,7,255,192,0,0,0,0,3,255,255,255,255,240,0,0,0,15,255,192,0,0,0,0,7,255,252,15,255,248,0,0,0,31,255,128,0,0,0,0,7,255,240,3,255,248,0,0,0,31,255,0,0,0,0,0,7,255,224,1,255,248,0,0,0,63,255,0,0,0,0,0,15,255,192,0,255,252,0,0,0,127,254,0,0,0,0,0,15,255,128,0,127,252,0,0,0,127,252,0,0,0,0,0,15,255,128,0,127,252,0,0,0,255,252,0,0,0,0,0,15,255,128,0,127,252,0,0,1,255,248,0,0,0,0,0,31,255,128,0,63,254,0,0,3,255,240,0,0,0,0,0,31,255,0,0,63,254,0,0,3,255,224,0,0,0,0,0,31,255,0,0,63,254,0,0,7,255,224,0,0,0,0,0,31,255,0,0,63,254,0,0,15,255,192,0,0,0,0,0,31,255,0,0,63,254,0,0,15,255,128,0,0,0,0,0,31,255,0,0,63,254,0,0,31,255,128,0,0,0,0,0,31,255,0,0,63,254,0,0,63,255,0,0,0,0,0,0,31,255,0,0,63,254,0,0,63,254,0,0,0,0,0,0,31,255,0,0,63,254,0,0,127,254,0,0,0,0,0,0,31,255,0,0,63,254,0,0,255,252,0,0,0,0,0,0,31,255,0,0,63,254,0,1,255,248,0,0,0,0,0,0,31,255,0,0,63,254,0,1,255,240,0,0,0,0,0,0,31,255,0,0,63,254,0,3,255,240,0,0,0,0,0,0,31,255,128,0,63,254,0,7,255,224,0,0,0,0,0,0,15,255,128,0,127,252,0,7,255,192,0,0,0,0,0,0,15,255,128,0,127,252,0,15,255,192,0,0,0,0,0,0,15,255,128,0,127,252,0,31,255,128,0,0,0,0,0,0,15,255,192,0,255,252,0,31,255,0,0,0,0,0,0,0,7,255,224,1,255,248,0,63,254,0,0,0,0,0,0,0,7,255,240,3,255,248,0,127,254,0,0,0,0,0,0,0,7,255,252,15,255,248,0,255,252,0,0,0,0,0,0,0,3,255,255,255,255,240,0,255,248,0,0,0,0,0,0,0,3,255,255,255,255,240,1,255,248,0,0,0,0,0,0,0,1,255,255,255,255,224,3,255,240,0,0,0,0,0,0,0,0,255,255,255,255,192,3,255,224,0,0,0,0,0,0,0,0,127,255,255,255,128,7,255,224,0,0,0,0,0,0,0,0,63,255,255,255,0,15,255,192,0,0,0,0,0,0,0,0,31,255,255,254,0,15,255,128,0,0,0,0,0,0,0,0,7,255,255,248,0,31,255,0,0,0,0,0,0,0,0,0,1,255,255,224,0,63,255,0,0,63,252,0,0,0,0,0,0,31,254,0,0,127,254,0,3,255,255,192,0,0,0,0,0,0,0,0,0,127,252,0,15,255,255,240,0,0,0,0,0,0,0,0,0,255,252,0,63,255,255,252,0,0,0,0,0,0,0,0,1,255,248,0,127,255,255,254,0,0,0,0,0,0,0,0,1,255,240,0,255,255,255,255,0,0,0,0,0,0,0,0,3,255,224,1,255,255,255,255,128,0,0,0,0,0,0,0,7,255,224,3,255,255,255,255,192,0,0,0,0,0,0,0,7,255,192,7,255,255,255,255,224,0,0,0,0,0,0,0,15,255,128,7,255,255,255,255,224,0,0,0,0,0,0,0,31,255,128,15,255,248,31,255,240,0,0,0,0,0,0,0,63,255,0,15,255,224,7,255,240,0,0,0,0,0,0,0,63,254,0,15,255,192,3,255,240,0,0,0,0,0,0,0,127,254,0,31,255,128,1,255,248,0,0,0,0,0,0,0,255,252,0,31,255,0,0,255,248,0,0,0,0,0,0,0,255,248,0,31,255,0,0,255,248,0,0,0,0,0,0,1,255,240,0,31,255,0,0,255,248,0,0,0,0,0,0,3,255,240,0,63,255,0,0,255,252,0,0,0,0,0,0,7,255,224,0,63,254,0,0,127,252,0,0,0,0,0,0,7,255,192,0,63,254,0,0,127,252,0,0,0,0,0,0,15,255,192,0,63,254,0,0,127,252,0,0,0,0,0,0,31,255,128,0,63,254,0,0,127,252,0,0,0,0,0,0,31,255,0,0,63,254,0,0,127,252,0,0,0,0,0,0,63,255,0,0,63,254,0,0,127,252,0,0,0,0,0,0,127,254,0,0,63,254,0,0,127,252,0,0,0,0,0,0,127,252,0,0,63,254,0,0,127,252,0,0,0,0,0,0,255,248,0,0,63,254,0,0,127,252,0,0,0,0,0,1,255,248,0,0,63,254,0,0,127,252,0,0,0,0,0,3,255,240,0,0,63,254,0,0,127,252,0,0,0,0,0,3,255,224,0,0,63,254,0,0,127,252,0,0,0,0,0,7,255,224,0,0,63,255,0,0,255,252,0,0,0,0,0,15,255,192,0,0,31,255,0,0,255,248,0,0,0,0,0,15,255,128,0,0,31,255,0,0,255,248,0,0,0,0,0,31,255,0,0,0,31,255,0,0,255,248,0,0,0,0,0,63,255,0,0,0,31,255,128,1,255,248,0,0,0,0,0,63,254,0,0,0,15,255,192,3,255,240,0,0,0,0,0,127,252,0,0,0,15,255,224,7,255,240,0,0,0,0,0,255,252,0,0,0,15,255,248,31,255,240,0,0,0,0,1,255,248,0,0,0,7,255,255,255,255,224,0,0,0,0,1,255,240,0,0,0,7,255,255,255,255,224,0,0,0,0,3,255,240,0,0,0,3,255,255,255,255,192,0,0,0,0,7,255,224,0,0,0,1,255,255,255,255,128,0,0,0,0,7,255,192,0,0,0,0,255,255,255,255,0,0,0,0,0,15,255,128,0,0,0,0,127,255,255,254,0,0,0,0,0,31,255,128,0,0,0,0,63,255,255,252,0,0,0,0,0,31,255,0,0,0,0,0,15,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,63,252,0,0,0,0,7,255,255,0,0,0,127,255,240,0,0,7,255,255,0,0,0,127,255,240,0,0,7,255,255,0,0,0,127,255,240,0,0,7,255,255,0,0,0,127,255,240,0,0,7,255,255,0,0,0,127,255,240,0,0,7,255,255,0,0,0,127,255,240,0,0,7,255,255,0,0,0,127,255,240,0,0,7,255,255,0,0,0,127,255,240,0,0,7,255,255,0,0,0,127,255,240,0,0,7,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,0,0,0,127,255,240,0,0,7,255,255,0,0,0,127,255,240,0,0,7,255,255,0,0,0,127,255,240,0,0,7,255,255,0,0,0,127,255,240,0,0,7,255,255,0,0,0,127,255,240,0,0,7,255,255,0,0,0,127,255,240,0,0,7,255,255,0,0,0,127,255,240,0,0,7,255,255,0,0,0,127,255,240,0,0,7,255,255,0,0,0,127,255,240,0,0,7,255,255,0,0,7,255,255,255,252,0,255,255,255,255,128,31,255,255,255,240,3,255,255,255,254,0,127,255,255,255,192,15,255,255,255,248,1,255,255,255,255,0,63,255,255,255,224,7,255,255,255,252,0,255,255,255,255,128,31,255,255,255,240,3,255,255,255,254,0,127,255,255,255,192,15,255,255,255,248,1,255,255,255,255,0,63,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,252,0,0,31,255,252,0,0,31,255,252,0,0,31,255,252,0,0,31,255,252,0,0,31,255,252,0,0,31,255,252,0,0,31,255,252,0,0,31,255,252,0,0,31,255,252,0,0,31,255,252,0,0,31,255,252,0,0,31,255,252,0,0,31,255,252,0,0,31,255,252,0,0,31,255,252,0,0,31,255,252,0,0,31,255,252,0,0,31,255,252,0}; +static const Glyph f0_132_glyphs[]={{0,78,94,0,0,78,48},{917,78,91,0,1,78,49},{1805,79,92,0,0,79,50},{2714,79,94,0,0,79,51},{3643,78,92,0,0,78,52},{4540,79,92,0,2,79,53},{5449,78,94,0,0,78,54},{6366,78,90,0,2,78,55},{7244,79,94,0,0,79,56},{8173,78,94,0,0,78,57},{9090,128,94,0,0,128,37},{10594,44,69,0,23,44,58},{10974,43,44,0,48,43,45},{11211,40,19,0,73,40,46},{11306,26,0,0,92,26,32}}; +static const uint8_t f0_144_bits[]={0,0,0,0,15,255,192,0,0,0,0,0,0,0,7,255,255,248,0,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,31,255,255,255,254,0,0,0,0,0,0,255,255,255,255,254,0,0,0,0,0,15,255,255,255,255,252,0,0,0,0,0,127,255,255,255,255,248,0,0,0,0,3,255,255,255,255,255,240,0,0,0,0,63,255,255,255,255,255,240,0,0,0,0,255,255,255,255,255,255,192,0,0,0,7,255,255,255,255,255,255,128,0,0,0,63,255,255,255,255,255,255,0,0,0,1,255,255,255,255,255,255,254,0,0,0,7,255,255,255,255,255,255,248,0,0,0,63,255,255,255,255,255,255,240,0,0,1,255,255,255,255,255,255,255,224,0,0,7,255,255,248,0,127,255,255,128,0,0,63,255,255,128,0,127,255,255,0,0,0,255,255,252,0,0,255,255,252,0,0,3,255,255,224,0,1,255,255,240,0,0,31,255,255,0,0,3,255,255,224,0,0,127,255,252,0,0,15,255,255,128,0,1,255,255,224,0,0,31,255,254,0,0,15,255,255,0,0,0,127,255,252,0,0,63,255,252,0,0,0,255,255,240,0,0,255,255,240,0,0,3,255,255,192,0,7,255,255,128,0,0,7,255,255,128,0,31,255,254,0,0,0,31,255,254,0,0,127,255,248,0,0,0,127,255,248,0,1,255,255,224,0,0,1,255,255,224,0,7,255,255,128,0,0,7,255,255,128,0,63,255,252,0,0,0,15,255,255,0,0,255,255,240,0,0,0,63,255,252,0,3,255,255,192,0,0,0,255,255,240,0,15,255,255,0,0,0,3,255,255,192,0,63,255,252,0,0,0,15,255,255,0,0,255,255,240,0,0,0,63,255,252,0,3,255,255,192,0,0,0,255,255,240,0,15,255,255,0,0,0,3,255,255,192,0,127,255,252,0,0,0,15,255,255,128,1,255,255,240,0,0,0,63,255,254,0,7,255,255,192,0,0,0,255,255,248,0,31,255,255,0,0,0,3,255,255,224,0,127,255,252,0,0,0,15,255,255,128,1,255,255,224,0,0,0,63,255,254,0,7,255,255,128,0,0,0,127,255,248,0,31,255,254,0,0,0,1,255,255,224,0,127,255,248,0,0,0,7,255,255,128,1,255,255,224,0,0,0,31,255,254,0,7,255,255,128,0,0,0,127,255,248,0,31,255,254,0,0,0,1,255,255,224,0,127,255,248,0,0,0,7,255,255,128,1,255,255,224,0,0,0,31,255,254,0,7,255,255,128,0,0,0,127,255,248,0,31,255,254,0,0,0,1,255,255,224,0,127,255,248,0,0,0,7,255,255,128,1,255,255,224,0,0,0,31,255,254,0,7,255,255,128,0,0,0,127,255,248,0,31,255,254,0,0,0,1,255,255,224,0,127,255,252,0,0,0,15,255,255,128,1,255,255,240,0,0,0,63,255,254,0,7,255,255,192,0,0,0,255,255,248,0,31,255,255,0,0,0,3,255,255,224,0,127,255,252,0,0,0,15,255,255,128,0,255,255,240,0,0,0,63,255,252,0,3,255,255,192,0,0,0,255,255,240,0,15,255,255,0,0,0,3,255,255,192,0,63,255,252,0,0,0,15,255,255,0,0,255,255,240,0,0,0,63,255,252,0,3,255,255,192,0,0,0,255,255,240,0,15,255,255,0,0,0,3,255,255,192,0,63,255,252,0,0,0,15,255,255,0,0,127,255,248,0,0,0,127,255,248,0,1,255,255,224,0,0,1,255,255,224,0,7,255,255,128,0,0,7,255,255,128,0,31,255,254,0,0,0,31,255,254,0,0,127,255,248,0,0,0,127,255,248,0,0,255,255,240,0,0,3,255,255,192,0,3,255,255,192,0,0,15,255,255,0,0,15,255,255,0,0,0,63,255,252,0,0,31,255,254,0,0,1,255,255,224,0,0,127,255,248,0,0,7,255,255,128,0,1,255,255,240,0,0,63,255,254,0,0,3,255,255,224,0,1,255,255,240,0,0,15,255,255,192,0,15,255,255,192,0,0,63,255,255,128,0,127,255,255,0,0,0,127,255,255,128,7,255,255,248,0,0,1,255,255,255,255,255,255,255,224,0,0,3,255,255,255,255,255,255,255,0,0,0,7,255,255,255,255,255,255,248,0,0,0,31,255,255,255,255,255,255,224,0,0,0,63,255,255,255,255,255,255,0,0,0,0,127,255,255,255,255,255,248,0,0,0,0,255,255,255,255,255,255,192,0,0,0,1,255,255,255,255,255,254,0,0,0,0,3,255,255,255,255,255,240,0,0,0,0,7,255,255,255,255,255,128,0,0,0,0,15,255,255,255,255,252,0,0,0,0,0,15,255,255,255,255,192,0,0,0,0,0,15,255,255,255,254,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,7,255,255,248,0,0,0,0,0,0,0,0,255,252,0,0,0,0,0,0,0,0,0,0,0,127,192,0,0,0,0,0,0,0,0,7,255,0,0,0,0,0,0,0,0,0,127,252,0,0,0,0,0,0,0,0,7,255,240,0,0,0,0,0,0,0,0,127,255,192,0,0,0,0,0,0,0,7,255,255,0,0,0,0,0,0,0,0,127,255,252,0,0,0,0,0,0,0,15,255,255,240,0,0,0,0,0,0,0,255,255,255,192,0,0,0,0,0,0,31,255,255,255,0,0,0,0,0,0,7,255,255,255,252,0,0,0,0,0,0,255,255,255,255,240,0,0,0,0,0,127,255,255,255,255,192,0,0,0,0,15,255,255,255,255,255,0,0,0,0,0,63,255,255,255,255,252,0,0,0,0,0,255,255,255,255,255,240,0,0,0,0,3,255,255,255,255,255,192,0,0,0,0,15,255,255,255,255,255,0,0,0,0,0,63,255,255,255,255,252,0,0,0,0,0,255,255,255,255,255,240,0,0,0,0,3,255,255,255,255,255,192,0,0,0,0,15,255,255,255,255,255,0,0,0,0,0,63,255,255,255,255,252,0,0,0,0,0,255,255,255,255,255,240,0,0,0,0,3,255,255,255,255,255,192,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,3,255,255,192,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,3,255,255,192,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,3,255,255,192,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,3,255,255,192,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,3,255,255,192,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,3,255,255,192,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,3,255,255,192,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,3,255,255,192,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,3,255,255,192,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,3,255,255,192,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,3,255,255,192,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,3,255,255,192,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,3,255,255,192,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,3,255,255,192,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,3,255,255,255,255,255,255,255,255,0,0,15,255,255,255,255,255,255,255,252,0,0,63,255,255,255,255,255,255,255,240,0,0,255,255,255,255,255,255,255,255,192,0,3,255,255,255,255,255,255,255,255,0,0,15,255,255,255,255,255,255,255,252,0,0,63,255,255,255,255,255,255,255,240,0,0,255,255,255,255,255,255,255,255,192,0,3,255,255,255,255,255,255,255,255,0,0,15,255,255,255,255,255,255,255,252,0,0,63,255,255,255,255,255,255,255,240,0,0,255,255,255,255,255,255,255,255,192,0,3,255,255,255,255,255,255,255,255,0,0,15,255,255,255,255,255,255,255,252,0,0,63,255,255,255,255,255,255,255,240,0,0,255,255,255,255,255,255,255,255,192,0,0,0,0,7,255,240,0,0,0,0,0,0,0,3,255,255,254,0,0,0,0,0,0,0,255,255,255,255,0,0,0,0,0,0,31,255,255,255,255,0,0,0,0,0,1,255,255,255,255,255,0,0,0,0,0,31,255,255,255,255,255,0,0,0,0,0,255,255,255,255,255,254,0,0,0,0,15,255,255,255,255,255,254,0,0,0,0,127,255,255,255,255,255,252,0,0,0,3,255,255,255,255,255,255,248,0,0,0,31,255,255,255,255,255,255,224,0,0,0,255,255,255,255,255,255,255,192,0,0,3,255,255,255,255,255,255,255,128,0,0,31,255,255,255,255,255,255,255,0,0,0,255,255,255,255,255,255,255,252,0,0,7,255,255,255,255,255,255,255,248,0,0,31,255,255,248,0,63,255,255,224,0,0,255,255,255,128,0,63,255,255,192,0,3,255,255,248,0,0,127,255,255,0,0,15,255,255,192,0,0,255,255,252,0,0,127,255,254,0,0,1,255,255,248,0,1,255,255,240,0,0,3,255,255,224,0,7,255,255,192,0,0,15,255,255,128,0,31,255,254,0,0,0,31,255,254,0,0,255,255,248,0,0,0,127,255,252,0,3,255,255,224,0,0,1,255,255,240,0,15,255,255,0,0,0,7,255,255,192,0,63,255,252,0,0,0,15,255,255,0,0,255,255,240,0,0,0,63,255,252,0,3,255,255,192,0,0,1,255,255,240,0,15,255,255,0,0,0,7,255,255,192,0,63,255,252,0,0,0,31,255,255,0,0,255,255,240,0,0,0,127,255,252,0,3,255,255,192,0,0,1,255,255,240,0,15,255,255,0,0,0,7,255,255,192,0,31,255,252,0,0,0,63,255,254,0,0,0,0,0,0,0,0,255,255,248,0,0,0,0,0,0,0,7,255,255,224,0,0,0,0,0,0,0,31,255,255,128,0,0,0,0,0,0,0,255,255,252,0,0,0,0,0,0,0,7,255,255,240,0,0,0,0,0,0,0,31,255,255,192,0,0,0,0,0,0,0,255,255,254,0,0,0,0,0,0,0,7,255,255,248,0,0,0,0,0,0,0,63,255,255,192,0,0,0,0,0,0,1,255,255,255,0,0,0,0,0,0,0,15,255,255,248,0,0,0,0,0,0,0,127,255,255,192,0,0,0,0,0,0,3,255,255,255,0,0,0,0,0,0,0,31,255,255,248,0,0,0,0,0,0,1,255,255,255,192,0,0,0,0,0,0,15,255,255,254,0,0,0,0,0,0,0,127,255,255,248,0,0,0,0,0,0,3,255,255,255,192,0,0,0,0,0,0,63,255,255,254,0,0,0,0,0,0,1,255,255,255,240,0,0,0,0,0,0,15,255,255,255,128,0,0,0,0,0,0,127,255,255,252,0,0,0,0,0,0,7,255,255,255,224,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,1,255,255,255,240,0,0,0,0,0,0,31,255,255,255,128,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,7,255,255,255,224,0,0,0,0,0,0,63,255,255,254,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,31,255,255,255,128,0,0,0,0,0,0,255,255,255,248,0,0,0,0,0,0,7,255,255,255,192,0,0,0,0,0,0,63,255,255,252,0,0,0,0,0,0,1,255,255,255,224,0,0,0,0,0,0,15,255,255,255,0,0,0,0,0,0,0,127,255,255,240,0,0,0,0,0,0,3,255,255,255,128,0,0,0,0,0,0,31,255,255,252,0,0,0,0,0,0,0,255,255,255,192,0,0,0,0,0,0,3,255,255,254,0,0,0,0,0,0,0,31,255,255,240,0,0,0,0,0,0,0,255,255,255,128,0,0,0,0,0,0,3,255,255,248,0,0,0,0,0,0,0,31,255,255,192,0,0,0,0,0,0,0,255,255,255,0,0,0,0,0,0,0,3,255,255,248,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,255,128,0,127,255,255,255,255,255,255,255,254,0,1,255,255,255,255,255,255,255,255,248,0,15,255,255,255,255,255,255,255,255,224,0,63,255,255,255,255,255,255,255,255,128,0,255,255,255,255,255,255,255,255,254,0,3,255,255,255,255,255,255,255,255,248,0,31,255,255,255,255,255,255,255,255,224,0,127,255,255,255,255,255,255,255,255,128,1,255,255,255,255,255,255,255,255,254,0,7,255,255,255,255,255,255,255,255,248,0,31,255,255,255,255,255,255,255,255,224,0,127,255,255,255,255,255,255,255,255,128,1,255,255,255,255,255,255,255,255,254,0,7,255,255,255,255,255,255,255,255,248,0,31,255,255,255,255,255,255,255,255,224,0,127,255,255,255,255,255,255,255,255,128,1,255,255,255,255,255,255,255,255,254,0,0,0,0,0,31,255,192,0,0,0,0,0,0,0,15,255,255,248,0,0,0,0,0,0,1,255,255,255,254,0,0,0,0,0,0,63,255,255,255,254,0,0,0,0,0,3,255,255,255,255,254,0,0,0,0,0,63,255,255,255,255,254,0,0,0,0,1,255,255,255,255,255,254,0,0,0,0,31,255,255,255,255,255,252,0,0,0,0,255,255,255,255,255,255,248,0,0,0,7,255,255,255,255,255,255,240,0,0,0,63,255,255,255,255,255,255,224,0,0,1,255,255,255,255,255,255,255,192,0,0,7,255,255,255,255,255,255,255,128,0,0,63,255,255,255,255,255,255,254,0,0,1,255,255,255,255,255,255,255,252,0,0,7,255,255,255,255,255,255,255,240,0,0,63,255,255,240,0,255,255,255,224,0,0,255,255,254,0,0,127,255,255,128,0,7,255,255,240,0,0,255,255,255,0,0,31,255,255,0,0,1,255,255,252,0,0,127,255,252,0,0,3,255,255,240,0,1,255,255,224,0,0,7,255,255,192,0,15,255,255,0,0,0,31,255,255,128,0,63,255,252,0,0,0,63,255,254,0,0,255,255,240,0,0,0,255,255,248,0,3,255,255,128,0,0,3,255,255,224,0,15,255,254,0,0,0,15,255,255,128,0,63,255,248,0,0,0,63,255,254,0,0,255,255,224,0,0,0,127,255,248,0,3,255,255,128,0,0,1,255,255,224,0,15,255,254,0,0,0,15,255,255,128,0,0,0,0,0,0,0,63,255,254,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,3,255,255,192,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,0,127,255,252,0,0,0,0,0,0,0,1,255,255,224,0,0,0,0,0,0,0,15,255,255,128,0,0,0,0,0,0,0,127,255,252,0,0,0,0,0,0,0,3,255,255,240,0,0,0,0,0,0,0,31,255,255,128,0,0,0,0,0,0,1,255,255,252,0,0,0,0,0,0,0,63,255,255,224,0,0,0,0,0,31,255,255,255,255,0,0,0,0,0,0,127,255,255,255,248,0,0,0,0,0,1,255,255,255,255,192,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,31,255,255,255,224,0,0,0,0,0,0,127,255,255,255,0,0,0,0,0,0,1,255,255,255,240,0,0,0,0,0,0,7,255,255,255,192,0,0,0,0,0,0,31,255,255,255,224,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,1,255,255,255,255,192,0,0,0,0,0,7,255,255,255,255,192,0,0,0,0,0,31,255,255,255,255,128,0,0,0,0,0,127,255,255,255,255,0,0,0,0,0,1,255,255,255,255,254,0,0,0,0,0,3,255,255,255,255,252,0,0,0,0,0,0,0,31,255,255,248,0,0,0,0,0,0,0,15,255,255,224,0,0,0,0,0,0,0,31,255,255,192,0,0,0,0,0,0,0,31,255,255,0,0,0,0,0,0,0,0,63,255,254,0,0,0,0,0,0,0,0,255,255,248,0,0,0,0,0,0,0,1,255,255,240,0,0,0,0,0,0,0,7,255,255,192,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,255,255,248,0,0,0,0,0,0,0,3,255,255,224,0,255,255,224,0,0,0,15,255,255,128,3,255,255,128,0,0,0,31,255,254,0,15,255,254,0,0,0,0,127,255,248,0,63,255,248,0,0,0,3,255,255,224,0,255,255,224,0,0,0,15,255,255,128,3,255,255,128,0,0,0,63,255,254,0,15,255,254,0,0,0,0,255,255,248,0,63,255,252,0,0,0,3,255,255,224,0,127,255,240,0,0,0,31,255,255,128,1,255,255,224,0,0,0,127,255,252,0,7,255,255,128,0,0,3,255,255,240,0,31,255,255,0,0,0,15,255,255,192,0,63,255,254,0,0,0,127,255,255,0,0,255,255,252,0,0,7,255,255,248,0,3,255,255,252,0,0,63,255,255,224,0,7,255,255,252,0,7,255,255,255,0,0,31,255,255,255,255,255,255,255,252,0,0,63,255,255,255,255,255,255,255,224,0,0,127,255,255,255,255,255,255,255,128,0,1,255,255,255,255,255,255,255,252,0,0,3,255,255,255,255,255,255,255,224,0,0,7,255,255,255,255,255,255,255,0,0,0,15,255,255,255,255,255,255,248,0,0,0,31,255,255,255,255,255,255,192,0,0,0,63,255,255,255,255,255,254,0,0,0,0,63,255,255,255,255,255,224,0,0,0,0,127,255,255,255,255,255,0,0,0,0,0,127,255,255,255,255,240,0,0,0,0,0,127,255,255,255,255,0,0,0,0,0,0,63,255,255,255,224,0,0,0,0,0,0,31,255,255,252,0,0,0,0,0,0,0,1,255,252,0,0,0,0,0,0,0,0,3,255,255,224,0,0,0,0,0,0,0,15,255,255,128,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,7,255,255,192,0,0,0,0,0,0,0,31,255,255,0,0,0,0,0,0,0,0,127,255,248,0,0,0,0,0,0,0,1,255,255,224,0,0,0,0,0,0,0,15,255,255,128,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,7,255,255,192,0,0,0,0,0,0,0,31,255,254,0,0,0,0,0,0,0,0,127,255,248,0,127,192,0,0,0,0,3,255,255,224,3,255,0,0,0,0,0,15,255,255,0,15,252,0,0,0,0,0,63,255,252,0,63,240,0,0,0,0,1,255,255,224,1,255,192,0,0,0,0,7,255,255,128,7,255,0,0,0,0,0,31,255,254,0,31,252,0,0,0,0,0,255,255,240,0,255,240,0,0,0,0,3,255,255,192,3,255,192,0,0,0,0,15,255,255,0,31,255,0,0,0,0,0,127,255,248,0,127,252,0,0,0,0,1,255,255,224,1,255,240,0,0,0,0,7,255,255,0,15,255,192,0,0,0,0,63,255,252,0,63,255,0,0,0,0,0,255,255,224,1,255,252,0,0,0,0,7,255,255,128,7,255,240,0,0,0,0,31,255,252,0,31,255,192,0,0,0,0,127,255,240,0,255,255,0,0,0,0,3,255,255,128,3,255,252,0,0,0,0,15,255,254,0,31,255,240,0,0,0,0,127,255,240,0,127,255,192,0,0,0,1,255,255,192,3,255,255,0,0,0,0,15,255,254,0,15,255,252,0,0,0,0,63,255,248,0,127,255,240,0,0,0,0,255,255,224,1,255,255,192,0,0,0,7,255,255,0,15,255,255,0,0,0,0,31,255,248,0,63,255,252,0,0,0,0,255,255,224,0,255,255,240,0,0,0,3,255,255,0,3,255,255,192,0,0,0,31,255,252,0,15,255,255,0,0,0,0,127,255,224,0,63,255,252,0,0,0,3,255,255,128,0,255,255,240,0,0,0,15,255,252,0,3,255,255,192,0,0,0,127,255,240,0,15,255,255,0,0,0,1,255,255,128,0,63,255,252,0,0,0,15,255,254,0,0,255,255,240,0,0,0,63,255,240,0,3,255,255,192,0,0,1,255,255,128,0,15,255,255,0,0,0,7,255,254,0,0,63,255,252,0,0,0,63,255,240,0,0,255,255,240,0,0,0,255,255,192,0,3,255,255,192,0,0,7,255,254,0,0,15,255,255,0,0,0,31,255,240,0,0,63,255,252,0,0,0,255,255,192,0,0,255,255,240,0,0,3,255,254,0,0,3,255,255,192,0,0,31,255,240,0,0,15,255,255,0,0,0,255,255,192,0,0,63,255,252,0,0,3,255,254,0,0,0,255,255,240,0,0,31,255,240,0,0,3,255,255,192,0,0,127,255,128,0,0,15,255,255,0,0,3,255,255,255,255,255,255,255,255,255,224,15,255,255,255,255,255,255,255,255,255,128,63,255,255,255,255,255,255,255,255,254,0,255,255,255,255,255,255,255,255,255,248,3,255,255,255,255,255,255,255,255,255,224,15,255,255,255,255,255,255,255,255,255,128,63,255,255,255,255,255,255,255,255,254,0,255,255,255,255,255,255,255,255,255,248,3,255,255,255,255,255,255,255,255,255,224,15,255,255,255,255,255,255,255,255,255,128,63,255,255,255,255,255,255,255,255,254,0,255,255,255,255,255,255,255,255,255,248,3,255,255,255,255,255,255,255,255,255,224,15,255,255,255,255,255,255,255,255,255,128,63,255,255,255,255,255,255,255,255,254,0,255,255,255,255,255,255,255,255,255,248,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,3,255,255,192,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,3,255,255,192,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,3,255,255,192,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,3,255,255,192,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,3,255,255,192,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,255,255,240,0,0,0,3,255,255,255,255,255,255,255,128,0,0,31,255,255,255,255,255,255,252,0,0,0,255,255,255,255,255,255,255,224,0,0,15,255,255,255,255,255,255,255,0,0,0,127,255,255,255,255,255,255,248,0,0,3,255,255,255,255,255,255,255,192,0,0,31,255,255,255,255,255,255,254,0,0,0,255,255,255,255,255,255,255,240,0,0,7,255,255,255,255,255,255,255,128,0,0,63,255,255,255,255,255,255,252,0,0,1,255,255,255,255,255,255,255,224,0,0,15,255,255,255,255,255,255,255,0,0,0,127,255,255,255,255,255,255,248,0,0,3,255,255,255,255,255,255,255,192,0,0,31,255,255,255,255,255,255,254,0,0,0,255,255,255,255,255,255,255,240,0,0,7,255,255,255,255,255,255,255,128,0,0,127,255,255,255,255,255,255,252,0,0,3,255,255,128,0,0,0,0,0,0,0,31,255,248,0,0,0,0,0,0,0,0,255,255,192,0,0,0,0,0,0,0,7,255,254,0,0,0,0,0,0,0,0,63,255,240,0,0,0,0,0,0,0,1,255,255,128,0,0,0,0,0,0,0,15,255,252,0,0,0,0,0,0,0,0,127,255,224,0,0,0,0,0,0,0,3,255,255,0,0,0,0,0,0,0,0,31,255,248,0,0,0,0,0,0,0,0,255,255,192,0,0,0,0,0,0,0,7,255,254,0,0,0,0,0,0,0,0,127,255,240,0,0,0,0,0,0,0,3,255,255,128,0,0,0,0,0,0,0,31,255,252,0,0,0,0,0,0,0,0,255,255,192,0,0,0,0,0,0,0,7,255,254,0,15,255,0,0,0,0,0,63,255,240,7,255,255,128,0,0,0,1,255,255,129,255,255,255,128,0,0,0,15,255,252,63,255,255,255,0,0,0,0,127,255,231,255,255,255,254,0,0,0,3,255,255,127,255,255,255,252,0,0,0,31,255,255,255,255,255,255,240,0,0,0,255,255,255,255,255,255,255,192,0,0,7,255,255,255,255,255,255,255,0,0,0,63,255,255,255,255,255,255,252,0,0,3,255,255,255,255,255,255,255,240,0,0,31,255,255,255,255,255,255,255,192,0,0,255,255,255,255,255,255,255,254,0,0,7,255,255,255,255,255,255,255,248,0,0,63,255,255,255,255,255,255,255,192,0,1,255,255,255,255,255,255,255,255,0,0,15,255,255,254,0,63,255,255,248,0,0,127,255,255,128,0,63,255,255,224,0,3,255,255,248,0,0,255,255,255,0,0,31,255,255,0,0,3,255,255,252,0,0,255,255,240,0,0,15,255,255,224,0,1,255,255,0,0,0,63,255,255,0,0,0,15,240,0,0,0,255,255,248,0,0,0,0,0,0,0,7,255,255,224,0,0,0,0,0,0,0,31,255,255,0,0,0,0,0,0,0,0,255,255,248,0,0,0,0,0,0,0,7,255,255,192,0,0,0,0,0,0,0,63,255,254,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,7,255,255,128,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,1,255,255,224,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,0,127,255,248,0,63,255,252,0,0,0,3,255,255,192,1,255,255,224,0,0,0,31,255,254,0,15,255,255,0,0,0,0,255,255,240,0,127,255,248,0,0,0,7,255,255,128,3,255,255,192,0,0,0,127,255,252,0,31,255,255,0,0,0,3,255,255,224,0,255,255,248,0,0,0,31,255,255,0,3,255,255,192,0,0,0,255,255,248,0,31,255,255,0,0,0,15,255,255,128,0,255,255,248,0,0,0,127,255,252,0,7,255,255,224,0,0,7,255,255,224,0,31,255,255,0,0,0,63,255,254,0,0,255,255,252,0,0,3,255,255,240,0,7,255,255,240,0,0,63,255,255,128,0,31,255,255,192,0,3,255,255,248,0,0,255,255,255,128,0,127,255,255,192,0,3,255,255,255,0,15,255,255,252,0,0,31,255,255,255,255,255,255,255,224,0,0,127,255,255,255,255,255,255,254,0,0,1,255,255,255,255,255,255,255,224,0,0,15,255,255,255,255,255,255,255,0,0,0,63,255,255,255,255,255,255,240,0,0,0,255,255,255,255,255,255,255,0,0,0,3,255,255,255,255,255,255,240,0,0,0,15,255,255,255,255,255,255,0,0,0,0,31,255,255,255,255,255,240,0,0,0,0,127,255,255,255,255,255,0,0,0,0,1,255,255,255,255,255,224,0,0,0,0,3,255,255,255,255,252,0,0,0,0,0,7,255,255,255,255,128,0,0,0,0,0,7,255,255,255,240,0,0,0,0,0,0,7,255,255,252,0,0,0,0,0,0,0,1,255,252,0,0,0,0,0,0,0,0,0,7,255,224,0,0,0,0,0,0,0,3,255,255,248,0,0,0,0,0,0,0,127,255,255,252,0,0,0,0,0,0,15,255,255,255,254,0,0,0,0,0,0,255,255,255,255,254,0,0,0,0,0,7,255,255,255,255,252,0,0,0,0,0,63,255,255,255,255,248,0,0,0,0,3,255,255,255,255,255,248,0,0,0,0,31,255,255,255,255,255,240,0,0,0,0,255,255,255,255,255,255,224,0,0,0,3,255,255,255,255,255,255,192,0,0,0,31,255,255,255,255,255,255,0,0,0,0,255,255,255,255,255,255,254,0,0,0,7,255,255,255,255,255,255,252,0,0,0,31,255,255,255,255,255,255,240,0,0,0,255,255,255,255,255,255,255,224,0,0,3,255,255,252,0,127,255,255,128,0,0,31,255,255,128,0,127,255,255,0,0,0,127,255,252,0,0,127,255,252,0,0,3,255,255,224,0,0,255,255,240,0,0,15,255,255,0,0,3,255,255,224,0,0,127,255,252,0,0,7,255,255,128,0,1,255,255,224,0,0,31,255,254,0,0,7,255,255,0,0,0,63,255,248,0,0,31,255,252,0,0,0,255,255,224,0,0,255,255,240,0,0,3,255,255,192,0,3,255,255,128,0,0,7,255,255,0,0,15,255,254,0,0,0,31,255,252,0,0,127,255,248,0,0,0,127,255,240,0,1,255,255,224,0,0,1,255,255,192,0,7,255,255,128,0,0,7,255,255,0,0,31,255,252,0,0,0,0,0,0,0,0,127,255,240,0,0,0,0,0,0,0,3,255,255,192,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,3,255,255,192,0,127,248,0,0,0,0,15,255,255,0,15,255,255,0,0,0,0,63,255,252,1,255,255,255,128,0,0,0,255,255,240,31,255,255,255,128,0,0,3,255,255,193,255,255,255,255,128,0,0,31,255,255,15,255,255,255,255,0,0,0,127,255,248,255,255,255,255,254,0,0,1,255,255,231,255,255,255,255,252,0,0,7,255,255,255,255,255,255,255,252,0,0,31,255,255,255,255,255,255,255,248,0,0,127,255,255,255,255,255,255,255,224,0,1,255,255,255,255,255,255,255,255,192,0,7,255,255,255,255,255,255,255,255,128,0,31,255,255,255,255,255,255,255,254,0,0,127,255,255,255,255,255,255,255,252,0,1,255,255,255,255,255,255,255,255,240,0,7,255,255,255,240,0,255,255,255,224,0,31,255,255,255,0,0,255,255,255,128,0,127,255,255,240,0,0,255,255,255,0,1,255,255,255,128,0,1,255,255,252,0,7,255,255,252,0,0,3,255,255,240,0,31,255,255,240,0,0,15,255,255,224,0,127,255,255,128,0,0,31,255,255,128,1,255,255,252,0,0,0,63,255,254,0,7,255,255,240,0,0,0,255,255,248,0,31,255,255,192,0,0,3,255,255,224,0,127,255,254,0,0,0,7,255,255,192,1,255,255,248,0,0,0,31,255,255,0,7,255,255,224,0,0,0,127,255,252,0,15,255,255,128,0,0,1,255,255,240,0,63,255,254,0,0,0,7,255,255,192,0,255,255,248,0,0,0,31,255,255,0,3,255,255,192,0,0,0,63,255,252,0,15,255,255,0,0,0,0,255,255,240,0,63,255,254,0,0,0,7,255,255,192,0,255,255,248,0,0,0,31,255,255,0,1,255,255,224,0,0,0,127,255,252,0,7,255,255,128,0,0,1,255,255,240,0,31,255,254,0,0,0,7,255,255,192,0,127,255,248,0,0,0,31,255,254,0,0,255,255,240,0,0,0,255,255,248,0,3,255,255,192,0,0,3,255,255,224,0,15,255,255,0,0,0,15,255,255,128,0,63,255,254,0,0,0,127,255,252,0,0,127,255,248,0,0,3,255,255,240,0,1,255,255,240,0,0,15,255,255,192,0,3,255,255,224,0,0,127,255,254,0,0,15,255,255,192,0,3,255,255,248,0,0,31,255,255,192,0,63,255,255,224,0,0,127,255,255,192,3,255,255,255,0,0,0,255,255,255,255,255,255,255,248,0,0,3,255,255,255,255,255,255,255,224,0,0,7,255,255,255,255,255,255,255,0,0,0,15,255,255,255,255,255,255,248,0,0,0,63,255,255,255,255,255,255,192,0,0,0,127,255,255,255,255,255,255,0,0,0,0,255,255,255,255,255,255,248,0,0,0,1,255,255,255,255,255,255,192,0,0,0,3,255,255,255,255,255,252,0,0,0,0,3,255,255,255,255,255,224,0,0,0,0,7,255,255,255,255,255,0,0,0,0,0,7,255,255,255,255,240,0,0,0,0,0,7,255,255,255,255,0,0,0,0,0,0,7,255,255,255,240,0,0,0,0,0,0,3,255,255,252,0,0,0,0,0,0,0,0,127,254,0,0,0,0,0,1,255,255,255,255,255,255,255,255,255,0,7,255,255,255,255,255,255,255,255,252,0,31,255,255,255,255,255,255,255,255,240,0,127,255,255,255,255,255,255,255,255,192,1,255,255,255,255,255,255,255,255,255,0,7,255,255,255,255,255,255,255,255,252,0,31,255,255,255,255,255,255,255,255,240,0,127,255,255,255,255,255,255,255,255,192,1,255,255,255,255,255,255,255,255,255,0,7,255,255,255,255,255,255,255,255,252,0,31,255,255,255,255,255,255,255,255,240,0,127,255,255,255,255,255,255,255,255,192,1,255,255,255,255,255,255,255,255,255,0,7,255,255,255,255,255,255,255,255,248,0,31,255,255,255,255,255,255,255,255,192,0,127,255,255,255,255,255,255,255,254,0,1,255,255,255,255,255,255,255,255,240,0,7,255,255,255,255,255,255,255,255,192,0,0,0,0,0,0,0,15,255,254,0,0,0,0,0,0,0,0,127,255,240,0,0,0,0,0,0,0,3,255,255,128,0,0,0,0,0,0,0,31,255,254,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,3,255,255,128,0,0,0,0,0,0,0,31,255,252,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,7,255,255,128,0,0,0,0,0,0,0,31,255,252,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,7,255,255,128,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,7,255,255,128,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,7,255,255,128,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,7,255,255,128,0,0,0,0,0,0,0,31,255,254,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,7,255,255,128,0,0,0,0,0,0,0,31,255,254,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,3,255,255,192,0,0,0,0,0,0,0,31,255,254,0,0,0,0,0,0,0,0,127,255,248,0,0,0,0,0,0,0,3,255,255,192,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,0,127,255,248,0,0,0,0,0,0,0,1,255,255,224,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,1,255,255,224,0,0,0,0,0,0,0,7,255,255,128,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,7,255,255,192,0,0,0,0,0,0,0,31,255,254,0,0,0,0,0,0,0,0,255,255,248,0,0,0,0,0,0,0,3,255,255,192,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,0,127,255,252,0,0,0,0,0,0,0,1,255,255,224,0,0,0,0,0,0,0,15,255,255,128,0,0,0,0,0,0,0,63,255,254,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,7,255,255,192,0,0,0,0,0,0,0,31,255,255,0,0,0,0,0,0,0,0,127,255,248,0,0,0,0,0,0,0,3,255,255,224,0,0,0,0,0,0,0,15,255,255,128,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,7,255,255,192,0,0,0,0,0,0,0,31,255,255,0,0,0,0,0,0,0,0,127,255,252,0,0,0,0,0,0,0,1,255,255,224,0,0,0,0,0,0,0,15,255,255,128,0,0,0,0,0,0,0,63,255,254,0,0,0,0,0,0,0,0,255,255,248,0,0,0,0,0,0,0,3,255,255,224,0,0,0,0,0,0,0,15,255,255,128,0,0,0,0,0,0,0,127,255,252,0,0,0,0,0,0,0,1,255,255,240,0,0,0,0,0,0,0,7,255,255,192,0,0,0,0,0,0,0,31,255,255,0,0,0,0,0,0,0,0,127,255,252,0,0,0,0,0,0,0,1,255,255,240,0,0,0,0,0,0,0,7,255,255,192,0,0,0,0,0,0,0,63,255,255,0,0,0,0,0,0,0,0,255,255,252,0,0,0,0,0,0,0,3,255,255,240,0,0,0,0,0,0,0,15,255,255,192,0,0,0,0,0,0,0,63,255,255,0,0,0,0,0,0,0,0,255,255,252,0,0,0,0,0,0,0,3,255,255,240,0,0,0,0,0,0,0,15,255,255,192,0,0,0,0,0,0,0,63,255,255,0,0,0,0,0,0,0,0,127,255,252,0,0,0,0,0,0,0,0,0,31,255,128,0,0,0,0,0,0,0,15,255,255,248,0,0,0,0,0,0,1,255,255,255,252,0,0,0,0,0,0,31,255,255,255,252,0,0,0,0,0,1,255,255,255,255,252,0,0,0,0,0,31,255,255,255,255,252,0,0,0,0,1,255,255,255,255,255,248,0,0,0,0,15,255,255,255,255,255,248,0,0,0,0,127,255,255,255,255,255,240,0,0,0,3,255,255,255,255,255,255,224,0,0,0,31,255,255,255,255,255,255,192,0,0,0,255,255,255,255,255,255,255,128,0,0,3,255,255,255,255,255,255,254,0,0,0,31,255,255,255,255,255,255,252,0,0,0,255,255,255,255,255,255,255,248,0,0,3,255,255,255,255,255,255,255,224,0,0,31,255,255,240,0,127,255,255,192,0,0,127,255,255,0,0,127,255,255,0,0,1,255,255,248,0,0,255,255,252,0,0,15,255,255,128,0,1,255,255,248,0,0,63,255,254,0,0,3,255,255,224,0,0,255,255,240,0,0,7,255,255,128,0,3,255,255,128,0,0,15,255,254,0,0,31,255,254,0,0,0,63,255,252,0,0,127,255,248,0,0,0,255,255,240,0,1,255,255,192,0,0,1,255,255,192,0,7,255,255,0,0,0,7,255,255,0,0,31,255,252,0,0,0,31,255,252,0,0,127,255,240,0,0,0,127,255,240,0,1,255,255,192,0,0,1,255,255,192,0,7,255,255,0,0,0,7,255,255,0,0,31,255,252,0,0,0,31,255,252,0,0,127,255,240,0,0,0,127,255,240,0,0,255,255,224,0,0,3,255,255,128,0,3,255,255,128,0,0,15,255,254,0,0,15,255,254,0,0,0,63,255,248,0,0,63,255,252,0,0,1,255,255,224,0,0,127,255,240,0,0,15,255,255,0,0,1,255,255,224,0,0,127,255,252,0,0,3,255,255,192,0,3,255,255,224,0,0,15,255,255,192,0,31,255,255,128,0,0,31,255,255,192,1,255,255,252,0,0,0,127,255,255,255,255,255,255,240,0,0,0,255,255,255,255,255,255,255,128,0,0,1,255,255,255,255,255,255,252,0,0,0,3,255,255,255,255,255,255,224,0,0,0,7,255,255,255,255,255,255,0,0,0,0,15,255,255,255,255,255,248,0,0,0,0,15,255,255,255,255,255,192,0,0,0,0,31,255,255,255,255,252,0,0,0,0,0,127,255,255,255,255,240,0,0,0,0,7,255,255,255,255,255,240,0,0,0,0,63,255,255,255,255,255,240,0,0,0,3,255,255,255,255,255,255,224,0,0,0,31,255,255,255,255,255,255,224,0,0,0,255,255,255,255,255,255,255,192,0,0,15,255,255,255,255,255,255,255,128,0,0,63,255,255,255,255,255,255,255,0,0,1,255,255,255,192,31,255,255,254,0,0,15,255,255,248,0,7,255,255,248,0,0,127,255,255,128,0,7,255,255,240,0,1,255,255,248,0,0,15,255,255,224,0,15,255,255,192,0,0,31,255,255,128,0,63,255,254,0,0,0,63,255,255,0,0,255,255,248,0,0,0,127,255,252,0,7,255,255,192,0,0,1,255,255,240,0,31,255,255,0,0,0,3,255,255,224,0,127,255,248,0,0,0,15,255,255,128,1,255,255,224,0,0,0,31,255,254,0,15,255,255,128,0,0,0,127,255,248,0,63,255,254,0,0,0,1,255,255,224,0,255,255,248,0,0,0,7,255,255,128,3,255,255,192,0,0,0,31,255,254,0,15,255,255,0,0,0,0,127,255,248,0,63,255,254,0,0,0,1,255,255,224,0,255,255,248,0,0,0,7,255,255,128,3,255,255,224,0,0,0,31,255,254,0,15,255,255,128,0,0,0,255,255,248,0,63,255,254,0,0,0,3,255,255,224,0,127,255,252,0,0,0,15,255,255,128,1,255,255,240,0,0,0,127,255,254,0,7,255,255,224,0,0,1,255,255,248,0,31,255,255,192,0,0,15,255,255,192,0,63,255,255,128,0,0,127,255,255,0,0,255,255,255,0,0,7,255,255,252,0,3,255,255,255,0,0,63,255,255,224,0,7,255,255,255,0,3,255,255,255,128,0,31,255,255,255,255,255,255,255,252,0,0,63,255,255,255,255,255,255,255,240,0,0,255,255,255,255,255,255,255,255,128,0,1,255,255,255,255,255,255,255,252,0,0,3,255,255,255,255,255,255,255,240,0,0,7,255,255,255,255,255,255,255,128,0,0,15,255,255,255,255,255,255,252,0,0,0,31,255,255,255,255,255,255,224,0,0,0,63,255,255,255,255,255,254,0,0,0,0,127,255,255,255,255,255,240,0,0,0,0,127,255,255,255,255,255,128,0,0,0,0,127,255,255,255,255,248,0,0,0,0,0,127,255,255,255,255,128,0,0,0,0,0,127,255,255,255,240,0,0,0,0,0,0,31,255,255,254,0,0,0,0,0,0,0,3,255,254,0,0,0,0,0,0,0,0,0,31,255,0,0,0,0,0,0,0,0,15,255,255,240,0,0,0,0,0,0,3,255,255,255,248,0,0,0,0,0,0,63,255,255,255,248,0,0,0,0,0,3,255,255,255,255,248,0,0,0,0,0,63,255,255,255,255,248,0,0,0,0,1,255,255,255,255,255,248,0,0,0,0,15,255,255,255,255,255,240,0,0,0,0,255,255,255,255,255,255,224,0,0,0,7,255,255,255,255,255,255,192,0,0,0,63,255,255,255,255,255,255,128,0,0,1,255,255,255,255,255,255,255,0,0,0,7,255,255,255,255,255,255,252,0,0,0,63,255,255,255,255,255,255,248,0,0,1,255,255,255,255,255,255,255,240,0,0,15,255,255,255,255,255,255,255,192,0,0,63,255,255,240,0,255,255,255,128,0,1,255,255,255,0,0,255,255,254,0,0,7,255,255,248,0,0,255,255,252,0,0,63,255,255,128,0,1,255,255,240,0,0,255,255,252,0,0,3,255,255,224,0,3,255,255,240,0,0,7,255,255,128,0,31,255,255,128,0,0,31,255,254,0,0,127,255,254,0,0,0,63,255,252,0,1,255,255,240,0,0,0,255,255,240,0,7,255,255,192,0,0,1,255,255,192,0,31,255,254,0,0,0,7,255,255,128,0,255,255,248,0,0,0,31,255,254,0,3,255,255,224,0,0,0,127,255,248,0,15,255,255,128,0,0,1,255,255,224,0,63,255,254,0,0,0,3,255,255,192,0,255,255,248,0,0,0,15,255,255,0,3,255,255,192,0,0,0,63,255,252,0,15,255,255,0,0,0,0,255,255,240,0,63,255,254,0,0,0,3,255,255,192,0,255,255,248,0,0,0,15,255,255,0,3,255,255,224,0,0,0,127,255,252,0,15,255,255,128,0,0,1,255,255,240,0,63,255,254,0,0,0,7,255,255,224,0,255,255,248,0,0,0,31,255,255,128,1,255,255,224,0,0,0,127,255,254,0,7,255,255,192,0,0,3,255,255,248,0,31,255,255,0,0,0,15,255,255,224,0,127,255,254,0,0,0,127,255,255,128,1,255,255,248,0,0,1,255,255,254,0,3,255,255,240,0,0,15,255,255,248,0,15,255,255,224,0,0,127,255,255,224,0,63,255,255,192,0,3,255,255,255,128,0,127,255,255,128,0,31,255,255,254,0,1,255,255,255,128,1,255,255,255,248,0,3,255,255,255,192,63,255,255,255,224,0,15,255,255,255,255,255,255,255,255,128,0,31,255,255,255,255,255,255,255,254,0,0,127,255,255,255,255,255,255,255,248,0,0,255,255,255,255,255,255,255,255,224,0,1,255,255,255,255,255,255,255,255,128,0,3,255,255,255,255,255,255,255,254,0,0,15,255,255,255,255,255,255,255,248,0,0,15,255,255,255,255,249,255,255,224,0,0,31,255,255,255,255,199,255,255,128,0,0,63,255,255,255,254,63,255,254,0,0,0,127,255,255,255,224,255,255,240,0,0,0,127,255,255,255,3,255,255,192,0,0,0,127,255,255,224,15,255,255,0,0,0,0,63,255,254,0,63,255,252,0,0,0,0,15,255,128,0,255,255,240,0,0,0,0,0,0,0,3,255,255,192,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,0,63,255,252,0,0,0,0,0,0,0,0,255,255,224,0,0,0,0,0,0,0,3,255,255,128,0,0,0,0,0,0,0,15,255,254,0,0,127,255,248,0,0,0,127,255,248,0,1,255,255,224,0,0,1,255,255,224,0,7,255,255,128,0,0,7,255,255,0,0,31,255,254,0,0,0,31,255,252,0,0,127,255,248,0,0,0,127,255,240,0,1,255,255,240,0,0,3,255,255,192,0,3,255,255,192,0,0,15,255,254,0,0,15,255,255,0,0,0,63,255,248,0,0,63,255,252,0,0,1,255,255,224,0,0,255,255,248,0,0,7,255,255,0,0,3,255,255,240,0,0,63,255,252,0,0,7,255,255,192,0,1,255,255,240,0,0,31,255,255,128,0,15,255,255,128,0,0,63,255,255,128,0,127,255,254,0,0,0,255,255,255,128,7,255,255,240,0,0,3,255,255,255,255,255,255,255,192,0,0,7,255,255,255,255,255,255,254,0,0,0,15,255,255,255,255,255,255,240,0,0,0,63,255,255,255,255,255,255,192,0,0,0,127,255,255,255,255,255,254,0,0,0,0,255,255,255,255,255,255,240,0,0,0,3,255,255,255,255,255,255,128,0,0,0,7,255,255,255,255,255,252,0,0,0,0,7,255,255,255,255,255,224,0,0,0,0,15,255,255,255,255,255,0,0,0,0,0,31,255,255,255,255,248,0,0,0,0,0,31,255,255,255,255,128,0,0,0,0,0,31,255,255,255,248,0,0,0,0,0,0,31,255,255,255,128,0,0,0,0,0,0,15,255,255,240,0,0,0,0,0,0,0,1,255,248,0,0,0,0,0,0,0,0,3,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,128,0,0,0,0,0,63,255,0,0,0,0,0,0,127,255,255,254,0,0,0,0,0,7,255,224,0,0,0,0,0,15,255,255,255,240,0,0,0,0,0,127,252,0,0,0,0,0,1,255,255,255,255,128,0,0,0,0,15,255,192,0,0,0,0,0,63,255,255,255,252,0,0,0,0,1,255,248,0,0,0,0,0,7,255,255,255,255,224,0,0,0,0,31,255,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,3,255,240,0,0,0,0,0,15,255,255,255,255,240,0,0,0,0,127,254,0,0,0,0,0,1,255,255,255,255,255,128,0,0,0,7,255,192,0,0,0,0,0,31,255,248,31,255,248,0,0,0,0,255,252,0,0,0,0,0,3,255,254,0,127,255,192,0,0,0,31,255,128,0,0,0,0,0,63,255,192,3,255,252,0,0,0,3,255,240,0,0,0,0,0,7,255,248,0,31,255,224,0,0,0,63,254,0,0,0,0,0,0,127,255,128,1,255,254,0,0,0,7,255,224,0,0,0,0,0,7,255,240,0,15,255,224,0,0,0,255,252,0,0,0,0,0,0,127,255,0,0,255,254,0,0,0,15,255,128,0,0,0,0,0,7,255,240,0,15,255,224,0,0,1,255,248,0,0,0,0,0,0,255,254,0,0,127,255,0,0,0,63,255,0,0,0,0,0,0,15,255,224,0,7,255,240,0,0,3,255,224,0,0,0,0,0,0,255,254,0,0,127,255,0,0,0,127,252,0,0,0,0,0,0,15,255,224,0,7,255,240,0,0,15,255,192,0,0,0,0,0,0,255,254,0,0,127,255,0,0,1,255,248,0,0,0,0,0,0,15,255,224,0,7,255,240,0,0,31,255,0,0,0,0,0,0,0,255,254,0,0,127,255,0,0,3,255,240,0,0,0,0,0,0,15,255,224,0,7,255,240,0,0,127,254,0,0,0,0,0,0,0,255,254,0,0,127,255,0,0,7,255,192,0,0,0,0,0,0,15,255,224,0,7,255,240,0,0,255,252,0,0,0,0,0,0,0,255,254,0,0,127,255,0,0,31,255,128,0,0,0,0,0,0,15,255,224,0,7,255,240,0,3,255,240,0,0,0,0,0,0,0,255,254,0,0,127,255,0,0,63,254,0,0,0,0,0,0,0,15,255,224,0,7,255,240,0,7,255,224,0,0,0,0,0,0,0,127,255,0,0,255,254,0,0,255,252,0,0,0,0,0,0,0,7,255,240,0,15,255,224,0,15,255,128,0,0,0,0,0,0,0,127,255,0,0,255,254,0,1,255,248,0,0,0,0,0,0,0,7,255,248,0,31,255,224,0,63,255,0,0,0,0,0,0,0,0,127,255,128,1,255,254,0,3,255,224,0,0,0,0,0,0,0,3,255,252,0,63,255,192,0,127,254,0,0,0,0,0,0,0,0,63,255,224,7,255,252,0,15,255,192,0,0,0,0,0,0,0,1,255,255,129,255,255,128,1,255,248,0,0,0,0,0,0,0,0,31,255,255,255,255,248,0,31,255,0,0,0,0,0,0,0,0,0,255,255,255,255,255,0,3,255,240,0,0,0,0,0,0,0,0,15,255,255,255,255,240,0,127,254,0,0,0,0,0,0,0,0,0,127,255,255,255,254,0,7,255,192,0,0,0,0,0,0,0,0,3,255,255,255,255,192,0,255,252,0,0,0,0,0,0,0,0,0,31,255,255,255,248,0,31,255,128,0,0,0,0,0,0,0,0,0,255,255,255,255,0,1,255,240,0,0,0,0,0,0,0,0,0,7,255,255,255,224,0,63,254,0,0,0,0,0,0,0,0,0,0,31,255,255,248,0,7,255,224,0,0,0,0,0,0,0,0,0,0,63,255,252,0,0,255,252,0,0,0,0,0,0,0,0,0,0,0,63,252,0,0,15,255,128,0,1,255,224,0,0,0,0,0,0,0,0,0,0,1,255,248,0,1,255,255,224,0,0,0,0,0,0,0,0,0,0,63,255,0,0,255,255,255,192,0,0,0,0,0,0,0,0,0,3,255,224,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,127,254,0,7,255,255,255,248,0,0,0,0,0,0,0,0,0,15,255,192,0,255,255,255,255,192,0,0,0,0,0,0,0,0,0,255,248,0,31,255,255,255,254,0,0,0,0,0,0,0,0,0,31,255,0,3,255,255,255,255,240,0,0,0,0,0,0,0,0,3,255,240,0,127,255,255,255,255,128,0,0,0,0,0,0,0,0,127,254,0,7,255,255,255,255,252,0,0,0,0,0,0,0,0,7,255,192,0,255,255,255,255,255,192,0,0,0,0,0,0,0,0,255,252,0,15,255,252,15,255,254,0,0,0,0,0,0,0,0,31,255,128,1,255,255,0,63,255,224,0,0,0,0,0,0,0,1,255,240,0,31,255,224,1,255,254,0,0,0,0,0,0,0,0,63,255,0,3,255,252,0,15,255,240,0,0,0,0,0,0,0,7,255,224,0,63,255,192,0,255,255,0,0,0,0,0,0,0,0,127,252,0,3,255,248,0,7,255,240,0,0,0,0,0,0,0,15,255,128,0,63,255,128,0,127,255,0,0,0,0,0,0,0,1,255,248,0,3,255,248,0,7,255,240,0,0,0,0,0,0,0,63,255,0,0,127,255,0,0,63,255,128,0,0,0,0,0,0,3,255,224,0,7,255,240,0,3,255,248,0,0,0,0,0,0,0,127,254,0,0,127,255,0,0,63,255,128,0,0,0,0,0,0,15,255,192,0,7,255,240,0,3,255,248,0,0,0,0,0,0,0,255,248,0,0,127,255,0,0,63,255,128,0,0,0,0,0,0,31,255,0,0,7,255,240,0,3,255,248,0,0,0,0,0,0,3,255,240,0,0,127,255,0,0,63,255,128,0,0,0,0,0,0,63,254,0,0,7,255,240,0,3,255,248,0,0,0,0,0,0,7,255,192,0,0,127,255,0,0,63,255,128,0,0,0,0,0,0,255,252,0,0,7,255,240,0,3,255,248,0,0,0,0,0,0,31,255,128,0,0,127,255,0,0,63,255,128,0,0,0,0,0,1,255,240,0,0,7,255,240,0,3,255,248,0,0,0,0,0,0,63,255,0,0,0,127,255,0,0,63,255,128,0,0,0,0,0,7,255,224,0,0,7,255,240,0,3,255,248,0,0,0,0,0,0,127,252,0,0,0,63,255,128,0,127,255,0,0,0,0,0,0,15,255,128,0,0,3,255,248,0,7,255,240,0,0,0,0,0,1,255,248,0,0,0,63,255,128,0,127,255,0,0,0,0,0,0,31,255,0,0,0,3,255,252,0,15,255,240,0,0,0,0,0,3,255,224,0,0,0,63,255,192,0,255,255,0,0,0,0,0,0,127,254,0,0,0,1,255,254,0,31,255,224,0,0,0,0,0,15,255,192,0,0,0,31,255,240,3,255,254,0,0,0,0,0,0,255,248,0,0,0,0,255,255,192,255,255,224,0,0,0,0,0,31,255,128,0,0,0,15,255,255,255,255,252,0,0,0,0,0,3,255,240,0,0,0,0,127,255,255,255,255,192,0,0,0,0,0,63,254,0,0,0,0,7,255,255,255,255,248,0,0,0,0,0,7,255,192,0,0,0,0,63,255,255,255,255,0,0,0,0,0,0,255,252,0,0,0,0,1,255,255,255,255,224,0,0,0,0,0,15,255,128,0,0,0,0,15,255,255,255,252,0,0,0,0,0,1,255,240,0,0,0,0,0,127,255,255,255,128,0,0,0,0,0,63,255,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,7,255,224,0,0,0,0,0,15,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,254,0,0,0,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,3,255,255,255,255,192,3,255,255,255,255,192,3,255,255,255,255,192,3,255,255,255,255,192,3,255,255,255,255,192,3,255,255,255,255,192,3,255,255,255,255,192,3,255,255,255,255,192,3,255,255,255,255,192,3,255,255,255,255,192,3,255,255,255,255,192,3,255,255,255,255,192,3,255,255,255,255,192,3,255,255,255,255,192,3,255,255,255,255,192,3,255,255,255,255,192,3,255,255,255,255,192,3,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,0,0,0,255,255,240,0,0,15,255,255,0,0,0,255,255,240,0,0,15,255,255,0,0,0,255,255,240,0,0,15,255,255,0,0,0,255,255,240,0,0,15,255,255,0,0,0,255,255,240,0,0,15,255,255,0,0,0,255,255,240,0,0,15,255,255,0,0,0,255,255,240,0,0,15,255,255,0,0,0,255,255,240,0,0,15,255,255,0,0,0,255,255,240,0,0,15,255,255,0,0,0,255,255,240,0,0,15,255,255,0,0}; +static const Glyph f0_144_glyphs[]={{0,86,103,0,0,86,48},{1108,86,100,0,1,86,49},{2183,86,101,0,0,86,50},{3269,86,103,0,0,86,51},{4377,86,101,0,0,86,52},{5463,85,101,0,2,85,53},{6537,86,103,0,0,86,54},{7645,86,100,0,2,86,55},{8720,86,103,0,0,86,56},{9828,86,103,0,0,86,57},{10936,140,103,0,0,140,37},{12739,48,76,0,25,48,58},{13195,48,48,0,53,48,45},{13483,44,21,0,80,44,46},{13599,28,0,0,101,28,32}}; +static const uint8_t f0_212_bits[]={0,0,0,0,0,0,0,31,240,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,248,0,0,0,0,0,0,0,0,0,0,15,255,255,255,255,252,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,254,0,0,0,0,0,0,0,0,0,31,255,255,255,255,255,254,0,0,0,0,0,0,0,0,1,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,15,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,127,255,255,255,255,255,255,255,248,0,0,0,0,0,0,3,255,255,255,255,255,255,255,255,240,0,0,0,0,0,0,31,255,255,255,255,255,255,255,255,224,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,192,0,0,0,0,0,7,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,7,255,255,255,255,255,255,255,255,255,248,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,240,0,0,0,0,1,255,255,255,255,255,255,255,255,255,255,224,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,7,255,255,255,255,240,3,255,255,255,255,248,0,0,0,0,31,255,255,255,252,0,0,255,255,255,255,224,0,0,0,0,255,255,255,255,192,0,0,255,255,255,255,192,0,0,0,3,255,255,255,254,0,0,1,255,255,255,255,0,0,0,0,31,255,255,255,240,0,0,1,255,255,255,254,0,0,0,0,127,255,255,255,128,0,0,3,255,255,255,248,0,0,0,1,255,255,255,252,0,0,0,15,255,255,255,224,0,0,0,15,255,255,255,224,0,0,0,31,255,255,255,192,0,0,0,63,255,255,255,0,0,0,0,63,255,255,255,0,0,0,1,255,255,255,252,0,0,0,0,255,255,255,254,0,0,0,7,255,255,255,224,0,0,0,1,255,255,255,248,0,0,0,31,255,255,255,128,0,0,0,7,255,255,255,224,0,0,0,127,255,255,252,0,0,0,0,15,255,255,255,128,0,0,3,255,255,255,240,0,0,0,0,63,255,255,255,0,0,0,15,255,255,255,192,0,0,0,0,255,255,255,252,0,0,0,63,255,255,254,0,0,0,0,1,255,255,255,240,0,0,1,255,255,255,248,0,0,0,0,7,255,255,255,224,0,0,7,255,255,255,224,0,0,0,0,31,255,255,255,128,0,0,31,255,255,255,0,0,0,0,0,63,255,255,254,0,0,0,127,255,255,252,0,0,0,0,0,255,255,255,248,0,0,1,255,255,255,240,0,0,0,0,3,255,255,255,224,0,0,15,255,255,255,192,0,0,0,0,15,255,255,255,192,0,0,63,255,255,255,0,0,0,0,0,63,255,255,255,0,0,0,255,255,255,248,0,0,0,0,0,255,255,255,252,0,0,3,255,255,255,224,0,0,0,0,1,255,255,255,240,0,0,15,255,255,255,128,0,0,0,0,7,255,255,255,192,0,0,63,255,255,254,0,0,0,0,0,31,255,255,255,0,0,1,255,255,255,248,0,0,0,0,0,127,255,255,254,0,0,7,255,255,255,224,0,0,0,0,1,255,255,255,248,0,0,31,255,255,255,128,0,0,0,0,7,255,255,255,224,0,0,127,255,255,254,0,0,0,0,0,31,255,255,255,128,0,1,255,255,255,248,0,0,0,0,0,127,255,255,254,0,0,7,255,255,255,224,0,0,0,0,1,255,255,255,248,0,0,31,255,255,255,0,0,0,0,0,7,255,255,255,224,0,0,127,255,255,252,0,0,0,0,0,15,255,255,255,128,0,1,255,255,255,240,0,0,0,0,0,63,255,255,254,0,0,7,255,255,255,192,0,0,0,0,0,255,255,255,248,0,0,63,255,255,255,0,0,0,0,0,3,255,255,255,240,0,0,255,255,255,252,0,0,0,0,0,15,255,255,255,192,0,3,255,255,255,240,0,0,0,0,0,63,255,255,255,0,0,15,255,255,255,192,0,0,0,0,0,255,255,255,252,0,0,63,255,255,255,0,0,0,0,0,3,255,255,255,240,0,0,255,255,255,252,0,0,0,0,0,15,255,255,255,192,0,3,255,255,255,240,0,0,0,0,0,63,255,255,255,0,0,15,255,255,255,192,0,0,0,0,0,255,255,255,252,0,0,63,255,255,255,0,0,0,0,0,3,255,255,255,240,0,0,255,255,255,252,0,0,0,0,0,15,255,255,255,192,0,3,255,255,255,240,0,0,0,0,0,63,255,255,255,0,0,15,255,255,255,192,0,0,0,0,0,255,255,255,252,0,0,63,255,255,255,0,0,0,0,0,3,255,255,255,240,0,0,255,255,255,252,0,0,0,0,0,15,255,255,255,192,0,3,255,255,255,240,0,0,0,0,0,63,255,255,255,0,0,15,255,255,255,192,0,0,0,0,0,255,255,255,252,0,0,63,255,255,255,0,0,0,0,0,3,255,255,255,240,0,0,255,255,255,252,0,0,0,0,0,15,255,255,255,192,0,3,255,255,255,240,0,0,0,0,0,63,255,255,255,0,0,15,255,255,255,192,0,0,0,0,0,255,255,255,252,0,0,63,255,255,255,0,0,0,0,0,3,255,255,255,240,0,0,255,255,255,252,0,0,0,0,0,15,255,255,255,192,0,3,255,255,255,240,0,0,0,0,0,63,255,255,255,0,0,15,255,255,255,192,0,0,0,0,0,255,255,255,252,0,0,63,255,255,255,0,0,0,0,0,3,255,255,255,240,0,0,255,255,255,252,0,0,0,0,0,15,255,255,255,192,0,3,255,255,255,240,0,0,0,0,0,63,255,255,255,0,0,15,255,255,255,192,0,0,0,0,0,255,255,255,252,0,0,63,255,255,255,0,0,0,0,0,3,255,255,255,240,0,0,255,255,255,252,0,0,0,0,0,15,255,255,255,192,0,3,255,255,255,240,0,0,0,0,0,63,255,255,255,0,0,7,255,255,255,192,0,0,0,0,0,255,255,255,248,0,0,31,255,255,255,0,0,0,0,0,3,255,255,255,224,0,0,127,255,255,252,0,0,0,0,0,15,255,255,255,128,0,1,255,255,255,240,0,0,0,0,0,63,255,255,254,0,0,7,255,255,255,224,0,0,0,0,1,255,255,255,248,0,0,31,255,255,255,128,0,0,0,0,7,255,255,255,224,0,0,127,255,255,254,0,0,0,0,0,31,255,255,255,128,0,1,255,255,255,248,0,0,0,0,0,127,255,255,254,0,0,7,255,255,255,224,0,0,0,0,1,255,255,255,248,0,0,31,255,255,255,128,0,0,0,0,7,255,255,255,224,0,0,63,255,255,254,0,0,0,0,0,31,255,255,255,0,0,0,255,255,255,248,0,0,0,0,0,127,255,255,252,0,0,3,255,255,255,224,0,0,0,0,1,255,255,255,240,0,0,15,255,255,255,128,0,0,0,0,7,255,255,255,192,0,0,63,255,255,255,0,0,0,0,0,63,255,255,255,0,0,0,255,255,255,252,0,0,0,0,0,255,255,255,252,0,0,1,255,255,255,240,0,0,0,0,3,255,255,255,224,0,0,7,255,255,255,192,0,0,0,0,15,255,255,255,128,0,0,31,255,255,255,0,0,0,0,0,63,255,255,254,0,0,0,127,255,255,254,0,0,0,0,1,255,255,255,248,0,0,1,255,255,255,248,0,0,0,0,7,255,255,255,224,0,0,3,255,255,255,224,0,0,0,0,31,255,255,255,0,0,0,15,255,255,255,128,0,0,0,0,127,255,255,252,0,0,0,63,255,255,255,0,0,0,0,3,255,255,255,240,0,0,0,255,255,255,252,0,0,0,0,15,255,255,255,192,0,0,1,255,255,255,240,0,0,0,0,63,255,255,254,0,0,0,7,255,255,255,224,0,0,0,1,255,255,255,248,0,0,0,31,255,255,255,128,0,0,0,7,255,255,255,224,0,0,0,63,255,255,255,0,0,0,0,63,255,255,255,0,0,0,0,255,255,255,254,0,0,0,1,255,255,255,252,0,0,0,3,255,255,255,248,0,0,0,7,255,255,255,240,0,0,0,7,255,255,255,240,0,0,0,63,255,255,255,128,0,0,0,31,255,255,255,224,0,0,1,255,255,255,254,0,0,0,0,63,255,255,255,192,0,0,15,255,255,255,240,0,0,0,0,255,255,255,255,192,0,0,127,255,255,255,192,0,0,0,1,255,255,255,255,192,0,7,255,255,255,254,0,0,0,0,7,255,255,255,255,192,0,255,255,255,255,248,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,192,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,248,0,0,0,0,1,255,255,255,255,255,255,255,255,255,255,224,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,7,255,255,255,255,255,255,255,255,255,248,0,0,0,0,0,31,255,255,255,255,255,255,255,255,255,224,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,127,255,255,255,255,255,255,255,255,248,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,192,0,0,0,0,0,1,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,3,255,255,255,255,255,255,255,255,240,0,0,0,0,0,0,7,255,255,255,255,255,255,255,255,128,0,0,0,0,0,0,15,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,15,255,255,255,255,255,255,255,192,0,0,0,0,0,0,0,31,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,224,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,31,255,255,255,255,255,240,0,0,0,0,0,0,0,0,0,31,255,255,255,255,254,0,0,0,0,0,0,0,0,0,0,15,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,31,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,252,0,0,0,0,0,0,0,0,0,0,0,0,0,7,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,192,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,252,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,192,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,252,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,252,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,240,0,0,0,0,0,0,0,0,0,15,255,255,255,255,255,192,0,0,0,0,0,0,0,0,1,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,127,255,255,255,255,255,252,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,240,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,192,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,7,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,240,0,0,0,0,0,0,0,127,255,255,255,255,255,255,255,192,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,7,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,240,0,0,0,0,0,0,0,127,255,255,255,255,255,255,255,192,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,7,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,240,0,0,0,0,0,0,0,127,255,255,255,255,255,255,255,192,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,7,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,240,0,0,0,0,0,0,0,127,255,255,255,255,255,255,255,192,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,7,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,192,0,0,1,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,252,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,192,0,0,1,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,252,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,192,0,0,1,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,252,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,192,0,0,1,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,252,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,192,0,0,1,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,252,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,192,0,0,1,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,252,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,0,0,0,0,127,255,240,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,128,0,0,0,0,0,0,0,0,0,0,15,255,255,255,255,240,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,252,0,0,0,0,0,0,0,0,0,15,255,255,255,255,255,254,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,7,255,255,255,255,255,255,255,128,0,0,0,0,0,0,0,63,255,255,255,255,255,255,255,192,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,224,0,0,0,0,0,0,7,255,255,255,255,255,255,255,255,224,0,0,0,0,0,0,63,255,255,255,255,255,255,255,255,224,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,224,0,0,0,0,0,3,255,255,255,255,255,255,255,255,255,224,0,0,0,0,0,15,255,255,255,255,255,255,255,255,255,224,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,224,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,224,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,224,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,224,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,224,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,192,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,192,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,63,255,255,255,255,240,1,255,255,255,255,255,128,0,0,0,255,255,255,255,252,0,0,63,255,255,255,255,0,0,0,1,255,255,255,255,224,0,0,31,255,255,255,254,0,0,0,7,255,255,255,255,0,0,0,15,255,255,255,254,0,0,0,15,255,255,255,252,0,0,0,15,255,255,255,252,0,0,0,31,255,255,255,224,0,0,0,15,255,255,255,252,0,0,0,127,255,255,255,128,0,0,0,15,255,255,255,248,0,0,0,255,255,255,254,0,0,0,0,15,255,255,255,240,0,0,1,255,255,255,252,0,0,0,0,15,255,255,255,224,0,0,7,255,255,255,240,0,0,0,0,31,255,255,255,224,0,0,15,255,255,255,192,0,0,0,0,31,255,255,255,192,0,0,31,255,255,255,128,0,0,0,0,63,255,255,255,128,0,0,63,255,255,255,0,0,0,0,0,63,255,255,255,0,0,0,127,255,255,252,0,0,0,0,0,127,255,255,254,0,0,1,255,255,255,248,0,0,0,0,0,255,255,255,254,0,0,3,255,255,255,240,0,0,0,0,0,255,255,255,252,0,0,7,255,255,255,224,0,0,0,0,1,255,255,255,248,0,0,15,255,255,255,128,0,0,0,0,3,255,255,255,240,0,0,31,255,255,255,0,0,0,0,0,7,255,255,255,224,0,0,63,255,255,254,0,0,0,0,0,15,255,255,255,192,0,0,127,255,255,252,0,0,0,0,0,31,255,255,255,128,0,0,255,255,255,248,0,0,0,0,0,63,255,255,255,0,0,1,255,255,255,240,0,0,0,0,0,127,255,255,254,0,0,3,255,255,255,224,0,0,0,0,1,255,255,255,252,0,0,7,255,255,255,192,0,0,0,0,3,255,255,255,248,0,0,15,255,255,255,128,0,0,0,0,7,255,255,255,240,0,0,31,255,255,255,0,0,0,0,0,15,255,255,255,224,0,0,63,255,255,254,0,0,0,0,0,31,255,255,255,128,0,0,127,255,255,252,0,0,0,0,0,127,255,255,255,0,0,0,127,255,255,248,0,0,0,0,0,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,3,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,240,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,15,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,254,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,224,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,127,255,255,255,255,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,252,0,0,0,0,0,0,0,0,0,0,15,255,255,255,255,240,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,224,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0,3,255,255,255,255,254,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,127,255,255,255,255,224,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0,15,255,255,255,255,254,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,224,0,0,0,0,0,0,0,0,0,7,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0,31,255,255,255,255,254,0,0,0,0,0,0,0,0,0,0,127,255,255,255,255,240,0,0,0,0,0,0,0,0,0,3,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,15,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,252,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,224,0,0,0,0,0,0,0,0,0,7,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0,31,255,255,255,255,254,0,0,0,0,0,0,0,0,0,0,127,255,255,255,255,240,0,0,0,0,0,0,0,0,0,3,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,15,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,224,0,0,0,0,0,0,0,0,0,3,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0,15,255,255,255,255,252,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,240,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0,3,255,255,255,255,254,0,0,0,0,0,0,0,0,0,0,15,255,255,255,255,240,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,15,255,255,255,255,224,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,252,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,240,0,0,0,0,0,0,0,0,0,0,15,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,31,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,248,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,224,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,15,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,3,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,192,0,0,63,255,255,255,255,255,255,255,255,255,255,255,255,128,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,1,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,3,255,255,255,255,255,255,255,255,255,255,255,255,252,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,248,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,240,0,0,63,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,127,255,255,255,255,255,255,255,255,255,255,255,255,192,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,128,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,15,255,255,255,255,255,255,255,255,255,255,255,255,252,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,248,0,0,63,255,255,255,255,255,255,255,255,255,255,255,255,240,0,0,127,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,192,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,128,0,3,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,15,255,255,255,255,255,255,255,255,255,255,255,255,252,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,248,0,0,63,255,255,255,255,255,255,255,255,255,255,255,255,240,0,0,127,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,192,0,0,0,0,0,0,0,0,63,240,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,254,0,0,0,0,0,0,0,0,0,3,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,63,255,255,255,255,255,255,0,0,0,0,0,0,0,0,7,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,63,255,255,255,255,255,255,255,0,0,0,0,0,0,0,3,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,63,255,255,255,255,255,255,255,255,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,15,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,248,0,0,0,0,0,7,255,255,255,255,255,255,255,255,255,240,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,224,0,0,0,0,1,255,255,255,255,255,255,255,255,255,255,192,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,0,0,0,0,1,255,255,255,255,255,255,255,255,255,255,254,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,248,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,240,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,15,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,63,255,255,255,255,192,7,255,255,255,255,255,0,0,0,1,255,255,255,255,240,0,1,255,255,255,255,252,0,0,0,7,255,255,255,255,0,0,0,255,255,255,255,240,0,0,0,63,255,255,255,240,0,0,1,255,255,255,255,224,0,0,0,255,255,255,255,128,0,0,1,255,255,255,255,128,0,0,3,255,255,255,248,0,0,0,3,255,255,255,255,0,0,0,31,255,255,255,192,0,0,0,7,255,255,255,252,0,0,0,127,255,255,255,0,0,0,0,15,255,255,255,240,0,0,1,255,255,255,248,0,0,0,0,63,255,255,255,192,0,0,7,255,255,255,192,0,0,0,0,127,255,255,255,0,0,0,31,255,255,255,0,0,0,0,1,255,255,255,254,0,0,0,255,255,255,248,0,0,0,0,3,255,255,255,248,0,0,3,255,255,255,224,0,0,0,0,15,255,255,255,224,0,0,15,255,255,255,128,0,0,0,0,63,255,255,255,128,0,0,63,255,255,252,0,0,0,0,0,255,255,255,254,0,0,0,255,255,255,240,0,0,0,0,1,255,255,255,248,0,0,3,255,255,255,192,0,0,0,0,7,255,255,255,224,0,0,15,255,255,255,0,0,0,0,0,31,255,255,255,128,0,0,63,255,255,252,0,0,0,0,0,127,255,255,254,0,0,0,255,255,255,240,0,0,0,0,1,255,255,255,248,0,0,3,255,255,255,192,0,0,0,0,7,255,255,255,224,0,0,15,255,255,255,0,0,0,0,0,31,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,127,255,255,254,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,127,255,255,254,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,192,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,248,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,192,0,0,0,0,0,0,0,0,127,255,255,255,255,255,254,0,0,0,0,0,0,0,0,1,255,255,255,255,255,255,240,0,0,0,0,0,0,0,0,7,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,31,255,255,255,255,255,248,0,0,0,0,0,0,0,0,0,127,255,255,255,255,255,192,0,0,0,0,0,0,0,0,1,255,255,255,255,255,252,0,0,0,0,0,0,0,0,0,7,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,31,255,255,255,255,254,0,0,0,0,0,0,0,0,0,0,127,255,255,255,255,224,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0,31,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0,127,255,255,255,255,255,128,0,0,0,0,0,0,0,0,1,255,255,255,255,255,255,128,0,0,0,0,0,0,0,0,7,255,255,255,255,255,255,128,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,127,255,255,255,255,255,254,0,0,0,0,0,0,0,0,1,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,7,255,255,255,255,255,255,252,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,248,0,0,0,0,0,0,0,0,127,255,255,255,255,255,255,240,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,127,255,255,254,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,127,255,255,252,0,0,0,0,0,3,255,255,255,240,0,1,255,255,255,240,0,0,0,0,0,15,255,255,255,192,0,7,255,255,255,192,0,0,0,0,0,63,255,255,255,0,0,31,255,255,255,0,0,0,0,0,0,255,255,255,252,0,0,127,255,255,252,0,0,0,0,0,3,255,255,255,240,0,1,255,255,255,240,0,0,0,0,0,15,255,255,255,192,0,7,255,255,255,192,0,0,0,0,0,63,255,255,255,0,0,31,255,255,255,0,0,0,0,0,0,255,255,255,252,0,0,127,255,255,252,0,0,0,0,0,7,255,255,255,240,0,1,255,255,255,248,0,0,0,0,0,31,255,255,255,192,0,7,255,255,255,224,0,0,0,0,0,127,255,255,255,0,0,15,255,255,255,128,0,0,0,0,1,255,255,255,252,0,0,63,255,255,254,0,0,0,0,0,15,255,255,255,240,0,0,255,255,255,252,0,0,0,0,0,63,255,255,255,128,0,3,255,255,255,240,0,0,0,0,1,255,255,255,254,0,0,15,255,255,255,224,0,0,0,0,7,255,255,255,248,0,0,31,255,255,255,192,0,0,0,0,63,255,255,255,224,0,0,127,255,255,255,128,0,0,0,1,255,255,255,255,0,0,1,255,255,255,255,0,0,0,0,15,255,255,255,252,0,0,3,255,255,255,254,0,0,0,0,127,255,255,255,240,0,0,15,255,255,255,252,0,0,0,3,255,255,255,255,128,0,0,31,255,255,255,252,0,0,0,63,255,255,255,254,0,0,0,127,255,255,255,252,0,0,3,255,255,255,255,240,0,0,0,255,255,255,255,255,0,0,255,255,255,255,255,192,0,0,3,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,248,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,192,0,0,0,63,255,255,255,255,255,255,255,255,255,255,254,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,248,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,192,0,0,0,3,255,255,255,255,255,255,255,255,255,255,254,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,31,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,224,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,127,255,255,255,255,255,255,255,255,248,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,0,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,192,0,0,0,0,0,0,1,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,192,0,0,0,0,0,0,0,0,255,255,255,255,255,255,252,0,0,0,0,0,0,0,0,0,127,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0,63,255,255,255,255,240,0,0,0,0,0,0,0,0,0,0,15,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,127,255,248,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,127,255,255,254,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,1,255,248,0,0,0,0,0,0,0,3,255,255,255,240,0,7,255,224,0,0,0,0,0,0,0,31,255,255,255,128,0,31,255,128,0,0,0,0,0,0,0,127,255,255,254,0,0,255,254,0,0,0,0,0,0,0,1,255,255,255,240,0,3,255,248,0,0,0,0,0,0,0,15,255,255,255,192,0,15,255,224,0,0,0,0,0,0,0,63,255,255,255,0,0,127,255,128,0,0,0,0,0,0,1,255,255,255,248,0,1,255,254,0,0,0,0,0,0,0,7,255,255,255,224,0,7,255,248,0,0,0,0,0,0,0,31,255,255,255,0,0,63,255,224,0,0,0,0,0,0,0,255,255,255,252,0,0,255,255,128,0,0,0,0,0,0,3,255,255,255,240,0,3,255,254,0,0,0,0,0,0,0,15,255,255,255,128,0,31,255,248,0,0,0,0,0,0,0,127,255,255,254,0,0,127,255,224,0,0,0,0,0,0,1,255,255,255,240,0,3,255,255,128,0,0,0,0,0,0,15,255,255,255,192,0,15,255,254,0,0,0,0,0,0,0,63,255,255,254,0,0,63,255,248,0,0,0,0,0,0,0,255,255,255,248,0,1,255,255,224,0,0,0,0,0,0,7,255,255,255,224,0,7,255,255,128,0,0,0,0,0,0,31,255,255,255,0,0,63,255,254,0,0,0,0,0,0,0,255,255,255,252,0,0,255,255,248,0,0,0,0,0,0,3,255,255,255,224,0,7,255,255,224,0,0,0,0,0,0,15,255,255,255,128,0,31,255,255,128,0,0,0,0,0,0,127,255,255,252,0,0,127,255,254,0,0,0,0,0,0,1,255,255,255,240,0,3,255,255,248,0,0,0,0,0,0,15,255,255,255,128,0,15,255,255,224,0,0,0,0,0,0,63,255,255,254,0,0,127,255,255,128,0,0,0,0,0,1,255,255,255,240,0,1,255,255,254,0,0,0,0,0,0,7,255,255,255,192,0,15,255,255,248,0,0,0,0,0,0,31,255,255,254,0,0,63,255,255,224,0,0,0,0,0,0,255,255,255,248,0,1,255,255,255,128,0,0,0,0,0,3,255,255,255,192,0,7,255,255,254,0,0,0,0,0,0,31,255,255,255,0,0,63,255,255,248,0,0,0,0,0,0,127,255,255,248,0,0,255,255,255,224,0,0,0,0,0,3,255,255,255,224,0,7,255,255,255,128,0,0,0,0,0,15,255,255,255,0,0,31,255,255,254,0,0,0,0,0,0,127,255,255,252,0,0,255,255,255,248,0,0,0,0,0,1,255,255,255,224,0,3,255,255,255,224,0,0,0,0,0,15,255,255,255,0,0,15,255,255,255,128,0,0,0,0,0,63,255,255,252,0,0,63,255,255,254,0,0,0,0,0,1,255,255,255,224,0,0,255,255,255,248,0,0,0,0,0,7,255,255,255,128,0,3,255,255,255,224,0,0,0,0,0,31,255,255,252,0,0,15,255,255,255,128,0,0,0,0,0,255,255,255,240,0,0,63,255,255,254,0,0,0,0,0,3,255,255,255,128,0,0,255,255,255,248,0,0,0,0,0,31,255,255,254,0,0,3,255,255,255,224,0,0,0,0,0,127,255,255,240,0,0,15,255,255,255,128,0,0,0,0,3,255,255,255,128,0,0,63,255,255,254,0,0,0,0,0,31,255,255,254,0,0,0,255,255,255,248,0,0,0,0,0,127,255,255,240,0,0,3,255,255,255,224,0,0,0,0,3,255,255,255,192,0,0,15,255,255,255,128,0,0,0,0,15,255,255,254,0,0,0,63,255,255,254,0,0,0,0,0,127,255,255,240,0,0,0,255,255,255,248,0,0,0,0,1,255,255,255,192,0,0,3,255,255,255,224,0,0,0,0,15,255,255,254,0,0,0,15,255,255,255,128,0,0,0,0,63,255,255,248,0,0,0,63,255,255,254,0,0,0,0,1,255,255,255,192,0,0,0,255,255,255,248,0,0,0,0,7,255,255,254,0,0,0,3,255,255,255,224,0,0,0,0,63,255,255,248,0,0,0,15,255,255,255,128,0,0,0,0,255,255,255,192,0,0,0,63,255,255,254,0,0,0,0,7,255,255,254,0,0,0,0,255,255,255,248,0,0,0,0,63,255,255,248,0,0,0,3,255,255,255,224,0,0,0,0,255,255,255,192,0,0,0,15,255,255,255,128,0,0,0,7,255,255,254,0,0,0,0,63,255,255,254,0,0,0,0,31,255,255,248,0,0,0,0,255,255,255,248,0,0,0,0,255,255,255,192,0,0,0,3,255,255,255,224,0,0,0,7,255,255,254,0,0,0,0,15,255,255,255,128,0,0,0,31,255,255,240,0,0,0,0,63,255,255,254,0,0,0,0,255,255,255,192,0,0,0,0,255,255,255,248,0,0,0,3,255,255,254,0,0,0,0,3,255,255,255,224,0,0,0,31,255,255,240,0,0,0,0,15,255,255,255,128,0,0,0,255,255,255,128,0,0,0,0,63,255,255,254,0,0,0,3,255,255,255,255,255,255,255,255,255,255,255,255,255,254,0,15,255,255,255,255,255,255,255,255,255,255,255,255,255,248,0,63,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,128,3,255,255,255,255,255,255,255,255,255,255,255,255,255,254,0,15,255,255,255,255,255,255,255,255,255,255,255,255,255,248,0,63,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,128,3,255,255,255,255,255,255,255,255,255,255,255,255,255,254,0,15,255,255,255,255,255,255,255,255,255,255,255,255,255,248,0,63,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,128,3,255,255,255,255,255,255,255,255,255,255,255,255,255,254,0,15,255,255,255,255,255,255,255,255,255,255,255,255,255,248,0,63,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,128,3,255,255,255,255,255,255,255,255,255,255,255,255,255,254,0,15,255,255,255,255,255,255,255,255,255,255,255,255,255,248,0,63,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,128,3,255,255,255,255,255,255,255,255,255,255,255,255,255,254,0,15,255,255,255,255,255,255,255,255,255,255,255,255,255,248,0,63,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,63,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,63,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,63,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,63,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,63,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,63,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,63,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,63,255,255,254,0,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,192,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,192,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,192,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,31,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,192,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,31,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,192,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,31,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,192,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,192,0,0,0,3,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,15,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,240,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,240,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,224,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,7,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,224,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,7,255,255,254,0,0,15,255,224,0,0,0,0,0,0,0,63,255,255,248,0,7,255,255,252,0,0,0,0,0,0,0,255,255,255,224,1,255,255,255,254,0,0,0,0,0,0,3,255,255,255,128,63,255,255,255,255,0,0,0,0,0,0,15,255,255,254,3,255,255,255,255,255,0,0,0,0,0,0,63,255,255,248,63,255,255,255,255,255,0,0,0,0,0,0,255,255,255,227,255,255,255,255,255,255,0,0,0,0,0,3,255,255,255,159,255,255,255,255,255,255,0,0,0,0,0,15,255,255,255,255,255,255,255,255,255,254,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,248,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,248,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,224,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,31,255,255,255,255,255,255,255,255,255,255,254,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,252,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,192,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,7,255,255,255,255,252,0,15,255,255,255,255,248,0,0,0,31,255,255,255,255,128,0,7,255,255,255,255,240,0,0,0,127,255,255,255,248,0,0,7,255,255,255,255,192,0,0,1,255,255,255,255,128,0,0,15,255,255,255,255,128,0,0,7,255,255,255,252,0,0,0,15,255,255,255,254,0,0,0,63,255,255,255,192,0,0,0,31,255,255,255,248,0,0,0,255,255,255,254,0,0,0,0,63,255,255,255,240,0,0,3,255,255,255,240,0,0,0,0,127,255,255,255,192,0,0,0,127,255,255,128,0,0,0,1,255,255,255,255,0,0,0,0,3,255,252,0,0,0,0,3,255,255,255,252,0,0,0,0,0,15,240,0,0,0,0,7,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,127,255,255,252,0,0,0,0,0,3,255,255,255,240,0,1,255,255,255,240,0,0,0,0,0,15,255,255,255,192,0,7,255,255,255,192,0,0,0,0,0,63,255,255,255,0,0,31,255,255,255,0,0,0,0,0,0,255,255,255,252,0,0,127,255,255,252,0,0,0,0,0,7,255,255,255,240,0,1,255,255,255,240,0,0,0,0,0,31,255,255,255,192,0,7,255,255,255,224,0,0,0,0,0,127,255,255,254,0,0,31,255,255,255,128,0,0,0,0,1,255,255,255,248,0,0,63,255,255,254,0,0,0,0,0,7,255,255,255,224,0,0,255,255,255,248,0,0,0,0,0,63,255,255,255,128,0,3,255,255,255,240,0,0,0,0,0,255,255,255,254,0,0,15,255,255,255,192,0,0,0,0,7,255,255,255,248,0,0,63,255,255,255,128,0,0,0,0,31,255,255,255,192,0,0,127,255,255,254,0,0,0,0,0,127,255,255,255,0,0,1,255,255,255,252,0,0,0,0,3,255,255,255,252,0,0,7,255,255,255,240,0,0,0,0,31,255,255,255,224,0,0,31,255,255,255,224,0,0,0,0,127,255,255,255,128,0,0,63,255,255,255,192,0,0,0,3,255,255,255,254,0,0,0,255,255,255,255,0,0,0,0,31,255,255,255,240,0,0,1,255,255,255,255,0,0,0,0,255,255,255,255,192,0,0,7,255,255,255,254,0,0,0,7,255,255,255,255,0,0,0,31,255,255,255,252,0,0,0,63,255,255,255,248,0,0,0,63,255,255,255,252,0,0,3,255,255,255,255,224,0,0,0,255,255,255,255,252,0,0,63,255,255,255,255,0,0,0,1,255,255,255,255,254,0,7,255,255,255,255,252,0,0,0,3,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,15,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,31,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,224,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,1,255,255,255,255,255,255,255,255,255,255,248,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,224,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,15,255,255,255,255,255,255,255,255,255,248,0,0,0,0,0,31,255,255,255,255,255,255,255,255,255,192,0,0,0,0,0,63,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,127,255,255,255,255,255,255,255,255,240,0,0,0,0,0,0,127,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,248,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,128,0,0,0,0,0,0,1,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,192,0,0,0,0,0,0,0,1,255,255,255,255,255,255,252,0,0,0,0,0,0,0,0,1,255,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,255,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,127,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,15,252,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,240,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,252,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,127,255,255,255,255,255,0,0,0,0,0,0,0,0,0,7,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,127,255,255,255,255,255,255,0,0,0,0,0,0,0,0,7,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,63,255,255,255,255,255,255,254,0,0,0,0,0,0,0,3,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,248,0,0,0,0,0,0,7,255,255,255,255,255,255,255,255,240,0,0,0,0,0,0,63,255,255,255,255,255,255,255,255,224,0,0,0,0,0,1,255,255,255,255,255,255,255,255,255,192,0,0,0,0,0,15,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,0,0,0,0,0,3,255,255,255,255,255,255,255,255,255,254,0,0,0,0,0,15,255,255,255,255,255,255,255,255,255,248,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,240,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,224,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,0,0,0,0,3,255,255,255,255,248,1,255,255,255,255,252,0,0,0,0,15,255,255,255,254,0,0,127,255,255,255,248,0,0,0,0,127,255,255,255,224,0,0,127,255,255,255,224,0,0,0,1,255,255,255,254,0,0,0,255,255,255,255,192,0,0,0,15,255,255,255,240,0,0,0,255,255,255,255,0,0,0,0,63,255,255,255,128,0,0,1,255,255,255,252,0,0,0,0,255,255,255,252,0,0,0,7,255,255,255,248,0,0,0,7,255,255,255,224,0,0,0,15,255,255,255,224,0,0,0,31,255,255,255,0,0,0,0,31,255,255,255,128,0,0,0,255,255,255,252,0,0,0,0,127,255,255,254,0,0,0,3,255,255,255,224,0,0,0,0,255,255,255,252,0,0,0,15,255,255,255,128,0,0,0,3,255,255,255,240,0,0,0,127,255,255,252,0,0,0,0,15,255,255,255,192,0,0,1,255,255,255,240,0,0,0,0,31,255,255,255,0,0,0,7,255,255,255,128,0,0,0,0,127,255,255,252,0,0,0,31,255,255,254,0,0,0,0,1,255,255,255,248,0,0,0,255,255,255,248,0,0,0,0,7,255,255,255,224,0,0,3,255,255,255,224,0,0,0,0,15,255,255,255,128,0,0,15,255,255,255,0,0,0,0,0,63,255,255,254,0,0,0,63,255,255,252,0,0,0,0,0,255,255,255,248,0,0,1,255,255,255,240,0,0,0,0,3,255,255,255,224,0,0,7,255,255,255,192,0,0,0,0,15,255,255,255,128,0,0,31,255,255,255,0,0,0,0,0,63,255,255,254,0,0,0,127,255,255,248,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,63,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,63,255,255,254,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,192,0,0,31,255,192,0,0,0,0,0,0,31,255,255,255,0,0,15,255,255,248,0,0,0,0,0,0,127,255,255,252,0,1,255,255,255,254,0,0,0,0,0,1,255,255,255,240,0,63,255,255,255,254,0,0,0,0,0,7,255,255,255,192,3,255,255,255,255,255,0,0,0,0,0,31,255,255,255,0,63,255,255,255,255,255,0,0,0,0,0,127,255,255,252,3,255,255,255,255,255,254,0,0,0,0,1,255,255,255,240,31,255,255,255,255,255,254,0,0,0,0,7,255,255,255,193,255,255,255,255,255,255,252,0,0,0,0,63,255,255,255,15,255,255,255,255,255,255,252,0,0,0,0,255,255,255,252,255,255,255,255,255,255,255,248,0,0,0,3,255,255,255,247,255,255,255,255,255,255,255,240,0,0,0,15,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,63,255,255,255,255,255,255,255,255,255,255,255,192,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,128,0,0,3,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,15,255,255,255,255,255,255,255,255,255,255,255,252,0,0,0,63,255,255,255,255,255,255,255,255,255,255,255,248,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,240,0,0,3,255,255,255,255,255,255,255,255,255,255,255,255,192,0,0,15,255,255,255,255,255,255,255,255,255,255,255,255,128,0,0,63,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,255,255,255,255,255,255,128,31,255,255,255,255,252,0,0,3,255,255,255,255,255,224,0,7,255,255,255,255,240,0,0,15,255,255,255,255,254,0,0,7,255,255,255,255,224,0,0,63,255,255,255,255,224,0,0,7,255,255,255,255,128,0,0,255,255,255,255,255,0,0,0,15,255,255,255,255,0,0,3,255,255,255,255,248,0,0,0,15,255,255,255,252,0,0,15,255,255,255,255,192,0,0,0,31,255,255,255,240,0,0,63,255,255,255,254,0,0,0,0,127,255,255,255,224,0,0,255,255,255,255,240,0,0,0,0,255,255,255,255,128,0,3,255,255,255,255,192,0,0,0,1,255,255,255,254,0,0,15,255,255,255,254,0,0,0,0,7,255,255,255,248,0,0,63,255,255,255,248,0,0,0,0,15,255,255,255,240,0,0,255,255,255,255,192,0,0,0,0,63,255,255,255,192,0,3,255,255,255,255,0,0,0,0,0,127,255,255,255,0,0,15,255,255,255,248,0,0,0,0,1,255,255,255,252,0,0,63,255,255,255,224,0,0,0,0,3,255,255,255,240,0,0,255,255,255,255,128,0,0,0,0,15,255,255,255,192,0,1,255,255,255,252,0,0,0,0,0,63,255,255,255,128,0,7,255,255,255,240,0,0,0,0,0,255,255,255,254,0,0,31,255,255,255,192,0,0,0,0,3,255,255,255,248,0,0,127,255,255,255,0,0,0,0,0,7,255,255,255,224,0,1,255,255,255,252,0,0,0,0,0,31,255,255,255,128,0,7,255,255,255,240,0,0,0,0,0,127,255,255,254,0,0,31,255,255,255,192,0,0,0,0,1,255,255,255,248,0,0,127,255,255,255,0,0,0,0,0,7,255,255,255,224,0,1,255,255,255,252,0,0,0,0,0,31,255,255,255,128,0,3,255,255,255,240,0,0,0,0,0,127,255,255,254,0,0,15,255,255,255,192,0,0,0,0,1,255,255,255,248,0,0,63,255,255,255,0,0,0,0,0,7,255,255,255,224,0,0,255,255,255,252,0,0,0,0,0,31,255,255,255,128,0,3,255,255,255,240,0,0,0,0,0,127,255,255,254,0,0,7,255,255,255,192,0,0,0,0,3,255,255,255,248,0,0,31,255,255,255,0,0,0,0,0,15,255,255,255,224,0,0,127,255,255,252,0,0,0,0,0,63,255,255,255,0,0,1,255,255,255,248,0,0,0,0,0,255,255,255,252,0,0,7,255,255,255,224,0,0,0,0,3,255,255,255,240,0,0,15,255,255,255,128,0,0,0,0,31,255,255,255,192,0,0,63,255,255,255,0,0,0,0,0,127,255,255,255,0,0,0,255,255,255,252,0,0,0,0,1,255,255,255,252,0,0,1,255,255,255,240,0,0,0,0,15,255,255,255,224,0,0,7,255,255,255,224,0,0,0,0,63,255,255,255,128,0,0,31,255,255,255,128,0,0,0,1,255,255,255,254,0,0,0,63,255,255,255,0,0,0,0,15,255,255,255,240,0,0,0,255,255,255,254,0,0,0,0,63,255,255,255,192,0,0,3,255,255,255,248,0,0,0,1,255,255,255,255,0,0,0,7,255,255,255,240,0,0,0,15,255,255,255,248,0,0,0,31,255,255,255,224,0,0,0,127,255,255,255,224,0,0,0,63,255,255,255,224,0,0,3,255,255,255,255,0,0,0,0,255,255,255,255,192,0,0,63,255,255,255,252,0,0,0,1,255,255,255,255,192,0,3,255,255,255,255,224,0,0,0,7,255,255,255,255,224,0,127,255,255,255,255,128,0,0,0,15,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,224,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,15,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,31,255,255,255,255,255,255,255,255,255,224,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,127,255,255,255,255,255,255,255,255,248,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,192,0,0,0,0,0,1,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,3,255,255,255,255,255,255,255,255,240,0,0,0,0,0,0,7,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,7,255,255,255,255,255,255,255,248,0,0,0,0,0,0,0,15,255,255,255,255,255,255,255,128,0,0,0,0,0,0,0,15,255,255,255,255,255,255,248,0,0,0,0,0,0,0,0,15,255,255,255,255,255,255,128,0,0,0,0,0,0,0,0,15,255,255,255,255,255,248,0,0,0,0,0,0,0,0,0,7,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0,3,255,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,7,255,252,0,0,0,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,255,255,252,0,0,127,255,255,255,255,255,255,255,255,255,255,255,255,248,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,240,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,3,255,255,255,255,255,255,255,255,255,255,255,255,255,192,0,7,255,255,255,255,255,255,255,255,255,255,255,255,255,128,0,15,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,63,255,255,255,255,255,255,255,255,255,255,255,255,252,0,0,127,255,255,255,255,255,255,255,255,255,255,255,255,248,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,240,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,3,255,255,255,255,255,255,255,255,255,255,255,255,255,192,0,7,255,255,255,255,255,255,255,255,255,255,255,255,255,128,0,15,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,63,255,255,255,255,255,255,255,255,255,255,255,255,252,0,0,127,255,255,255,255,255,255,255,255,255,255,255,255,248,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,128,0,3,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,248,0,0,15,255,255,255,255,255,255,255,255,255,255,255,255,240,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,192,0,0,63,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,63,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,252,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,63,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,127,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,254,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,252,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,252,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,252,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,254,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,127,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,254,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,252,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,254,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,63,240,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,248,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,254,0,0,0,0,0,0,0,0,0,3,255,255,255,255,255,254,0,0,0,0,0,0,0,0,0,63,255,255,255,255,255,255,0,0,0,0,0,0,0,0,3,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,63,255,255,255,255,255,255,254,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,15,255,255,255,255,255,255,255,255,248,0,0,0,0,0,0,127,255,255,255,255,255,255,255,255,240,0,0,0,0,0,3,255,255,255,255,255,255,255,255,255,224,0,0,0,0,0,31,255,255,255,255,255,255,255,255,255,192,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,31,255,255,255,255,255,255,255,255,255,254,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,248,0,0,0,0,31,255,255,255,255,255,255,255,255,255,255,224,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,192,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,31,255,255,255,255,224,1,255,255,255,255,254,0,0,0,0,255,255,255,255,248,0,0,127,255,255,255,248,0,0,0,3,255,255,255,255,128,0,0,127,255,255,255,240,0,0,0,15,255,255,255,248,0,0,0,127,255,255,255,192,0,0,0,127,255,255,255,192,0,0,0,255,255,255,255,128,0,0,1,255,255,255,254,0,0,0,1,255,255,255,254,0,0,0,7,255,255,255,240,0,0,0,3,255,255,255,248,0,0,0,63,255,255,255,128,0,0,0,7,255,255,255,240,0,0,0,255,255,255,252,0,0,0,0,15,255,255,255,192,0,0,3,255,255,255,224,0,0,0,0,31,255,255,255,0,0,0,15,255,255,255,128,0,0,0,0,127,255,255,252,0,0,0,63,255,255,252,0,0,0,0,1,255,255,255,240,0,0,1,255,255,255,240,0,0,0,0,3,255,255,255,224,0,0,7,255,255,255,192,0,0,0,0,15,255,255,255,128,0,0,31,255,255,255,0,0,0,0,0,63,255,255,254,0,0,0,127,255,255,248,0,0,0,0,0,127,255,255,248,0,0,1,255,255,255,224,0,0,0,0,1,255,255,255,224,0,0,7,255,255,255,128,0,0,0,0,7,255,255,255,128,0,0,31,255,255,254,0,0,0,0,0,31,255,255,254,0,0,0,127,255,255,248,0,0,0,0,0,127,255,255,248,0,0,1,255,255,255,224,0,0,0,0,1,255,255,255,224,0,0,7,255,255,255,128,0,0,0,0,7,255,255,255,128,0,0,31,255,255,254,0,0,0,0,0,31,255,255,254,0,0,0,127,255,255,248,0,0,0,0,0,127,255,255,248,0,0,1,255,255,255,224,0,0,0,0,1,255,255,255,224,0,0,3,255,255,255,192,0,0,0,0,15,255,255,255,0,0,0,15,255,255,255,0,0,0,0,0,63,255,255,252,0,0,0,63,255,255,252,0,0,0,0,0,255,255,255,240,0,0,0,255,255,255,240,0,0,0,0,3,255,255,255,192,0,0,1,255,255,255,224,0,0,0,0,31,255,255,254,0,0,0,7,255,255,255,128,0,0,0,0,127,255,255,248,0,0,0,31,255,255,255,0,0,0,0,3,255,255,255,224,0,0,0,63,255,255,254,0,0,0,0,31,255,255,255,0,0,0,0,255,255,255,248,0,0,0,0,255,255,255,252,0,0,0,3,255,255,255,240,0,0,0,7,255,255,255,224,0,0,0,7,255,255,255,240,0,0,0,63,255,255,255,128,0,0,0,31,255,255,255,224,0,0,1,255,255,255,252,0,0,0,0,63,255,255,255,224,0,0,31,255,255,255,240,0,0,0,0,127,255,255,255,224,0,3,255,255,255,255,128,0,0,0,1,255,255,255,255,248,0,127,255,255,255,254,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,15,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,31,255,255,255,255,255,255,255,255,255,224,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,127,255,255,255,255,255,255,255,255,248,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,192,0,0,0,0,0,1,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,3,255,255,255,255,255,255,255,255,240,0,0,0,0,0,0,3,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,7,255,255,255,255,255,255,255,248,0,0,0,0,0,0,0,15,255,255,255,255,255,255,255,192,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,192,0,0,0,0,0,0,15,255,255,255,255,255,255,255,255,192,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,192,0,0,0,0,0,7,255,255,255,255,255,255,255,255,255,192,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,128,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,31,255,255,255,255,255,255,255,255,255,255,0,0,0,0,1,255,255,255,255,255,255,255,255,255,255,254,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,248,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,15,255,255,255,255,224,0,31,255,255,255,255,192,0,0,0,127,255,255,255,252,0,0,15,255,255,255,255,128,0,0,3,255,255,255,255,192,0,0,15,255,255,255,255,0,0,0,15,255,255,255,252,0,0,0,15,255,255,255,252,0,0,0,127,255,255,255,192,0,0,0,15,255,255,255,248,0,0,1,255,255,255,254,0,0,0,0,31,255,255,255,224,0,0,15,255,255,255,240,0,0,0,0,63,255,255,255,192,0,0,63,255,255,255,128,0,0,0,0,127,255,255,255,0,0,1,255,255,255,254,0,0,0,0,1,255,255,255,254,0,0,7,255,255,255,240,0,0,0,0,3,255,255,255,248,0,0,31,255,255,255,128,0,0,0,0,7,255,255,255,224,0,0,255,255,255,254,0,0,0,0,0,31,255,255,255,192,0,3,255,255,255,248,0,0,0,0,0,127,255,255,255,0,0,15,255,255,255,192,0,0,0,0,0,255,255,255,252,0,0,63,255,255,255,0,0,0,0,0,3,255,255,255,240,0,0,255,255,255,252,0,0,0,0,0,15,255,255,255,192,0,7,255,255,255,224,0,0,0,0,0,31,255,255,255,128,0,31,255,255,255,128,0,0,0,0,0,127,255,255,254,0,0,127,255,255,254,0,0,0,0,0,1,255,255,255,248,0,1,255,255,255,248,0,0,0,0,0,7,255,255,255,224,0,7,255,255,255,224,0,0,0,0,0,31,255,255,255,128,0,31,255,255,255,128,0,0,0,0,0,127,255,255,254,0,0,127,255,255,254,0,0,0,0,0,1,255,255,255,248,0,1,255,255,255,248,0,0,0,0,0,7,255,255,255,224,0,7,255,255,255,224,0,0,0,0,0,31,255,255,255,128,0,31,255,255,255,128,0,0,0,0,0,127,255,255,254,0,0,127,255,255,255,0,0,0,0,0,3,255,255,255,248,0,1,255,255,255,252,0,0,0,0,0,15,255,255,255,224,0,7,255,255,255,240,0,0,0,0,0,63,255,255,255,128,0,15,255,255,255,224,0,0,0,0,1,255,255,255,252,0,0,63,255,255,255,128,0,0,0,0,7,255,255,255,240,0,0,255,255,255,254,0,0,0,0,0,31,255,255,255,192,0,3,255,255,255,252,0,0,0,0,0,255,255,255,255,0,0,15,255,255,255,248,0,0,0,0,7,255,255,255,252,0,0,31,255,255,255,224,0,0,0,0,31,255,255,255,224,0,0,127,255,255,255,192,0,0,0,0,255,255,255,255,128,0,1,255,255,255,255,128,0,0,0,7,255,255,255,254,0,0,3,255,255,255,255,0,0,0,0,63,255,255,255,240,0,0,15,255,255,255,255,0,0,0,3,255,255,255,255,192,0,0,63,255,255,255,255,0,0,0,63,255,255,255,255,0,0,0,127,255,255,255,255,0,0,3,255,255,255,255,248,0,0,1,255,255,255,255,255,128,0,127,255,255,255,255,224,0,0,3,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,15,255,255,255,255,255,255,255,255,255,255,255,252,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,63,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,252,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,3,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,248,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,192,0,0,0,0,31,255,255,255,255,255,255,255,255,255,254,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,240,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,192,0,0,0,0,0,1,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,224,0,0,0,0,0,0,1,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,224,0,0,0,0,0,0,0,1,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,127,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,15,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,127,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,128,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,224,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,240,0,0,0,0,0,0,0,0,0,3,255,255,255,255,255,248,0,0,0,0,0,0,0,0,0,127,255,255,255,255,255,252,0,0,0,0,0,0,0,0,7,255,255,255,255,255,255,252,0,0,0,0,0,0,0,0,127,255,255,255,255,255,255,248,0,0,0,0,0,0,0,3,255,255,255,255,255,255,255,248,0,0,0,0,0,0,0,63,255,255,255,255,255,255,255,248,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,240,0,0,0,0,0,0,31,255,255,255,255,255,255,255,255,224,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,192,0,0,0,0,0,7,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,0,0,0,0,0,1,255,255,255,255,255,255,255,255,255,254,0,0,0,0,0,15,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,248,0,0,0,0,1,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,192,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,128,0,0,0,3,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,127,255,255,255,255,224,7,255,255,255,255,248,0,0,0,1,255,255,255,255,248,0,1,255,255,255,255,224,0,0,0,15,255,255,255,255,128,0,1,255,255,255,255,192,0,0,0,63,255,255,255,248,0,0,1,255,255,255,255,0,0,0,1,255,255,255,255,192,0,0,3,255,255,255,254,0,0,0,7,255,255,255,254,0,0,0,3,255,255,255,248,0,0,0,63,255,255,255,240,0,0,0,7,255,255,255,240,0,0,0,255,255,255,255,128,0,0,0,31,255,255,255,192,0,0,3,255,255,255,252,0,0,0,0,63,255,255,255,0,0,0,31,255,255,255,224,0,0,0,0,127,255,255,254,0,0,0,127,255,255,255,128,0,0,0,1,255,255,255,248,0,0,1,255,255,255,252,0,0,0,0,3,255,255,255,224,0,0,15,255,255,255,240,0,0,0,0,15,255,255,255,192,0,0,63,255,255,255,128,0,0,0,0,31,255,255,255,0,0,0,255,255,255,254,0,0,0,0,0,127,255,255,252,0,0,3,255,255,255,248,0,0,0,0,1,255,255,255,248,0,0,15,255,255,255,192,0,0,0,0,3,255,255,255,224,0,0,63,255,255,255,0,0,0,0,0,15,255,255,255,128,0,1,255,255,255,252,0,0,0,0,0,63,255,255,254,0,0,7,255,255,255,240,0,0,0,0,0,255,255,255,248,0,0,31,255,255,255,128,0,0,0,0,3,255,255,255,240,0,0,127,255,255,254,0,0,0,0,0,7,255,255,255,192,0,1,255,255,255,248,0,0,0,0,0,31,255,255,255,0,0,7,255,255,255,224,0,0,0,0,0,127,255,255,252,0,0,31,255,255,255,128,0,0,0,0,1,255,255,255,240,0,0,127,255,255,254,0,0,0,0,0,7,255,255,255,224,0,1,255,255,255,248,0,0,0,0,0,31,255,255,255,128,0,7,255,255,255,224,0,0,0,0,0,127,255,255,254,0,0,31,255,255,255,128,0,0,0,0,1,255,255,255,248,0,0,127,255,255,254,0,0,0,0,0,7,255,255,255,224,0,1,255,255,255,248,0,0,0,0,0,63,255,255,255,128,0,7,255,255,255,240,0,0,0,0,0,255,255,255,254,0,0,31,255,255,255,192,0,0,0,0,3,255,255,255,248,0,0,127,255,255,255,0,0,0,0,0,15,255,255,255,224,0,0,255,255,255,252,0,0,0,0,0,63,255,255,255,192,0,3,255,255,255,240,0,0,0,0,1,255,255,255,255,0,0,15,255,255,255,224,0,0,0,0,7,255,255,255,252,0,0,63,255,255,255,128,0,0,0,0,31,255,255,255,240,0,0,255,255,255,255,0,0,0,0,0,255,255,255,255,192,0,3,255,255,255,252,0,0,0,0,3,255,255,255,255,0,0,7,255,255,255,248,0,0,0,0,31,255,255,255,252,0,0,31,255,255,255,224,0,0,0,0,127,255,255,255,240,0,0,127,255,255,255,192,0,0,0,3,255,255,255,255,192,0,0,255,255,255,255,128,0,0,0,31,255,255,255,255,0,0,3,255,255,255,254,0,0,0,0,127,255,255,255,252,0,0,15,255,255,255,252,0,0,0,3,255,255,255,255,240,0,0,31,255,255,255,252,0,0,0,63,255,255,255,255,192,0,0,127,255,255,255,248,0,0,1,255,255,255,255,255,0,0,1,255,255,255,255,248,0,0,31,255,255,255,255,252,0,0,3,255,255,255,255,248,0,1,255,255,255,255,255,240,0,0,15,255,255,255,255,254,0,127,255,255,255,255,255,192,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,252,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,240,0,0,1,255,255,255,255,255,255,255,255,255,255,255,255,192,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,15,255,255,255,255,255,255,255,255,255,255,255,252,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,192,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,252,0,0,0,3,255,255,255,255,255,255,255,251,255,255,255,240,0,0,0,7,255,255,255,255,255,255,255,207,255,255,255,192,0,0,0,15,255,255,255,255,255,255,252,63,255,255,255,0,0,0,0,15,255,255,255,255,255,255,224,255,255,255,252,0,0,0,0,31,255,255,255,255,255,255,3,255,255,255,224,0,0,0,0,31,255,255,255,255,255,240,15,255,255,255,128,0,0,0,0,63,255,255,255,255,255,0,63,255,255,254,0,0,0,0,0,63,255,255,255,255,240,0,255,255,255,248,0,0,0,0,0,31,255,255,255,255,0,3,255,255,255,224,0,0,0,0,0,31,255,255,255,240,0,15,255,255,255,128,0,0,0,0,0,7,255,255,252,0,0,63,255,255,254,0,0,0,0,0,0,0,127,255,0,0,0,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,127,255,255,252,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,252,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,128,0,0,31,255,255,255,0,0,0,0,0,31,255,255,254,0,0,0,127,255,255,252,0,0,0,0,0,255,255,255,248,0,0,1,255,255,255,240,0,0,0,0,3,255,255,255,224,0,0,7,255,255,255,192,0,0,0,0,15,255,255,255,128,0,0,31,255,255,255,0,0,0,0,0,63,255,255,252,0,0,0,127,255,255,252,0,0,0,0,0,255,255,255,240,0,0,1,255,255,255,248,0,0,0,0,7,255,255,255,192,0,0,7,255,255,255,224,0,0,0,0,31,255,255,255,0,0,0,15,255,255,255,128,0,0,0,0,127,255,255,248,0,0,0,63,255,255,254,0,0,0,0,3,255,255,255,224,0,0,0,255,255,255,248,0,0,0,0,15,255,255,255,128,0,0,3,255,255,255,240,0,0,0,0,63,255,255,252,0,0,0,15,255,255,255,192,0,0,0,1,255,255,255,240,0,0,0,31,255,255,255,128,0,0,0,7,255,255,255,192,0,0,0,127,255,255,254,0,0,0,0,63,255,255,254,0,0,0,1,255,255,255,252,0,0,0,1,255,255,255,248,0,0,0,7,255,255,255,240,0,0,0,7,255,255,255,224,0,0,0,15,255,255,255,224,0,0,0,63,255,255,255,0,0,0,0,63,255,255,255,192,0,0,1,255,255,255,252,0,0,0,0,255,255,255,255,128,0,0,15,255,255,255,224,0,0,0,1,255,255,255,255,128,0,0,255,255,255,255,128,0,0,0,7,255,255,255,255,0,0,15,255,255,255,252,0,0,0,0,15,255,255,255,255,128,1,255,255,255,255,240,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,127,255,255,255,255,255,255,255,255,255,254,0,0,0,0,1,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,7,255,255,255,255,255,255,255,255,255,254,0,0,0,0,0,31,255,255,255,255,255,255,255,255,255,240,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,127,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,224,0,0,0,0,0,1,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,3,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,7,255,255,255,255,255,255,255,255,192,0,0,0,0,0,0,15,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,240,0,0,0,0,0,0,0,63,255,255,255,255,255,255,255,128,0,0,0,0,0,0,0,63,255,255,255,255,255,255,248,0,0,0,0,0,0,0,0,127,255,255,255,255,255,255,192,0,0,0,0,0,0,0,0,127,255,255,255,255,255,252,0,0,0,0,0,0,0,0,0,127,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,63,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,7,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,240,0,0,0,0,0,0,0,0,15,255,255,192,0,0,0,0,0,0,0,0,15,255,255,255,255,248,0,0,0,0,0,0,0,0,31,255,255,0,0,0,0,0,0,0,0,0,127,255,255,255,255,252,0,0,0,0,0,0,0,0,127,255,252,0,0,0,0,0,0,0,0,1,255,255,255,255,255,252,0,0,0,0,0,0,0,1,255,255,248,0,0,0,0,0,0,0,0,15,255,255,255,255,255,254,0,0,0,0,0,0,0,3,255,255,224,0,0,0,0,0,0,0,0,63,255,255,255,255,255,254,0,0,0,0,0,0,0,15,255,255,128,0,0,0,0,0,0,0,0,255,255,255,255,255,255,254,0,0,0,0,0,0,0,63,255,255,0,0,0,0,0,0,0,0,1,255,255,255,255,255,255,252,0,0,0,0,0,0,0,127,255,252,0,0,0,0,0,0,0,0,7,255,255,255,255,255,255,252,0,0,0,0,0,0,1,255,255,240,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,252,0,0,0,0,0,0,7,255,255,192,0,0,0,0,0,0,0,0,127,255,255,255,255,255,255,252,0,0,0,0,0,0,31,255,255,128,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,248,0,0,0,0,0,0,63,255,254,0,0,0,0,0,0,0,0,3,255,255,255,255,255,255,255,248,0,0,0,0,0,0,255,255,248,0,0,0,0,0,0,0,0,7,255,255,255,128,127,255,255,240,0,0,0,0,0,3,255,255,240,0,0,0,0,0,0,0,0,31,255,255,248,0,31,255,255,240,0,0,0,0,0,7,255,255,192,0,0,0,0,0,0,0,0,63,255,255,224,0,31,255,255,224,0,0,0,0,0,31,255,255,0,0,0,0,0,0,0,0,0,127,255,255,0,0,31,255,255,192,0,0,0,0,0,127,255,254,0,0,0,0,0,0,0,0,1,255,255,254,0,0,31,255,255,192,0,0,0,0,0,255,255,248,0,0,0,0,0,0,0,0,3,255,255,248,0,0,31,255,255,128,0,0,0,0,3,255,255,224,0,0,0,0,0,0,0,0,7,255,255,240,0,0,31,255,255,0,0,0,0,0,15,255,255,128,0,0,0,0,0,0,0,0,15,255,255,192,0,0,63,255,254,0,0,0,0,0,63,255,255,0,0,0,0,0,0,0,0,0,63,255,255,128,0,0,127,255,254,0,0,0,0,0,127,255,252,0,0,0,0,0,0,0,0,0,127,255,254,0,0,0,127,255,252,0,0,0,0,1,255,255,240,0,0,0,0,0,0,0,0,0,255,255,252,0,0,0,255,255,248,0,0,0,0,7,255,255,224,0,0,0,0,0,0,0,0,1,255,255,248,0,0,1,255,255,240,0,0,0,0,15,255,255,128,0,0,0,0,0,0,0,0,3,255,255,240,0,0,3,255,255,224,0,0,0,0,63,255,254,0,0,0,0,0,0,0,0,0,15,255,255,224,0,0,3,255,255,192,0,0,0,0,255,255,252,0,0,0,0,0,0,0,0,0,31,255,255,128,0,0,7,255,255,192,0,0,0,1,255,255,240,0,0,0,0,0,0,0,0,0,63,255,255,0,0,0,15,255,255,128,0,0,0,7,255,255,192,0,0,0,0,0,0,0,0,0,127,255,254,0,0,0,31,255,255,0,0,0,0,31,255,255,0,0,0,0,0,0,0,0,0,0,255,255,252,0,0,0,63,255,254,0,0,0,0,127,255,254,0,0,0,0,0,0,0,0,0,1,255,255,248,0,0,0,127,255,252,0,0,0,0,255,255,248,0,0,0,0,0,0,0,0,0,3,255,255,240,0,0,0,255,255,248,0,0,0,3,255,255,224,0,0,0,0,0,0,0,0,0,7,255,255,224,0,0,1,255,255,240,0,0,0,15,255,255,192,0,0,0,0,0,0,0,0,0,15,255,255,192,0,0,3,255,255,224,0,0,0,31,255,255,0,0,0,0,0,0,0,0,0,0,31,255,255,128,0,0,7,255,255,192,0,0,0,127,255,252,0,0,0,0,0,0,0,0,0,0,63,255,255,0,0,0,15,255,255,128,0,0,1,255,255,240,0,0,0,0,0,0,0,0,0,0,127,255,254,0,0,0,31,255,255,0,0,0,3,255,255,224,0,0,0,0,0,0,0,0,0,0,255,255,252,0,0,0,63,255,254,0,0,0,15,255,255,128,0,0,0,0,0,0,0,0,0,1,255,255,248,0,0,0,127,255,252,0,0,0,63,255,254,0,0,0,0,0,0,0,0,0,0,3,255,255,240,0,0,0,255,255,248,0,0,0,255,255,252,0,0,0,0,0,0,0,0,0,0,7,255,255,224,0,0,1,255,255,240,0,0,1,255,255,240,0,0,0,0,0,0,0,0,0,0,15,255,255,192,0,0,3,255,255,224,0,0,7,255,255,192,0,0,0,0,0,0,0,0,0,0,31,255,255,128,0,0,7,255,255,192,0,0,31,255,255,128,0,0,0,0,0,0,0,0,0,0,63,255,255,128,0,0,31,255,255,0,0,0,63,255,254,0,0,0,0,0,0,0,0,0,0,0,63,255,255,0,0,0,63,255,254,0,0,0,255,255,248,0,0,0,0,0,0,0,0,0,0,0,127,255,254,0,0,0,127,255,252,0,0,3,255,255,224,0,0,0,0,0,0,0,0,0,0,0,255,255,252,0,0,0,255,255,248,0,0,7,255,255,192,0,0,0,0,0,0,0,0,0,0,1,255,255,248,0,0,1,255,255,240,0,0,31,255,255,0,0,0,0,0,0,0,0,0,0,0,3,255,255,248,0,0,7,255,255,224,0,0,127,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,240,0,0,15,255,255,128,0,1,255,255,248,0,0,0,0,0,0,0,0,0,0,0,7,255,255,240,0,0,31,255,255,0,0,3,255,255,224,0,0,0,0,0,0,0,0,0,0,0,15,255,255,224,0,0,127,255,254,0,0,15,255,255,128,0,0,0,0,0,0,0,0,0,0,0,31,255,255,224,0,0,255,255,252,0,0,63,255,255,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,192,0,3,255,255,240,0,0,127,255,252,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,192,0,15,255,255,224,0,1,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,224,0,127,255,255,192,0,7,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,248,7,255,255,255,0,0,15,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,254,0,0,63,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,248,0,0,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,240,0,3,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,192,0,7,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,0,0,31,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,255,252,0,0,127,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,255,248,0,0,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,255,224,0,3,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,255,128,0,15,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,252,0,0,31,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,240,0,0,127,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,128,0,1,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,252,0,0,7,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,224,0,0,15,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,252,0,0,0,63,255,254,0,0,0,0,255,254,0,0,0,0,0,0,0,0,0,0,0,127,255,0,0,0,0,255,255,248,0,0,0,127,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,240,0,0,7,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,192,0,0,127,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,0,0,3,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,254,0,0,31,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,248,0,0,127,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,224,0,1,255,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,192,0,15,255,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,0,0,63,255,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,252,0,0,127,255,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,240,0,1,255,255,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,224,0,7,255,255,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,128,0,31,255,255,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,63,255,254,0,0,63,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,252,0,0,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,240,0,1,255,255,255,224,31,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,192,0,7,255,255,254,0,7,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,128,0,15,255,255,240,0,3,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,63,255,254,0,0,31,255,255,192,0,3,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,255,255,248,0,0,127,255,255,128,0,7,255,255,248,0,0,0,0,0,0,0,0,0,0,0,3,255,255,224,0,0,255,255,254,0,0,7,255,255,240,0,0,0,0,0,0,0,0,0,0,0,7,255,255,192,0,1,255,255,252,0,0,7,255,255,224,0,0,0,0,0,0,0,0,0,0,0,31,255,255,0,0,7,255,255,240,0,0,15,255,255,224,0,0,0,0,0,0,0,0,0,0,0,127,255,252,0,0,15,255,255,224,0,0,31,255,255,192,0,0,0,0,0,0,0,0,0,0,0,255,255,248,0,0,31,255,255,128,0,0,31,255,255,128,0,0,0,0,0,0,0,0,0,0,3,255,255,224,0,0,63,255,255,0,0,0,63,255,255,0,0,0,0,0,0,0,0,0,0,0,15,255,255,128,0,0,127,255,254,0,0,0,127,255,254,0,0,0,0,0,0,0,0,0,0,0,63,255,254,0,0,0,255,255,252,0,0,0,255,255,252,0,0,0,0,0,0,0,0,0,0,0,127,255,252,0,0,3,255,255,248,0,0,0,255,255,252,0,0,0,0,0,0,0,0,0,0,1,255,255,240,0,0,7,255,255,224,0,0,1,255,255,248,0,0,0,0,0,0,0,0,0,0,7,255,255,192,0,0,15,255,255,192,0,0,3,255,255,240,0,0,0,0,0,0,0,0,0,0,15,255,255,128,0,0,31,255,255,128,0,0,7,255,255,224,0,0,0,0,0,0,0,0,0,0,63,255,254,0,0,0,63,255,255,0,0,0,15,255,255,192,0,0,0,0,0,0,0,0,0,0,255,255,248,0,0,0,127,255,254,0,0,0,31,255,255,128,0,0,0,0,0,0,0,0,0,1,255,255,240,0,0,0,255,255,252,0,0,0,63,255,255,0,0,0,0,0,0,0,0,0,0,7,255,255,192,0,0,1,255,255,248,0,0,0,127,255,254,0,0,0,0,0,0,0,0,0,0,31,255,255,0,0,0,3,255,255,240,0,0,0,255,255,252,0,0,0,0,0,0,0,0,0,0,127,255,252,0,0,0,7,255,255,224,0,0,1,255,255,248,0,0,0,0,0,0,0,0,0,0,255,255,248,0,0,0,15,255,255,192,0,0,3,255,255,240,0,0,0,0,0,0,0,0,0,3,255,255,224,0,0,0,31,255,255,128,0,0,7,255,255,224,0,0,0,0,0,0,0,0,0,15,255,255,128,0,0,0,63,255,255,0,0,0,15,255,255,192,0,0,0,0,0,0,0,0,0,31,255,255,0,0,0,0,127,255,254,0,0,0,31,255,255,128,0,0,0,0,0,0,0,0,0,127,255,252,0,0,0,0,255,255,252,0,0,0,63,255,255,0,0,0,0,0,0,0,0,0,1,255,255,240,0,0,0,1,255,255,248,0,0,0,127,255,254,0,0,0,0,0,0,0,0,0,3,255,255,224,0,0,0,3,255,255,240,0,0,0,255,255,252,0,0,0,0,0,0,0,0,0,15,255,255,128,0,0,0,7,255,255,240,0,0,3,255,255,248,0,0,0,0,0,0,0,0,0,63,255,254,0,0,0,0,7,255,255,224,0,0,7,255,255,224,0,0,0,0,0,0,0,0,0,255,255,248,0,0,0,0,15,255,255,192,0,0,15,255,255,192,0,0,0,0,0,0,0,0,1,255,255,240,0,0,0,0,31,255,255,128,0,0,31,255,255,128,0,0,0,0,0,0,0,0,7,255,255,192,0,0,0,0,63,255,255,0,0,0,63,255,255,0,0,0,0,0,0,0,0,0,31,255,255,0,0,0,0,0,127,255,255,0,0,0,255,255,254,0,0,0,0,0,0,0,0,0,63,255,254,0,0,0,0,0,255,255,254,0,0,1,255,255,252,0,0,0,0,0,0,0,0,0,255,255,248,0,0,0,0,0,255,255,254,0,0,3,255,255,240,0,0,0,0,0,0,0,0,3,255,255,224,0,0,0,0,1,255,255,252,0,0,15,255,255,224,0,0,0,0,0,0,0,0,7,255,255,192,0,0,0,0,3,255,255,252,0,0,31,255,255,192,0,0,0,0,0,0,0,0,31,255,255,0,0,0,0,0,3,255,255,248,0,0,127,255,255,0,0,0,0,0,0,0,0,0,127,255,252,0,0,0,0,0,7,255,255,248,0,1,255,255,254,0,0,0,0,0,0,0,0,1,255,255,240,0,0,0,0,0,15,255,255,252,0,15,255,255,252,0,0,0,0,0,0,0,0,3,255,255,224,0,0,0,0,0,15,255,255,255,0,255,255,255,240,0,0,0,0,0,0,0,0,15,255,255,128,0,0,0,0,0,31,255,255,255,255,255,255,255,224,0,0,0,0,0,0,0,0,63,255,254,0,0,0,0,0,0,31,255,255,255,255,255,255,255,128,0,0,0,0,0,0,0,0,127,255,252,0,0,0,0,0,0,31,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,1,255,255,240,0,0,0,0,0,0,63,255,255,255,255,255,255,252,0,0,0,0,0,0,0,0,7,255,255,192,0,0,0,0,0,0,63,255,255,255,255,255,255,240,0,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,63,255,255,255,255,255,255,224,0,0,0,0,0,0,0,0,63,255,254,0,0,0,0,0,0,0,63,255,255,255,255,255,255,128,0,0,0,0,0,0,0,0,255,255,248,0,0,0,0,0,0,0,63,255,255,255,255,255,254,0,0,0,0,0,0,0,0,3,255,255,224,0,0,0,0,0,0,0,63,255,255,255,255,255,248,0,0,0,0,0,0,0,0,7,255,255,192,0,0,0,0,0,0,0,63,255,255,255,255,255,192,0,0,0,0,0,0,0,0,31,255,255,0,0,0,0,0,0,0,0,63,255,255,255,255,255,0,0,0,0,0,0,0,0,0,127,255,252,0,0,0,0,0,0,0,0,31,255,255,255,255,248,0,0,0,0,0,0,0,0,0,255,255,248,0,0,0,0,0,0,0,0,15,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,255,240,0,0,0,0,0,0,0,0,15,255,255,255,224,0,0,0,0,31,255,255,255,192,0,0,0,0,63,255,255,255,128,0,0,0,0,127,255,255,255,0,0,0,0,0,255,255,255,254,0,0,0,0,1,255,255,255,252,0,0,0,0,3,255,255,255,248,0,0,0,0,7,255,255,255,240,0,0,0,0,15,255,255,255,224,0,0,0,0,31,255,255,255,192,0,0,0,0,63,255,255,255,128,0,0,0,0,127,255,255,255,0,0,0,0,0,255,255,255,254,0,0,0,0,1,255,255,255,252,0,0,0,0,3,255,255,255,248,0,0,0,0,7,255,255,255,240,0,0,0,0,15,255,255,255,224,0,0,0,0,31,255,255,255,192,0,0,0,0,63,255,255,255,128,0,0,0,0,127,255,255,255,0,0,0,0,0,255,255,255,254,0,0,0,0,1,255,255,255,252,0,0,0,0,3,255,255,255,248,0,0,0,0,7,255,255,255,240,0,0,0,0,15,255,255,255,224,0,0,0,0,31,255,255,255,192,0,0,0,0,63,255,255,255,128,0,0,0,0,127,255,255,255,0,0,0,0,0,255,255,255,254,0,0,0,0,1,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,192,0,0,0,0,63,255,255,255,128,0,0,0,0,127,255,255,255,0,0,0,0,0,255,255,255,254,0,0,0,0,1,255,255,255,252,0,0,0,0,3,255,255,255,248,0,0,0,0,7,255,255,255,240,0,0,0,0,15,255,255,255,224,0,0,0,0,31,255,255,255,192,0,0,0,0,63,255,255,255,128,0,0,0,0,127,255,255,255,0,0,0,0,0,255,255,255,254,0,0,0,0,1,255,255,255,252,0,0,0,0,3,255,255,255,248,0,0,0,0,7,255,255,255,240,0,0,0,0,15,255,255,255,224,0,0,0,0,31,255,255,255,192,0,0,0,0,63,255,255,255,128,0,0,0,0,127,255,255,255,0,0,0,0,0,255,255,255,254,0,0,0,0,1,255,255,255,252,0,0,0,0,3,255,255,255,248,0,0,0,0,7,255,255,255,240,0,0,0,0,15,255,255,255,224,0,0,0,0,31,255,255,255,192,0,0,0,0,63,255,255,255,128,0,0,0,0,127,255,255,255,0,0,0,0,0,255,255,255,254,0,0,0,0,1,255,255,255,252,0,0,0,0,3,255,255,255,248,0,0,0,0,255,255,255,255,255,255,252,0,3,255,255,255,255,255,255,240,0,15,255,255,255,255,255,255,192,0,63,255,255,255,255,255,255,0,0,255,255,255,255,255,255,252,0,3,255,255,255,255,255,255,240,0,15,255,255,255,255,255,255,192,0,63,255,255,255,255,255,255,0,0,255,255,255,255,255,255,252,0,3,255,255,255,255,255,255,240,0,15,255,255,255,255,255,255,192,0,63,255,255,255,255,255,255,0,0,255,255,255,255,255,255,252,0,3,255,255,255,255,255,255,240,0,15,255,255,255,255,255,255,192,0,63,255,255,255,255,255,255,0,0,255,255,255,255,255,255,252,0,3,255,255,255,255,255,255,240,0,15,255,255,255,255,255,255,192,0,63,255,255,255,255,255,255,0,0,255,255,255,255,255,255,252,0,3,255,255,255,255,255,255,240,0,15,255,255,255,255,255,255,192,0,63,255,255,255,255,255,255,0,0,255,255,255,255,255,255,252,0,3,255,255,255,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,0,0,0,0,63,255,255,255,128,0,0,0,31,255,255,255,192,0,0,0,15,255,255,255,224,0,0,0,7,255,255,255,240,0,0,0,3,255,255,255,248,0,0,0,1,255,255,255,252,0,0,0,0,255,255,255,254,0,0,0,0,127,255,255,255,0,0,0,0,63,255,255,255,128,0,0,0,31,255,255,255,192,0,0,0,15,255,255,255,224,0,0,0,7,255,255,255,240,0,0,0,3,255,255,255,248,0,0,0,1,255,255,255,252,0,0,0,0,255,255,255,254,0,0,0,0,127,255,255,255,0,0,0,0,63,255,255,255,128,0,0,0,31,255,255,255,192,0,0,0,15,255,255,255,224,0,0,0,7,255,255,255,240,0,0,0,3,255,255,255,248,0,0,0,1,255,255,255,252,0,0,0,0,255,255,255,254,0,0,0,0,127,255,255,255,0,0,0,0,63,255,255,255,128,0,0,0,31,255,255,255,192,0,0,0,15,255,255,255,224,0,0,0,7,255,255,255,240,0,0,0,3,255,255,255,248,0,0}; +static const Glyph f0_212_glyphs[]={{0,126,151,0,0,126,48},{2379,126,146,0,2,126,49},{4679,127,148,0,0,127,50},{7029,126,151,0,0,126,51},{9408,126,148,0,0,126,52},{11739,126,148,0,3,126,53},{14070,126,151,0,0,126,54},{16449,127,145,0,3,127,55},{18751,126,151,0,0,126,56},{21130,126,151,0,0,126,57},{23509,207,151,0,0,207,37},{27417,71,111,0,37,71,58},{28403,70,71,0,77,70,45},{29025,65,30,0,118,65,46},{29269,42,0,0,148,42,32}}; +static const uint8_t f0_236_bits[]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,255,254,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,127,255,255,255,255,255,255,224,0,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,128,0,0,0,0,0,0,0,0,7,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,240,0,0,0,0,0,0,0,0,63,255,255,255,255,255,255,255,255,192,0,0,0,0,0,0,0,7,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,240,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,0,3,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,224,0,0,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,255,255,248,0,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,192,0,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,224,0,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,248,0,0,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,255,192,0,0,0,0,7,255,255,255,255,255,128,15,255,255,255,255,254,0,0,0,0,0,127,255,255,255,255,128,0,31,255,255,255,255,224,0,0,0,0,15,255,255,255,255,224,0,0,63,255,255,255,255,0,0,0,0,0,255,255,255,255,248,0,0,1,255,255,255,255,240,0,0,0,0,31,255,255,255,255,0,0,0,7,255,255,255,255,128,0,0,0,1,255,255,255,255,192,0,0,0,63,255,255,255,248,0,0,0,0,63,255,255,255,252,0,0,0,1,255,255,255,255,192,0,0,0,3,255,255,255,255,128,0,0,0,31,255,255,255,252,0,0,0,0,63,255,255,255,240,0,0,0,0,255,255,255,255,192,0,0,0,7,255,255,255,254,0,0,0,0,7,255,255,255,254,0,0,0,0,127,255,255,255,224,0,0,0,0,127,255,255,255,224,0,0,0,7,255,255,255,252,0,0,0,0,3,255,255,255,254,0,0,0,0,255,255,255,255,192,0,0,0,0,63,255,255,255,240,0,0,0,15,255,255,255,248,0,0,0,0,1,255,255,255,255,0,0,0,0,255,255,255,255,128,0,0,0,0,31,255,255,255,240,0,0,0,15,255,255,255,240,0,0,0,0,1,255,255,255,255,0,0,0,1,255,255,255,255,0,0,0,0,0,15,255,255,255,248,0,0,0,31,255,255,255,240,0,0,0,0,0,255,255,255,255,128,0,0,1,255,255,255,255,0,0,0,0,0,15,255,255,255,248,0,0,0,31,255,255,255,224,0,0,0,0,0,127,255,255,255,128,0,0,3,255,255,255,254,0,0,0,0,0,7,255,255,255,252,0,0,0,63,255,255,255,224,0,0,0,0,0,127,255,255,255,192,0,0,3,255,255,255,254,0,0,0,0,0,7,255,255,255,252,0,0,0,63,255,255,255,192,0,0,0,0,0,127,255,255,255,192,0,0,3,255,255,255,252,0,0,0,0,0,3,255,255,255,252,0,0,0,127,255,255,255,192,0,0,0,0,0,63,255,255,255,224,0,0,7,255,255,255,252,0,0,0,0,0,3,255,255,255,254,0,0,0,127,255,255,255,192,0,0,0,0,0,63,255,255,255,224,0,0,7,255,255,255,252,0,0,0,0,0,3,255,255,255,254,0,0,0,127,255,255,255,192,0,0,0,0,0,63,255,255,255,224,0,0,7,255,255,255,248,0,0,0,0,0,3,255,255,255,254,0,0,0,255,255,255,255,128,0,0,0,0,0,31,255,255,255,240,0,0,15,255,255,255,248,0,0,0,0,0,1,255,255,255,255,0,0,0,255,255,255,255,128,0,0,0,0,0,31,255,255,255,240,0,0,15,255,255,255,248,0,0,0,0,0,1,255,255,255,255,0,0,0,255,255,255,255,128,0,0,0,0,0,31,255,255,255,240,0,0,15,255,255,255,248,0,0,0,0,0,1,255,255,255,255,0,0,0,255,255,255,255,128,0,0,0,0,0,31,255,255,255,240,0,0,15,255,255,255,248,0,0,0,0,0,1,255,255,255,255,0,0,0,255,255,255,255,128,0,0,0,0,0,31,255,255,255,240,0,0,15,255,255,255,248,0,0,0,0,0,1,255,255,255,255,0,0,0,255,255,255,255,128,0,0,0,0,0,31,255,255,255,240,0,0,31,255,255,255,248,0,0,0,0,0,1,255,255,255,255,128,0,1,255,255,255,255,128,0,0,0,0,0,31,255,255,255,248,0,0,31,255,255,255,248,0,0,0,0,0,1,255,255,255,255,128,0,1,255,255,255,255,128,0,0,0,0,0,31,255,255,255,248,0,0,31,255,255,255,248,0,0,0,0,0,1,255,255,255,255,128,0,1,255,255,255,255,128,0,0,0,0,0,31,255,255,255,248,0,0,31,255,255,255,248,0,0,0,0,0,1,255,255,255,255,128,0,1,255,255,255,255,128,0,0,0,0,0,31,255,255,255,248,0,0,31,255,255,255,248,0,0,0,0,0,1,255,255,255,255,128,0,1,255,255,255,255,128,0,0,0,0,0,31,255,255,255,248,0,0,31,255,255,255,248,0,0,0,0,0,1,255,255,255,255,128,0,1,255,255,255,255,128,0,0,0,0,0,31,255,255,255,248,0,0,31,255,255,255,248,0,0,0,0,0,1,255,255,255,255,128,0,1,255,255,255,255,128,0,0,0,0,0,31,255,255,255,248,0,0,31,255,255,255,248,0,0,0,0,0,1,255,255,255,255,128,0,1,255,255,255,255,128,0,0,0,0,0,31,255,255,255,248,0,0,31,255,255,255,248,0,0,0,0,0,1,255,255,255,255,128,0,1,255,255,255,255,128,0,0,0,0,0,31,255,255,255,248,0,0,31,255,255,255,248,0,0,0,0,0,1,255,255,255,255,128,0,1,255,255,255,255,128,0,0,0,0,0,31,255,255,255,248,0,0,31,255,255,255,248,0,0,0,0,0,1,255,255,255,255,128,0,1,255,255,255,255,128,0,0,0,0,0,31,255,255,255,248,0,0,31,255,255,255,248,0,0,0,0,0,1,255,255,255,255,128,0,1,255,255,255,255,128,0,0,0,0,0,31,255,255,255,248,0,0,31,255,255,255,248,0,0,0,0,0,1,255,255,255,255,128,0,1,255,255,255,255,128,0,0,0,0,0,31,255,255,255,248,0,0,31,255,255,255,248,0,0,0,0,0,1,255,255,255,255,128,0,1,255,255,255,255,128,0,0,0,0,0,31,255,255,255,248,0,0,31,255,255,255,248,0,0,0,0,0,1,255,255,255,255,128,0,1,255,255,255,255,128,0,0,0,0,0,31,255,255,255,248,0,0,31,255,255,255,248,0,0,0,0,0,1,255,255,255,255,128,0,1,255,255,255,255,128,0,0,0,0,0,31,255,255,255,248,0,0,31,255,255,255,248,0,0,0,0,0,1,255,255,255,255,0,0,0,255,255,255,255,128,0,0,0,0,0,31,255,255,255,240,0,0,15,255,255,255,248,0,0,0,0,0,1,255,255,255,255,0,0,0,255,255,255,255,128,0,0,0,0,0,31,255,255,255,240,0,0,15,255,255,255,248,0,0,0,0,0,1,255,255,255,255,0,0,0,255,255,255,255,128,0,0,0,0,0,31,255,255,255,240,0,0,15,255,255,255,248,0,0,0,0,0,1,255,255,255,255,0,0,0,255,255,255,255,128,0,0,0,0,0,31,255,255,255,240,0,0,15,255,255,255,248,0,0,0,0,0,1,255,255,255,255,0,0,0,255,255,255,255,128,0,0,0,0,0,31,255,255,255,240,0,0,15,255,255,255,248,0,0,0,0,0,1,255,255,255,255,0,0,0,127,255,255,255,128,0,0,0,0,0,31,255,255,255,224,0,0,7,255,255,255,252,0,0,0,0,0,3,255,255,255,254,0,0,0,127,255,255,255,192,0,0,0,0,0,63,255,255,255,224,0,0,7,255,255,255,252,0,0,0,0,0,3,255,255,255,254,0,0,0,127,255,255,255,192,0,0,0,0,0,63,255,255,255,224,0,0,7,255,255,255,252,0,0,0,0,0,3,255,255,255,254,0,0,0,127,255,255,255,192,0,0,0,0,0,63,255,255,255,224,0,0,3,255,255,255,252,0,0,0,0,0,3,255,255,255,252,0,0,0,63,255,255,255,224,0,0,0,0,0,127,255,255,255,192,0,0,3,255,255,255,254,0,0,0,0,0,7,255,255,255,252,0,0,0,63,255,255,255,224,0,0,0,0,0,127,255,255,255,192,0,0,3,255,255,255,254,0,0,0,0,0,7,255,255,255,252,0,0,0,31,255,255,255,224,0,0,0,0,0,127,255,255,255,128,0,0,1,255,255,255,255,0,0,0,0,0,15,255,255,255,248,0,0,0,31,255,255,255,240,0,0,0,0,0,255,255,255,255,128,0,0,1,255,255,255,255,0,0,0,0,0,15,255,255,255,248,0,0,0,15,255,255,255,248,0,0,0,0,1,255,255,255,255,0,0,0,0,255,255,255,255,128,0,0,0,0,31,255,255,255,240,0,0,0,15,255,255,255,248,0,0,0,0,1,255,255,255,255,0,0,0,0,255,255,255,255,192,0,0,0,0,63,255,255,255,240,0,0,0,7,255,255,255,252,0,0,0,0,3,255,255,255,254,0,0,0,0,127,255,255,255,224,0,0,0,0,127,255,255,255,224,0,0,0,7,255,255,255,254,0,0,0,0,7,255,255,255,254,0,0,0,0,63,255,255,255,240,0,0,0,0,255,255,255,255,192,0,0,0,3,255,255,255,255,128,0,0,0,31,255,255,255,252,0,0,0,0,31,255,255,255,252,0,0,0,3,255,255,255,255,128,0,0,0,1,255,255,255,255,224,0,0,0,63,255,255,255,248,0,0,0,0,31,255,255,255,255,0,0,0,15,255,255,255,255,128,0,0,0,0,255,255,255,255,248,0,0,1,255,255,255,255,240,0,0,0,0,15,255,255,255,255,224,0,0,127,255,255,255,255,0,0,0,0,0,127,255,255,255,255,128,0,31,255,255,255,255,224,0,0,0,0,7,255,255,255,255,255,192,63,255,255,255,255,254,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,255,192,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,224,0,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,31,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,1,255,255,255,255,255,255,255,255,255,255,248,0,0,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,224,0,0,0,0,0,0,3,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,240,0,0,0,0,0,0,0,3,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,255,128,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,240,0,0,0,0,0,0,0,0,3,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,0,0,15,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,240,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,255,255,224,0,0,0,0,0,0,0,0,0,0,127,255,255,255,255,255,254,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,255,255,224,0,0,0,0,0,0,0,0,0,127,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,224,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,255,224,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,255,224,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,255,224,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,255,224,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,255,224,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,255,224,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,255,224,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,255,224,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,255,224,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,0,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,15,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,0,0,63,255,255,255,255,255,255,255,248,0,0,0,0,0,0,0,0,7,255,255,255,255,255,255,255,255,240,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,192,0,0,0,0,0,0,0,15,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,255,255,248,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,255,255,224,0,0,0,0,0,0,31,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,248,0,0,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,255,192,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,248,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,7,255,255,255,255,255,240,0,255,255,255,255,255,252,0,0,0,0,63,255,255,255,255,240,0,0,127,255,255,255,255,224,0,0,0,3,255,255,255,255,254,0,0,0,255,255,255,255,255,128,0,0,0,31,255,255,255,255,192,0,0,1,255,255,255,255,252,0,0,0,1,255,255,255,255,248,0,0,0,7,255,255,255,255,224,0,0,0,15,255,255,255,255,128,0,0,0,31,255,255,255,255,128,0,0,0,127,255,255,255,248,0,0,0,0,127,255,255,255,252,0,0,0,7,255,255,255,255,128,0,0,0,1,255,255,255,255,224,0,0,0,63,255,255,255,248,0,0,0,0,7,255,255,255,255,128,0,0,1,255,255,255,255,128,0,0,0,0,31,255,255,255,252,0,0,0,15,255,255,255,248,0,0,0,0,0,255,255,255,255,224,0,0,0,255,255,255,255,192,0,0,0,0,3,255,255,255,255,0,0,0,7,255,255,255,252,0,0,0,0,0,31,255,255,255,252,0,0,0,63,255,255,255,224,0,0,0,0,0,127,255,255,255,224,0,0,1,255,255,255,255,0,0,0,0,0,3,255,255,255,255,0,0,0,15,255,255,255,240,0,0,0,0,0,31,255,255,255,248,0,0,0,255,255,255,255,128,0,0,0,0,0,127,255,255,255,192,0,0,7,255,255,255,252,0,0,0,0,0,3,255,255,255,254,0,0,0,63,255,255,255,224,0,0,0,0,0,31,255,255,255,240,0,0,1,255,255,255,254,0,0,0,0,0,0,255,255,255,255,128,0,0,15,255,255,255,240,0,0,0,0,0,7,255,255,255,252,0,0,0,127,255,255,255,128,0,0,0,0,0,63,255,255,255,240,0,0,3,255,255,255,252,0,0,0,0,0,1,255,255,255,255,0,0,0,31,255,255,255,224,0,0,0,0,0,15,255,255,255,248,0,0,0,255,255,255,255,0,0,0,0,0,0,127,255,255,255,192,0,0,7,255,255,255,248,0,0,0,0,0,3,255,255,255,254,0,0,0,63,255,255,255,192,0,0,0,0,0,63,255,255,255,240,0,0,1,255,255,255,254,0,0,0,0,0,1,255,255,255,255,128,0,0,15,255,255,255,240,0,0,0,0,0,15,255,255,255,252,0,0,0,127,255,255,255,128,0,0,0,0,0,127,255,255,255,224,0,0,3,255,255,255,252,0,0,0,0,0,7,255,255,255,255,0,0,0,31,255,255,255,224,0,0,0,0,0,63,255,255,255,248,0,0,0,127,255,255,255,0,0,0,0,0,1,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,255,224,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,255,224,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,15,255,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,255,255,240,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,255,224,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,255,224,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,192,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,255,255,240,0,0,3,255,255,255,255,255,255,255,255,255,255,255,255,255,255,128,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,255,252,0,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,15,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,255,255,248,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,255,255,192,0,0,63,255,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,255,240,0,0,15,255,255,255,255,255,255,255,255,255,255,255,255,255,255,128,0,0,127,255,255,255,255,255,255,255,255,255,255,255,255,255,252,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,63,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,255,248,0,0,15,255,255,255,255,255,255,255,255,255,255,255,255,255,255,192,0,0,127,255,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,3,255,255,255,255,255,255,255,255,255,255,255,255,255,255,240,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,255,255,128,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,252,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,63,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,255,248,0,0,15,255,255,255,255,255,255,255,255,255,255,255,255,255,255,192,0,0,127,255,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,3,255,255,255,255,255,255,255,255,255,255,255,255,255,255,240,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,255,255,128,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,255,255,240,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,224,0,0,0,0,0,0,0,0,0,3,255,255,255,255,255,255,255,224,0,0,0,0,0,0,0,0,0,127,255,255,255,255,255,255,255,192,0,0,0,0,0,0,0,0,15,255,255,255,255,255,255,255,255,128,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,3,255,255,255,255,255,255,255,255,255,248,0,0,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,224,0,0,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,248,0,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,224,0,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,254,0,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,255,248,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,0,15,255,255,255,255,255,128,7,255,255,255,255,255,240,0,0,0,0,127,255,255,255,255,192,0,3,255,255,255,255,255,192,0,0,0,7,255,255,255,255,240,0,0,3,255,255,255,255,254,0,0,0,0,63,255,255,255,254,0,0,0,15,255,255,255,255,240,0,0,0,3,255,255,255,255,224,0,0,0,31,255,255,255,255,192,0,0,0,31,255,255,255,254,0,0,0,0,127,255,255,255,254,0,0,0,0,255,255,255,255,192,0,0,0,1,255,255,255,255,240,0,0,0,15,255,255,255,254,0,0,0,0,7,255,255,255,255,192,0,0,0,127,255,255,255,224,0,0,0,0,31,255,255,255,254,0,0,0,3,255,255,255,254,0,0,0,0,0,127,255,255,255,240,0,0,0,31,255,255,255,224,0,0,0,0,3,255,255,255,255,128,0,0,0,255,255,255,255,0,0,0,0,0,15,255,255,255,252,0,0,0,15,255,255,255,248,0,0,0,0,0,127,255,255,255,240,0,0,0,127,255,255,255,128,0,0,0,0,3,255,255,255,255,128,0,0,3,255,255,255,252,0,0,0,0,0,15,255,255,255,252,0,0,0,31,255,255,255,224,0,0,0,0,0,127,255,255,255,224,0,0,0,255,255,255,254,0,0,0,0,0,3,255,255,255,255,0,0,0,7,255,255,255,240,0,0,0,0,0,31,255,255,255,248,0,0,0,63,255,255,255,128,0,0,0,0,0,255,255,255,255,192,0,0,1,255,255,255,252,0,0,0,0,0,7,255,255,255,254,0,0,0,15,255,255,255,224,0,0,0,0,0,63,255,255,255,240,0,0,0,127,255,255,255,0,0,0,0,0,1,255,255,255,255,128,0,0,3,255,255,255,248,0,0,0,0,0,15,255,255,255,252,0,0,0,31,255,255,255,192,0,0,0,0,0,127,255,255,255,224,0,0,0,255,255,255,254,0,0,0,0,0,3,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,252,0,0,0,0,0,0,0,0,0,7,255,255,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,63,255,255,255,255,255,255,252,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,15,255,255,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,127,255,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0,3,255,255,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,240,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,252,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,255,255,240,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,15,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,127,255,255,255,255,255,255,252,0,0,0,0,0,0,0,0,0,3,255,255,255,255,255,255,255,248,0,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,224,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,128,0,0,0,0,0,0,0,0,7,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,0,0,3,255,255,255,252,0,0,0,0,0,0,15,255,255,255,248,0,0,31,255,255,255,224,0,0,0,0,0,0,127,255,255,255,192,0,0,255,255,255,255,0,0,0,0,0,0,3,255,255,255,254,0,0,7,255,255,255,248,0,0,0,0,0,0,31,255,255,255,240,0,0,63,255,255,255,192,0,0,0,0,0,0,255,255,255,255,128,0,1,255,255,255,254,0,0,0,0,0,0,7,255,255,255,254,0,0,15,255,255,255,240,0,0,0,0,0,0,63,255,255,255,224,0,0,127,255,255,255,128,0,0,0,0,0,1,255,255,255,255,0,0,3,255,255,255,252,0,0,0,0,0,0,15,255,255,255,248,0,0,31,255,255,255,224,0,0,0,0,0,0,255,255,255,255,192,0,0,255,255,255,255,128,0,0,0,0,0,7,255,255,255,254,0,0,3,255,255,255,252,0,0,0,0,0,0,63,255,255,255,240,0,0,31,255,255,255,224,0,0,0,0,0,3,255,255,255,255,128,0,0,255,255,255,255,128,0,0,0,0,0,31,255,255,255,252,0,0,7,255,255,255,252,0,0,0,0,0,0,255,255,255,255,224,0,0,63,255,255,255,240,0,0,0,0,0,15,255,255,255,255,0,0,0,255,255,255,255,128,0,0,0,0,0,127,255,255,255,240,0,0,7,255,255,255,254,0,0,0,0,0,7,255,255,255,255,128,0,0,63,255,255,255,248,0,0,0,0,0,127,255,255,255,252,0,0,1,255,255,255,255,224,0,0,0,0,7,255,255,255,255,192,0,0,7,255,255,255,255,128,0,0,0,0,127,255,255,255,254,0,0,0,63,255,255,255,254,0,0,0,0,7,255,255,255,255,240,0,0,0,255,255,255,255,248,0,0,0,0,127,255,255,255,255,0,0,0,7,255,255,255,255,240,0,0,0,15,255,255,255,255,248,0,0,0,63,255,255,255,255,224,0,0,1,255,255,255,255,255,128,0,0,0,255,255,255,255,255,224,0,0,255,255,255,255,255,252,0,0,0,7,255,255,255,255,255,252,0,255,255,255,255,255,255,192,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,3,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,248,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,248,0,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,31,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,0,0,3,255,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,7,255,255,255,255,255,255,255,255,255,224,0,0,0,0,0,0,0,15,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,0,63,255,255,255,255,255,255,255,255,128,0,0,0,0,0,0,0,0,127,255,255,255,255,255,255,255,240,0,0,0,0,0,0,0,0,0,127,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,240,0,1,255,252,0,0,0,0,0,0,0,0,1,255,255,255,255,128,0,15,255,224,0,0,0,0,0,0,0,0,15,255,255,255,248,0,0,255,255,0,0,0,0,0,0,0,0,0,127,255,255,255,192,0,7,255,248,0,0,0,0,0,0,0,0,7,255,255,255,252,0,0,63,255,192,0,0,0,0,0,0,0,0,63,255,255,255,224,0,3,255,254,0,0,0,0,0,0,0,0,1,255,255,255,255,0,0,31,255,240,0,0,0,0,0,0,0,0,31,255,255,255,240,0,0,255,255,128,0,0,0,0,0,0,0,0,255,255,255,255,128,0,15,255,252,0,0,0,0,0,0,0,0,7,255,255,255,248,0,0,127,255,224,0,0,0,0,0,0,0,0,127,255,255,255,192,0,3,255,255,0,0,0,0,0,0,0,0,3,255,255,255,254,0,0,63,255,248,0,0,0,0,0,0,0,0,63,255,255,255,224,0,1,255,255,192,0,0,0,0,0,0,0,1,255,255,255,255,0,0,15,255,254,0,0,0,0,0,0,0,0,15,255,255,255,240,0,0,255,255,240,0,0,0,0,0,0,0,0,255,255,255,255,128,0,7,255,255,128,0,0,0,0,0,0,0,7,255,255,255,252,0,0,127,255,252,0,0,0,0,0,0,0,0,63,255,255,255,192,0,3,255,255,224,0,0,0,0,0,0,0,3,255,255,255,254,0,0,31,255,255,0,0,0,0,0,0,0,0,31,255,255,255,224,0,1,255,255,248,0,0,0,0,0,0,0,1,255,255,255,255,0,0,15,255,255,192,0,0,0,0,0,0,0,15,255,255,255,240,0,0,255,255,254,0,0,0,0,0,0,0,0,127,255,255,255,128,0,7,255,255,240,0,0,0,0,0,0,0,7,255,255,255,248,0,0,63,255,255,128,0,0,0,0,0,0,0,63,255,255,255,192,0,3,255,255,252,0,0,0,0,0,0,0,3,255,255,255,252,0,0,31,255,255,224,0,0,0,0,0,0,0,31,255,255,255,224,0,1,255,255,255,0,0,0,0,0,0,0,0,255,255,255,255,0,0,15,255,255,248,0,0,0,0,0,0,0,15,255,255,255,240,0,0,255,255,255,192,0,0,0,0,0,0,0,127,255,255,255,128,0,7,255,255,254,0,0,0,0,0,0,0,7,255,255,255,248,0,0,127,255,255,240,0,0,0,0,0,0,0,63,255,255,255,192,0,3,255,255,255,128,0,0,0,0,0,0,3,255,255,255,252,0,0,31,255,255,252,0,0,0,0,0,0,0,31,255,255,255,224,0,1,255,255,255,224,0,0,0,0,0,0,0,255,255,255,254,0,0,15,255,255,255,0,0,0,0,0,0,0,15,255,255,255,240,0,0,255,255,255,248,0,0,0,0,0,0,0,127,255,255,255,0,0,7,255,255,255,192,0,0,0,0,0,0,7,255,255,255,248,0,0,127,255,255,254,0,0,0,0,0,0,0,63,255,255,255,128,0,3,255,255,255,240,0,0,0,0,0,0,3,255,255,255,248,0,0,63,255,255,255,128,0,0,0,0,0,0,31,255,255,255,192,0,1,255,255,255,252,0,0,0,0,0,0,1,255,255,255,252,0,0,31,255,255,255,224,0,0,0,0,0,0,15,255,255,255,224,0,0,255,255,255,255,0,0,0,0,0,0,0,255,255,255,254,0,0,7,255,255,255,248,0,0,0,0,0,0,7,255,255,255,240,0,0,63,255,255,255,192,0,0,0,0,0,0,127,255,255,255,0,0,1,255,255,255,254,0,0,0,0,0,0,3,255,255,255,248,0,0,15,255,255,255,240,0,0,0,0,0,0,31,255,255,255,128,0,0,127,255,255,255,128,0,0,0,0,0,1,255,255,255,252,0,0,3,255,255,255,252,0,0,0,0,0,0,15,255,255,255,192,0,0,31,255,255,255,224,0,0,0,0,0,0,255,255,255,254,0,0,0,255,255,255,255,0,0,0,0,0,0,7,255,255,255,224,0,0,7,255,255,255,248,0,0,0,0,0,0,127,255,255,254,0,0,0,63,255,255,255,192,0,0,0,0,0,3,255,255,255,240,0,0,1,255,255,255,254,0,0,0,0,0,0,63,255,255,255,0,0,0,15,255,255,255,240,0,0,0,0,0,1,255,255,255,248,0,0,0,127,255,255,255,128,0,0,0,0,0,31,255,255,255,128,0,0,3,255,255,255,252,0,0,0,0,0,1,255,255,255,248,0,0,0,31,255,255,255,224,0,0,0,0,0,15,255,255,255,192,0,0,0,255,255,255,255,0,0,0,0,0,0,255,255,255,252,0,0,0,7,255,255,255,248,0,0,0,0,0,7,255,255,255,224,0,0,0,63,255,255,255,192,0,0,0,0,0,127,255,255,254,0,0,0,1,255,255,255,254,0,0,0,0,0,3,255,255,255,224,0,0,0,15,255,255,255,240,0,0,0,0,0,63,255,255,255,0,0,0,0,127,255,255,255,128,0,0,0,0,1,255,255,255,240,0,0,0,3,255,255,255,252,0,0,0,0,0,31,255,255,255,128,0,0,0,31,255,255,255,224,0,0,0,0,0,255,255,255,248,0,0,0,0,255,255,255,255,0,0,0,0,0,15,255,255,255,128,0,0,0,7,255,255,255,248,0,0,0,0,0,255,255,255,252,0,0,0,0,63,255,255,255,192,0,0,0,0,7,255,255,255,192,0,0,0,1,255,255,255,254,0,0,0,0,0,127,255,255,252,0,0,0,0,15,255,255,255,240,0,0,0,0,3,255,255,255,224,0,0,0,0,127,255,255,255,128,0,0,0,0,63,255,255,254,0,0,0,0,3,255,255,255,252,0,0,0,0,1,255,255,255,224,0,0,0,0,31,255,255,255,224,0,0,0,0,31,255,255,254,0,0,0,0,0,255,255,255,255,0,0,0,0,1,255,255,255,240,0,0,0,0,7,255,255,255,248,0,0,0,0,15,255,255,255,0,0,0,0,0,63,255,255,255,192,0,0,0,0,255,255,255,240,0,0,0,0,1,255,255,255,254,0,0,0,0,7,255,255,255,0,0,0,0,0,15,255,255,255,240,0,0,0,0,127,255,255,248,0,0,0,0,0,127,255,255,255,128,0,0,0,7,255,255,255,128,0,0,0,0,3,255,255,255,252,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,248,0,15,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,192,0,127,255,255,255,255,255,255,255,255,255,255,255,255,255,255,254,0,3,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,240,0,31,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,128,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,252,0,7,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,63,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,248,0,15,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,192,0,127,255,255,255,255,255,255,255,255,255,255,255,255,255,255,254,0,3,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,240,0,31,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,128,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,252,0,7,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,63,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,248,0,15,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,192,0,127,255,255,255,255,255,255,255,255,255,255,255,255,255,255,254,0,3,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,240,0,31,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,128,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,252,0,7,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,63,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,7,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,240,0,0,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,0,0,1,255,254,0,0,0,0,0,0,0,0,3,255,255,255,240,0,3,255,255,255,128,0,0,0,0,0,0,0,63,255,255,255,0,3,255,255,255,255,0,0,0,0,0,0,0,3,255,255,255,224,1,255,255,255,255,254,0,0,0,0,0,0,0,63,255,255,254,0,127,255,255,255,255,252,0,0,0,0,0,0,3,255,255,255,224,63,255,255,255,255,255,240,0,0,0,0,0,0,63,255,255,254,15,255,255,255,255,255,255,128,0,0,0,0,0,3,255,255,255,225,255,255,255,255,255,255,254,0,0,0,0,0,0,63,255,255,254,127,255,255,255,255,255,255,248,0,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,255,192,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,254,0,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,248,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,192,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,248,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,192,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,248,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,255,192,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,15,255,255,255,255,255,252,3,255,255,255,255,255,254,0,0,0,0,255,255,255,255,255,248,0,3,255,255,255,255,255,240,0,0,0,15,255,255,255,255,254,0,0,7,255,255,255,255,255,0,0,0,0,255,255,255,255,255,0,0,0,31,255,255,255,255,248,0,0,0,15,255,255,255,255,224,0,0,0,127,255,255,255,255,128,0,0,0,255,255,255,255,248,0,0,0,3,255,255,255,255,252,0,0,0,15,255,255,255,255,0,0,0,0,31,255,255,255,255,192,0,0,0,255,255,255,255,224,0,0,0,0,127,255,255,255,252,0,0,0,15,255,255,255,252,0,0,0,0,7,255,255,255,255,192,0,0,0,255,255,255,255,128,0,0,0,0,63,255,255,255,254,0,0,0,0,15,255,255,240,0,0,0,0,1,255,255,255,255,224,0,0,0,0,0,255,254,0,0,0,0,0,31,255,255,255,254,0,0,0,0,0,0,15,192,0,0,0,0,0,255,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,248,0,0,63,255,255,255,192,0,0,0,0,0,1,255,255,255,255,128,0,3,255,255,255,252,0,0,0,0,0,0,31,255,255,255,248,0,0,63,255,255,255,192,0,0,0,0,0,1,255,255,255,255,128,0,3,255,255,255,252,0,0,0,0,0,0,31,255,255,255,248,0,0,63,255,255,255,192,0,0,0,0,0,1,255,255,255,255,128,0,3,255,255,255,252,0,0,0,0,0,0,31,255,255,255,248,0,0,63,255,255,255,224,0,0,0,0,0,1,255,255,255,255,0,0,3,255,255,255,254,0,0,0,0,0,0,63,255,255,255,240,0,0,31,255,255,255,224,0,0,0,0,0,3,255,255,255,255,0,0,1,255,255,255,254,0,0,0,0,0,0,63,255,255,255,240,0,0,31,255,255,255,240,0,0,0,0,0,3,255,255,255,255,0,0,1,255,255,255,255,0,0,0,0,0,0,127,255,255,255,240,0,0,31,255,255,255,240,0,0,0,0,0,7,255,255,255,254,0,0,1,255,255,255,255,128,0,0,0,0,0,255,255,255,255,224,0,0,15,255,255,255,248,0,0,0,0,0,15,255,255,255,254,0,0,0,255,255,255,255,192,0,0,0,0,1,255,255,255,255,224,0,0,15,255,255,255,252,0,0,0,0,0,31,255,255,255,252,0,0,0,127,255,255,255,224,0,0,0,0,3,255,255,255,255,192,0,0,7,255,255,255,255,0,0,0,0,0,127,255,255,255,252,0,0,0,127,255,255,255,248,0,0,0,0,15,255,255,255,255,128,0,0,3,255,255,255,255,192,0,0,0,0,255,255,255,255,248,0,0,0,63,255,255,255,254,0,0,0,0,31,255,255,255,255,0,0,0,3,255,255,255,255,240,0,0,0,7,255,255,255,255,240,0,0,0,31,255,255,255,255,128,0,0,0,255,255,255,255,255,0,0,0,1,255,255,255,255,254,0,0,0,63,255,255,255,255,224,0,0,0,15,255,255,255,255,248,0,0,15,255,255,255,255,254,0,0,0,0,255,255,255,255,255,240,0,3,255,255,255,255,255,192,0,0,0,7,255,255,255,255,255,248,7,255,255,255,255,255,248,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,3,255,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,1,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,0,0,15,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,255,240,0,0,0,0,0,0,0,7,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,0,63,255,255,255,255,255,255,255,255,128,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,224,0,0,0,0,0,0,0,0,3,255,255,255,255,255,255,255,248,0,0,0,0,0,0,0,0,0,15,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,255,240,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,255,240,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,255,255,224,0,0,0,0,0,0,0,0,0,7,255,255,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,128,0,0,0,0,0,0,0,0,15,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,255,240,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,255,192,0,0,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,3,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,240,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,255,255,192,0,0,0,0,0,0,31,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,31,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,248,0,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,255,192,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,248,0,0,0,0,0,63,255,255,255,255,240,1,255,255,255,255,255,224,0,0,0,0,3,255,255,255,255,248,0,0,255,255,255,255,255,0,0,0,0,0,63,255,255,255,255,0,0,1,255,255,255,255,252,0,0,0,0,1,255,255,255,255,224,0,0,3,255,255,255,255,224,0,0,0,0,15,255,255,255,254,0,0,0,15,255,255,255,255,128,0,0,0,0,255,255,255,255,192,0,0,0,63,255,255,255,252,0,0,0,0,7,255,255,255,254,0,0,0,0,255,255,255,255,224,0,0,0,0,127,255,255,255,224,0,0,0,3,255,255,255,255,128,0,0,0,3,255,255,255,254,0,0,0,0,15,255,255,255,252,0,0,0,0,63,255,255,255,224,0,0,0,0,63,255,255,255,224,0,0,0,1,255,255,255,255,0,0,0,0,1,255,255,255,255,0,0,0,0,15,255,255,255,240,0,0,0,0,7,255,255,255,252,0,0,0,0,255,255,255,255,128,0,0,0,0,63,255,255,255,224,0,0,0,7,255,255,255,248,0,0,0,0,1,255,255,255,255,0,0,0,0,63,255,255,255,192,0,0,0,0,7,255,255,255,248,0,0,0,1,255,255,255,252,0,0,0,0,0,63,255,255,255,192,0,0,0,31,255,255,255,224,0,0,0,0,1,255,255,255,254,0,0,0,0,255,255,255,255,0,0,0,0,0,15,255,255,255,248,0,0,0,7,255,255,255,240,0,0,0,0,0,127,255,255,255,192,0,0,0,127,255,255,255,128,0,0,0,0,1,255,255,255,254,0,0,0,3,255,255,255,252,0,0,0,0,0,15,255,255,255,240,0,0,0,31,255,255,255,224,0,0,0,0,0,127,255,255,255,128,0,0,0,255,255,255,254,0,0,0,0,0,3,255,255,255,252,0,0,0,15,255,255,255,240,0,0,0,0,0,31,255,255,255,224,0,0,0,127,255,255,255,128,0,0,0,0,0,255,255,255,255,0,0,0,3,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,254,0,0,0,63,255,192,0,0,0,0,0,0,0,31,255,255,255,240,0,0,63,255,255,248,0,0,0,0,0,0,0,255,255,255,255,128,0,15,255,255,255,252,0,0,0,0,0,0,7,255,255,255,252,0,3,255,255,255,255,252,0,0,0,0,0,0,63,255,255,255,224,0,127,255,255,255,255,248,0,0,0,0,0,1,255,255,255,255,0,31,255,255,255,255,255,240,0,0,0,0,0,15,255,255,255,248,3,255,255,255,255,255,255,224,0,0,0,0,0,127,255,255,255,192,63,255,255,255,255,255,255,192,0,0,0,0,3,255,255,255,254,7,255,255,255,255,255,255,255,0,0,0,0,0,31,255,255,255,240,127,255,255,255,255,255,255,254,0,0,0,0,0,255,255,255,255,143,255,255,255,255,255,255,255,248,0,0,0,0,15,255,255,255,252,255,255,255,255,255,255,255,255,224,0,0,0,0,127,255,255,255,239,255,255,255,255,255,255,255,255,128,0,0,0,3,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,248,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,63,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,248,0,0,0,15,255,255,255,255,255,255,255,255,255,255,255,255,255,192,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,3,255,255,255,255,255,255,255,255,255,255,255,255,255,252,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,128,0,0,7,255,255,255,255,255,255,240,1,255,255,255,255,255,252,0,0,0,63,255,255,255,255,255,248,0,0,255,255,255,255,255,240,0,0,1,255,255,255,255,255,255,0,0,0,255,255,255,255,255,128,0,0,15,255,255,255,255,255,224,0,0,3,255,255,255,255,254,0,0,0,127,255,255,255,255,252,0,0,0,7,255,255,255,255,240,0,0,3,255,255,255,255,255,192,0,0,0,31,255,255,255,255,192,0,0,31,255,255,255,255,252,0,0,0,0,127,255,255,255,254,0,0,0,255,255,255,255,255,192,0,0,0,1,255,255,255,255,240,0,0,7,255,255,255,255,252,0,0,0,0,7,255,255,255,255,192,0,0,63,255,255,255,255,192,0,0,0,0,31,255,255,255,254,0,0,1,255,255,255,255,254,0,0,0,0,0,255,255,255,255,240,0,0,15,255,255,255,255,224,0,0,0,0,3,255,255,255,255,128,0,0,127,255,255,255,255,0,0,0,0,0,31,255,255,255,254,0,0,3,255,255,255,255,240,0,0,0,0,0,127,255,255,255,240,0,0,31,255,255,255,255,128,0,0,0,0,3,255,255,255,255,128,0,0,255,255,255,255,248,0,0,0,0,0,15,255,255,255,252,0,0,7,255,255,255,255,192,0,0,0,0,0,127,255,255,255,240,0,0,63,255,255,255,254,0,0,0,0,0,3,255,255,255,255,128,0,0,255,255,255,255,224,0,0,0,0,0,15,255,255,255,252,0,0,7,255,255,255,255,0,0,0,0,0,0,127,255,255,255,224,0,0,63,255,255,255,248,0,0,0,0,0,3,255,255,255,255,0,0,1,255,255,255,255,192,0,0,0,0,0,31,255,255,255,248,0,0,15,255,255,255,254,0,0,0,0,0,0,127,255,255,255,192,0,0,127,255,255,255,224,0,0,0,0,0,3,255,255,255,254,0,0,3,255,255,255,255,0,0,0,0,0,0,31,255,255,255,240,0,0,31,255,255,255,248,0,0,0,0,0,0,255,255,255,255,128,0,0,255,255,255,255,192,0,0,0,0,0,7,255,255,255,252,0,0,7,255,255,255,254,0,0,0,0,0,0,63,255,255,255,224,0,0,31,255,255,255,240,0,0,0,0,0,1,255,255,255,255,128,0,0,255,255,255,255,128,0,0,0,0,0,15,255,255,255,248,0,0,7,255,255,255,252,0,0,0,0,0,0,127,255,255,255,192,0,0,63,255,255,255,224,0,0,0,0,0,3,255,255,255,254,0,0,1,255,255,255,255,0,0,0,0,0,0,31,255,255,255,240,0,0,15,255,255,255,248,0,0,0,0,0,0,255,255,255,255,128,0,0,63,255,255,255,224,0,0,0,0,0,7,255,255,255,252,0,0,1,255,255,255,255,0,0,0,0,0,0,127,255,255,255,224,0,0,15,255,255,255,248,0,0,0,0,0,3,255,255,255,255,0,0,0,127,255,255,255,192,0,0,0,0,0,31,255,255,255,248,0,0,3,255,255,255,254,0,0,0,0,0,0,255,255,255,255,192,0,0,15,255,255,255,240,0,0,0,0,0,7,255,255,255,254,0,0,0,127,255,255,255,192,0,0,0,0,0,127,255,255,255,240,0,0,3,255,255,255,254,0,0,0,0,0,3,255,255,255,255,0,0,0,31,255,255,255,240,0,0,0,0,0,31,255,255,255,248,0,0,0,127,255,255,255,192,0,0,0,0,1,255,255,255,255,192,0,0,3,255,255,255,254,0,0,0,0,0,15,255,255,255,254,0,0,0,31,255,255,255,248,0,0,0,0,0,255,255,255,255,224,0,0,0,127,255,255,255,192,0,0,0,0,7,255,255,255,255,0,0,0,3,255,255,255,255,0,0,0,0,0,127,255,255,255,248,0,0,0,31,255,255,255,252,0,0,0,0,7,255,255,255,255,128,0,0,0,127,255,255,255,224,0,0,0,0,63,255,255,255,252,0,0,0,3,255,255,255,255,128,0,0,0,3,255,255,255,255,224,0,0,0,15,255,255,255,254,0,0,0,0,63,255,255,255,254,0,0,0,0,127,255,255,255,248,0,0,0,3,255,255,255,255,240,0,0,0,1,255,255,255,255,240,0,0,0,127,255,255,255,255,0,0,0,0,15,255,255,255,255,192,0,0,7,255,255,255,255,248,0,0,0,0,127,255,255,255,255,128,0,0,255,255,255,255,255,128,0,0,0,1,255,255,255,255,255,128,0,63,255,255,255,255,252,0,0,0,0,7,255,255,255,255,255,224,31,255,255,255,255,255,192,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,240,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,255,240,0,0,0,0,0,0,0,7,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,255,224,0,0,0,0,0,0,0,0,63,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,0,0,127,255,255,255,255,255,255,255,192,0,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,248,0,0,0,0,0,0,0,0,0,3,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,252,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,255,255,248,0,0,63,255,255,255,255,255,255,255,255,255,255,255,255,255,255,192,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,15,255,255,255,255,255,255,255,255,255,255,255,255,255,255,240,0,0,127,255,255,255,255,255,255,255,255,255,255,255,255,255,255,128,0,3,255,255,255,255,255,255,255,255,255,255,255,255,255,255,252,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,255,255,248,0,0,63,255,255,255,255,255,255,255,255,255,255,255,255,255,255,192,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,15,255,255,255,255,255,255,255,255,255,255,255,255,255,255,240,0,0,127,255,255,255,255,255,255,255,255,255,255,255,255,255,255,128,0,3,255,255,255,255,255,255,255,255,255,255,255,255,255,255,252,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,255,255,248,0,0,63,255,255,255,255,255,255,255,255,255,255,255,255,255,255,192,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,255,252,0,0,15,255,255,255,255,255,255,255,255,255,255,255,255,255,255,192,0,0,127,255,255,255,255,255,255,255,255,255,255,255,255,255,252,0,0,3,255,255,255,255,255,255,255,255,255,255,255,255,255,255,192,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,63,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,15,255,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0,7,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,248,0,0,0,0,0,0,0,0,0,127,255,255,255,255,255,255,255,224,0,0,0,0,0,0,0,0,15,255,255,255,255,255,255,255,255,128,0,0,0,0,0,0,0,3,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,240,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,255,255,192,0,0,0,0,0,0,3,255,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,240,0,0,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,1,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,224,0,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,248,0,0,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,192,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,255,255,255,255,255,254,3,255,255,255,255,255,248,0,0,0,0,31,255,255,255,255,248,0,1,255,255,255,255,255,192,0,0,0,1,255,255,255,255,254,0,0,3,255,255,255,255,252,0,0,0,0,63,255,255,255,255,128,0,0,15,255,255,255,255,224,0,0,0,3,255,255,255,255,224,0,0,0,63,255,255,255,254,0,0,0,0,127,255,255,255,252,0,0,0,1,255,255,255,255,224,0,0,0,7,255,255,255,255,128,0,0,0,15,255,255,255,255,0,0,0,0,127,255,255,255,240,0,0,0,0,127,255,255,255,240,0,0,0,7,255,255,255,254,0,0,0,0,3,255,255,255,255,0,0,0,0,255,255,255,255,192,0,0,0,0,31,255,255,255,248,0,0,0,15,255,255,255,252,0,0,0,0,1,255,255,255,255,128,0,0,0,255,255,255,255,128,0,0,0,0,15,255,255,255,248,0,0,0,15,255,255,255,248,0,0,0,0,0,255,255,255,255,128,0,0,0,255,255,255,255,0,0,0,0,0,7,255,255,255,248,0,0,0,31,255,255,255,240,0,0,0,0,0,127,255,255,255,192,0,0,1,255,255,255,255,0,0,0,0,0,7,255,255,255,252,0,0,0,31,255,255,255,240,0,0,0,0,0,127,255,255,255,192,0,0,1,255,255,255,254,0,0,0,0,0,3,255,255,255,252,0,0,0,31,255,255,255,224,0,0,0,0,0,63,255,255,255,192,0,0,1,255,255,255,254,0,0,0,0,0,3,255,255,255,252,0,0,0,31,255,255,255,224,0,0,0,0,0,63,255,255,255,192,0,0,1,255,255,255,254,0,0,0,0,0,3,255,255,255,252,0,0,0,31,255,255,255,224,0,0,0,0,0,63,255,255,255,192,0,0,1,255,255,255,254,0,0,0,0,0,3,255,255,255,252,0,0,0,31,255,255,255,224,0,0,0,0,0,63,255,255,255,192,0,0,1,255,255,255,254,0,0,0,0,0,3,255,255,255,252,0,0,0,31,255,255,255,224,0,0,0,0,0,63,255,255,255,192,0,0,1,255,255,255,254,0,0,0,0,0,3,255,255,255,248,0,0,0,15,255,255,255,240,0,0,0,0,0,127,255,255,255,128,0,0,0,255,255,255,255,0,0,0,0,0,7,255,255,255,248,0,0,0,15,255,255,255,240,0,0,0,0,0,127,255,255,255,128,0,0,0,255,255,255,255,0,0,0,0,0,7,255,255,255,248,0,0,0,7,255,255,255,248,0,0,0,0,0,255,255,255,255,0,0,0,0,127,255,255,255,128,0,0,0,0,15,255,255,255,240,0,0,0,7,255,255,255,252,0,0,0,0,1,255,255,255,255,0,0,0,0,127,255,255,255,192,0,0,0,0,31,255,255,255,224,0,0,0,3,255,255,255,254,0,0,0,0,3,255,255,255,254,0,0,0,0,63,255,255,255,240,0,0,0,0,127,255,255,255,224,0,0,0,1,255,255,255,255,128,0,0,0,15,255,255,255,252,0,0,0,0,31,255,255,255,252,0,0,0,1,255,255,255,255,192,0,0,0,0,255,255,255,255,224,0,0,0,63,255,255,255,248,0,0,0,0,15,255,255,255,255,128,0,0,15,255,255,255,255,128,0,0,0,0,127,255,255,255,254,0,0,3,255,255,255,255,240,0,0,0,0,7,255,255,255,255,248,0,1,255,255,255,255,254,0,0,0,0,0,63,255,255,255,255,252,3,255,255,255,255,255,224,0,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,192,0,0,0,0,0,1,255,255,255,255,255,255,255,255,255,255,248,0,0,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,224,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,15,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,63,255,255,255,255,255,255,255,255,224,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,248,0,0,0,0,0,0,0,0,15,255,255,255,255,255,255,255,255,128,0,0,0,0,0,0,0,3,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,248,0,0,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,192,0,0,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,224,0,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,248,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,248,0,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,3,255,255,255,255,255,240,0,255,255,255,255,255,252,0,0,0,0,127,255,255,255,255,224,0,0,127,255,255,255,255,224,0,0,0,7,255,255,255,255,248,0,0,1,255,255,255,255,255,0,0,0,0,255,255,255,255,252,0,0,0,3,255,255,255,255,240,0,0,0,31,255,255,255,255,128,0,0,0,31,255,255,255,255,128,0,0,1,255,255,255,255,240,0,0,0,0,255,255,255,255,248,0,0,0,63,255,255,255,252,0,0,0,0,3,255,255,255,255,192,0,0,3,255,255,255,255,128,0,0,0,0,31,255,255,255,252,0,0,0,127,255,255,255,240,0,0,0,0,0,255,255,255,255,224,0,0,7,255,255,255,255,0,0,0,0,0,15,255,255,255,254,0,0,0,255,255,255,255,224,0,0,0,0,0,127,255,255,255,240,0,0,15,255,255,255,252,0,0,0,0,0,3,255,255,255,255,0,0,0,255,255,255,255,192,0,0,0,0,0,63,255,255,255,240,0,0,15,255,255,255,248,0,0,0,0,0,1,255,255,255,255,128,0,1,255,255,255,255,128,0,0,0,0,0,31,255,255,255,248,0,0,31,255,255,255,248,0,0,0,0,0,1,255,255,255,255,128,0,1,255,255,255,255,0,0,0,0,0,0,15,255,255,255,248,0,0,31,255,255,255,240,0,0,0,0,0,0,255,255,255,255,128,0,1,255,255,255,255,0,0,0,0,0,0,15,255,255,255,248,0,0,63,255,255,255,240,0,0,0,0,0,0,255,255,255,255,192,0,3,255,255,255,255,0,0,0,0,0,0,15,255,255,255,252,0,0,63,255,255,255,240,0,0,0,0,0,0,255,255,255,255,192,0,3,255,255,255,255,0,0,0,0,0,0,15,255,255,255,252,0,0,63,255,255,255,240,0,0,0,0,0,0,255,255,255,255,192,0,3,255,255,255,255,0,0,0,0,0,0,15,255,255,255,252,0,0,63,255,255,255,240,0,0,0,0,0,0,255,255,255,255,192,0,3,255,255,255,255,0,0,0,0,0,0,15,255,255,255,252,0,0,63,255,255,255,240,0,0,0,0,0,0,255,255,255,255,192,0,3,255,255,255,255,0,0,0,0,0,0,15,255,255,255,252,0,0,63,255,255,255,240,0,0,0,0,0,0,255,255,255,255,192,0,3,255,255,255,255,0,0,0,0,0,0,15,255,255,255,252,0,0,63,255,255,255,240,0,0,0,0,0,0,255,255,255,255,192,0,3,255,255,255,255,128,0,0,0,0,0,31,255,255,255,252,0,0,31,255,255,255,248,0,0,0,0,0,1,255,255,255,255,128,0,1,255,255,255,255,192,0,0,0,0,0,63,255,255,255,248,0,0,31,255,255,255,252,0,0,0,0,0,3,255,255,255,255,128,0,1,255,255,255,255,224,0,0,0,0,0,127,255,255,255,248,0,0,31,255,255,255,254,0,0,0,0,0,7,255,255,255,255,128,0,0,255,255,255,255,240,0,0,0,0,0,255,255,255,255,240,0,0,15,255,255,255,255,128,0,0,0,0,31,255,255,255,255,0,0,0,255,255,255,255,252,0,0,0,0,3,255,255,255,255,240,0,0,7,255,255,255,255,224,0,0,0,0,127,255,255,255,254,0,0,0,127,255,255,255,255,0,0,0,0,15,255,255,255,255,224,0,0,7,255,255,255,255,248,0,0,0,1,255,255,255,255,254,0,0,0,63,255,255,255,255,224,0,0,0,127,255,255,255,255,192,0,0,3,255,255,255,255,255,128,0,0,31,255,255,255,255,252,0,0,0,31,255,255,255,255,255,0,0,15,255,255,255,255,255,128,0,0,1,255,255,255,255,255,255,192,63,255,255,255,255,255,248,0,0,0,15,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,3,255,255,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,224,0,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,31,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,224,0,0,0,0,0,0,3,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,15,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,63,255,255,255,255,255,255,255,255,192,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,240,0,0,0,0,0,0,0,0,3,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,0,0,15,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,240,0,0,0,0,0,0,0,0,63,255,255,255,255,255,255,255,255,224,0,0,0,0,0,0,0,3,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,0,0,127,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,7,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,240,0,0,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,192,0,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,240,0,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,254,0,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,248,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,0,31,255,255,255,255,255,192,7,255,255,255,255,255,128,0,0,0,1,255,255,255,255,255,224,0,3,255,255,255,255,254,0,0,0,0,15,255,255,255,255,248,0,0,7,255,255,255,255,240,0,0,0,0,255,255,255,255,255,0,0,0,15,255,255,255,255,192,0,0,0,7,255,255,255,255,240,0,0,0,31,255,255,255,254,0,0,0,0,127,255,255,255,255,0,0,0,0,127,255,255,255,248,0,0,0,3,255,255,255,255,240,0,0,0,1,255,255,255,255,192,0,0,0,31,255,255,255,255,0,0,0,0,7,255,255,255,254,0,0,0,1,255,255,255,255,240,0,0,0,0,31,255,255,255,248,0,0,0,15,255,255,255,255,0,0,0,0,0,255,255,255,255,192,0,0,0,127,255,255,255,240,0,0,0,0,3,255,255,255,254,0,0,0,7,255,255,255,255,128,0,0,0,0,15,255,255,255,248,0,0,0,63,255,255,255,248,0,0,0,0,0,127,255,255,255,192,0,0,1,255,255,255,255,192,0,0,0,0,1,255,255,255,254,0,0,0,31,255,255,255,252,0,0,0,0,0,15,255,255,255,248,0,0,0,255,255,255,255,224,0,0,0,0,0,63,255,255,255,192,0,0,7,255,255,255,254,0,0,0,0,0,1,255,255,255,254,0,0,0,63,255,255,255,240,0,0,0,0,0,15,255,255,255,248,0,0,1,255,255,255,255,128,0,0,0,0,0,127,255,255,255,192,0,0,15,255,255,255,252,0,0,0,0,0,1,255,255,255,254,0,0,0,255,255,255,255,192,0,0,0,0,0,15,255,255,255,240,0,0,7,255,255,255,254,0,0,0,0,0,0,127,255,255,255,192,0,0,63,255,255,255,240,0,0,0,0,0,3,255,255,255,254,0,0,1,255,255,255,255,128,0,0,0,0,0,31,255,255,255,240,0,0,15,255,255,255,252,0,0,0,0,0,0,255,255,255,255,128,0,0,127,255,255,255,224,0,0,0,0,0,7,255,255,255,252,0,0,3,255,255,255,255,0,0,0,0,0,0,63,255,255,255,224,0,0,31,255,255,255,248,0,0,0,0,0,0,255,255,255,255,128,0,0,255,255,255,255,192,0,0,0,0,0,7,255,255,255,252,0,0,7,255,255,255,254,0,0,0,0,0,0,63,255,255,255,224,0,0,63,255,255,255,240,0,0,0,0,0,3,255,255,255,255,0,0,1,255,255,255,255,128,0,0,0,0,0,31,255,255,255,248,0,0,15,255,255,255,252,0,0,0,0,0,0,255,255,255,255,192,0,0,127,255,255,255,224,0,0,0,0,0,7,255,255,255,254,0,0,3,255,255,255,255,0,0,0,0,0,0,63,255,255,255,248,0,0,31,255,255,255,248,0,0,0,0,0,1,255,255,255,255,192,0,0,255,255,255,255,192,0,0,0,0,0,15,255,255,255,254,0,0,3,255,255,255,254,0,0,0,0,0,0,127,255,255,255,240,0,0,31,255,255,255,248,0,0,0,0,0,7,255,255,255,255,128,0,0,255,255,255,255,192,0,0,0,0,0,63,255,255,255,252,0,0,7,255,255,255,254,0,0,0,0,0,1,255,255,255,255,224,0,0,63,255,255,255,248,0,0,0,0,0,31,255,255,255,255,0,0,1,255,255,255,255,192,0,0,0,0,0,255,255,255,255,248,0,0,7,255,255,255,254,0,0,0,0,0,7,255,255,255,255,192,0,0,63,255,255,255,248,0,0,0,0,0,127,255,255,255,254,0,0,1,255,255,255,255,192,0,0,0,0,3,255,255,255,255,240,0,0,15,255,255,255,255,0,0,0,0,0,63,255,255,255,255,128,0,0,63,255,255,255,252,0,0,0,0,3,255,255,255,255,252,0,0,1,255,255,255,255,224,0,0,0,0,31,255,255,255,255,224,0,0,15,255,255,255,255,128,0,0,0,1,255,255,255,255,255,0,0,0,63,255,255,255,254,0,0,0,0,31,255,255,255,255,248,0,0,1,255,255,255,255,248,0,0,0,1,255,255,255,255,255,192,0,0,15,255,255,255,255,240,0,0,0,31,255,255,255,255,254,0,0,0,63,255,255,255,255,192,0,0,3,255,255,255,255,255,248,0,0,1,255,255,255,255,255,128,0,0,127,255,255,255,255,255,192,0,0,7,255,255,255,255,255,0,0,15,255,255,255,255,255,252,0,0,0,63,255,255,255,255,255,128,7,255,255,255,255,255,255,224,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,255,248,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,255,192,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,254,0,0,0,3,255,255,255,255,255,255,255,255,255,255,255,255,255,240,0,0,0,15,255,255,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,63,255,255,255,255,255,255,255,255,255,255,255,255,252,0,0,0,1,255,255,255,255,255,255,255,255,255,255,255,255,255,224,0,0,0,7,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,255,248,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,255,255,192,0,0,0,1,255,255,255,255,255,255,255,255,251,255,255,255,254,0,0,0,0,7,255,255,255,255,255,255,255,255,31,255,255,255,240,0,0,0,0,31,255,255,255,255,255,255,255,240,255,255,255,255,128,0,0,0,0,63,255,255,255,255,255,255,255,7,255,255,255,252,0,0,0,0,0,255,255,255,255,255,255,255,224,63,255,255,255,224,0,0,0,0,3,255,255,255,255,255,255,254,1,255,255,255,255,0,0,0,0,0,7,255,255,255,255,255,255,192,15,255,255,255,248,0,0,0,0,0,15,255,255,255,255,255,248,0,127,255,255,255,192,0,0,0,0,0,31,255,255,255,255,255,0,3,255,255,255,254,0,0,0,0,0,0,31,255,255,255,255,224,0,31,255,255,255,224,0,0,0,0,0,0,63,255,255,255,248,0,0,255,255,255,255,0,0,0,0,0,0,0,31,255,255,254,0,0,7,255,255,255,248,0,0,0,0,0,0,0,3,255,252,0,0,0,63,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,224,0,0,0,255,255,255,255,0,0,0,0,0,1,255,255,255,254,0,0,0,7,255,255,255,248,0,0,0,0,0,15,255,255,255,240,0,0,0,63,255,255,255,192,0,0,0,0,0,127,255,255,255,128,0,0,1,255,255,255,254,0,0,0,0,0,3,255,255,255,252,0,0,0,15,255,255,255,240,0,0,0,0,0,31,255,255,255,192,0,0,0,127,255,255,255,128,0,0,0,0,1,255,255,255,254,0,0,0,3,255,255,255,252,0,0,0,0,0,15,255,255,255,240,0,0,0,31,255,255,255,240,0,0,0,0,0,127,255,255,255,128,0,0,0,127,255,255,255,128,0,0,0,0,3,255,255,255,248,0,0,0,3,255,255,255,252,0,0,0,0,0,63,255,255,255,192,0,0,0,31,255,255,255,224,0,0,0,0,1,255,255,255,254,0,0,0,0,255,255,255,255,128,0,0,0,0,15,255,255,255,240,0,0,0,7,255,255,255,252,0,0,0,0,0,255,255,255,255,0,0,0,0,63,255,255,255,224,0,0,0,0,7,255,255,255,248,0,0,0,0,255,255,255,255,128,0,0,0,0,127,255,255,255,192,0,0,0,7,255,255,255,252,0,0,0,0,3,255,255,255,252,0,0,0,0,63,255,255,255,240,0,0,0,0,63,255,255,255,224,0,0,0,1,255,255,255,255,128,0,0,0,3,255,255,255,254,0,0,0,0,7,255,255,255,254,0,0,0,0,31,255,255,255,240,0,0,0,0,63,255,255,255,248,0,0,0,1,255,255,255,255,128,0,0,0,1,255,255,255,255,224,0,0,0,31,255,255,255,248,0,0,0,0,7,255,255,255,255,128,0,0,3,255,255,255,255,192,0,0,0,0,63,255,255,255,254,0,0,0,63,255,255,255,252,0,0,0,0,0,255,255,255,255,252,0,0,7,255,255,255,255,224,0,0,0,0,7,255,255,255,255,252,0,1,255,255,255,255,254,0,0,0,0,0,31,255,255,255,255,254,1,255,255,255,255,255,240,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,255,248,0,0,0,0,0,31,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,248,0,0,0,0,0,3,255,255,255,255,255,255,255,255,255,255,255,192,0,0,0,0,0,15,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,63,255,255,255,255,255,255,255,255,255,255,192,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,7,255,255,255,255,255,255,255,255,255,255,192,0,0,0,0,0,0,31,255,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,224,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,7,255,255,255,255,255,255,255,255,255,192,0,0,0,0,0,0,0,15,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,0,63,255,255,255,255,255,255,255,255,192,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,128,0,0,0,0,0,0,0,0,7,255,255,255,255,255,255,255,248,0,0,0,0,0,0,0,0,0,15,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,224,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,255,128,0,0,0,0,0,0,0,0,0,63,255,255,192,0,0,0,0,0,0,0,0,0,15,255,255,255,255,255,128,0,0,0,0,0,0,0,0,1,255,255,254,0,0,0,0,0,0,0,0,0,0,127,255,255,255,255,255,128,0,0,0,0,0,0,0,0,7,255,255,240,0,0,0,0,0,0,0,0,0,7,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,63,255,255,128,0,0,0,0,0,0,0,0,0,63,255,255,255,255,255,254,0,0,0,0,0,0,0,0,1,255,255,254,0,0,0,0,0,0,0,0,0,3,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,15,255,255,240,0,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,252,0,0,0,0,0,0,0,0,63,255,255,128,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,248,0,0,0,0,0,0,0,1,255,255,254,0,0,0,0,0,0,0,0,0,3,255,255,255,255,255,255,255,224,0,0,0,0,0,0,0,15,255,255,240,0,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,192,0,0,0,0,0,0,0,63,255,255,128,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,128,0,0,0,0,0,0,1,255,255,254,0,0,0,0,0,0,0,0,0,7,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,15,255,255,240,0,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,63,255,255,128,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,248,0,0,0,0,0,0,1,255,255,252,0,0,0,0,0,0,0,0,0,3,255,255,255,240,7,255,255,255,224,0,0,0,0,0,0,15,255,255,240,0,0,0,0,0,0,0,0,0,31,255,255,254,0,3,255,255,255,192,0,0,0,0,0,0,127,255,255,128,0,0,0,0,0,0,0,0,0,127,255,255,224,0,7,255,255,255,0,0,0,0,0,0,1,255,255,252,0,0,0,0,0,0,0,0,0,3,255,255,255,0,0,15,255,255,254,0,0,0,0,0,0,15,255,255,240,0,0,0,0,0,0,0,0,0,15,255,255,248,0,0,31,255,255,248,0,0,0,0,0,0,127,255,255,128,0,0,0,0,0,0,0,0,0,63,255,255,224,0,0,63,255,255,224,0,0,0,0,0,1,255,255,252,0,0,0,0,0,0,0,0,0,1,255,255,255,0,0,0,127,255,255,192,0,0,0,0,0,15,255,255,224,0,0,0,0,0,0,0,0,0,7,255,255,252,0,0,1,255,255,255,0,0,0,0,0,0,127,255,255,128,0,0,0,0,0,0,0,0,0,31,255,255,224,0,0,3,255,255,252,0,0,0,0,0,3,255,255,252,0,0,0,0,0,0,0,0,0,0,127,255,255,128,0,0,15,255,255,240,0,0,0,0,0,15,255,255,224,0,0,0,0,0,0,0,0,0,3,255,255,254,0,0,0,31,255,255,224,0,0,0,0,0,127,255,255,128,0,0,0,0,0,0,0,0,0,15,255,255,240,0,0,0,127,255,255,128,0,0,0,0,3,255,255,252,0,0,0,0,0,0,0,0,0,0,63,255,255,192,0,0,1,255,255,254,0,0,0,0,0,15,255,255,224,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,7,255,255,248,0,0,0,0,0,127,255,255,0,0,0,0,0,0,0,0,0,0,3,255,255,252,0,0,0,31,255,255,224,0,0,0,0,3,255,255,252,0,0,0,0,0,0,0,0,0,0,15,255,255,240,0,0,0,63,255,255,128,0,0,0,0,31,255,255,224,0,0,0,0,0,0,0,0,0,0,127,255,255,128,0,0,0,255,255,255,0,0,0,0,0,127,255,255,0,0,0,0,0,0,0,0,0,0,1,255,255,254,0,0,0,3,255,255,252,0,0,0,0,3,255,255,252,0,0,0,0,0,0,0,0,0,0,7,255,255,248,0,0,0,15,255,255,240,0,0,0,0,31,255,255,224,0,0,0,0,0,0,0,0,0,0,31,255,255,224,0,0,0,63,255,255,192,0,0,0,0,127,255,255,0,0,0,0,0,0,0,0,0,0,0,127,255,255,128,0,0,0,255,255,255,0,0,0,0,3,255,255,248,0,0,0,0,0,0,0,0,0,0,1,255,255,254,0,0,0,3,255,255,252,0,0,0,0,31,255,255,224,0,0,0,0,0,0,0,0,0,0,7,255,255,248,0,0,0,15,255,255,240,0,0,0,0,127,255,255,0,0,0,0,0,0,0,0,0,0,0,31,255,255,224,0,0,0,63,255,255,192,0,0,0,3,255,255,248,0,0,0,0,0,0,0,0,0,0,0,127,255,255,128,0,0,0,255,255,255,0,0,0,0,31,255,255,224,0,0,0,0,0,0,0,0,0,0,1,255,255,254,0,0,0,3,255,255,252,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,7,255,255,248,0,0,0,15,255,255,240,0,0,0,3,255,255,248,0,0,0,0,0,0,0,0,0,0,0,31,255,255,224,0,0,0,63,255,255,192,0,0,0,31,255,255,224,0,0,0,0,0,0,0,0,0,0,0,127,255,255,128,0,0,0,255,255,255,0,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,1,255,255,254,0,0,0,3,255,255,252,0,0,0,3,255,255,248,0,0,0,0,0,0,0,0,0,0,0,7,255,255,248,0,0,0,15,255,255,240,0,0,0,31,255,255,192,0,0,0,0,0,0,0,0,0,0,0,31,255,255,224,0,0,0,63,255,255,192,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,128,0,0,0,255,255,255,0,0,0,7,255,255,248,0,0,0,0,0,0,0,0,0,0,0,1,255,255,254,0,0,0,3,255,255,252,0,0,0,31,255,255,192,0,0,0,0,0,0,0,0,0,0,0,7,255,255,248,0,0,0,15,255,255,240,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,240,0,0,0,127,255,255,128,0,0,7,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,192,0,0,1,255,255,254,0,0,0,31,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,7,255,255,248,0,0,0,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,252,0,0,0,31,255,255,224,0,0,7,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,248,0,0,0,255,255,255,128,0,0,31,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,224,0,0,3,255,255,254,0,0,0,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,128,0,0,15,255,255,240,0,0,7,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,0,0,0,127,255,255,192,0,0,63,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,252,0,0,1,255,255,255,0,0,0,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,248,0,0,15,255,255,252,0,0,7,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,224,0,0,63,255,255,224,0,0,63,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,192,0,1,255,255,255,128,0,0,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,128,0,15,255,255,254,0,0,7,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,128,0,255,255,255,240,0,0,63,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,192,31,255,255,255,192,0,1,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,255,255,255,254,0,0,7,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,248,0,0,63,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,255,192,0,1,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,255,255,255,255,0,0,7,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,255,255,255,255,255,248,0,0,63,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,192,0,1,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,255,255,254,0,0,15,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,255,255,255,248,0,0,63,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,192,0,1,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,255,254,0,0,15,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,255,255,224,0,0,63,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,255,255,0,0,1,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,255,248,0,0,15,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,255,128,0,0,63,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,248,0,0,1,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255,255,0,0,0,15,255,255,240,0,0,0,1,255,255,0,0,0,0,0,0,0,0,0,0,0,1,255,255,255,192,0,0,0,127,255,255,128,0,0,0,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,63,255,224,0,0,0,1,255,255,252,0,0,0,63,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,224,0,0,3,255,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,128,0,0,127,255,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,252,0,0,3,255,255,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,224,0,0,63,255,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,128,0,1,255,255,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,252,0,0,31,255,255,255,255,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,224,0,0,255,255,255,255,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,0,0,7,255,255,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,252,0,0,31,255,255,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,224,0,0,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,0,0,7,255,255,255,255,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,252,0,0,63,255,255,255,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,224,0,0,255,255,255,255,255,255,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,0,0,7,255,255,255,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,248,0,0,31,255,255,255,128,63,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,224,0,0,255,255,255,240,0,31,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,0,0,3,255,255,255,128,0,63,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,248,0,0,31,255,255,248,0,0,63,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,224,0,0,127,255,255,224,0,0,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,0,0,1,255,255,255,0,0,1,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,248,0,0,15,255,255,248,0,0,3,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,192,0,0,63,255,255,224,0,0,15,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,255,255,255,0,0,0,31,255,255,224,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,248,0,0,3,255,255,252,0,0,0,127,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,192,0,0,31,255,255,240,0,0,1,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,127,255,255,128,0,0,3,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,3,255,255,248,0,0,1,255,255,254,0,0,0,15,255,255,240,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,192,0,0,7,255,255,248,0,0,0,63,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,0,0,0,31,255,255,224,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,248,0,0,0,127,255,255,128,0,0,3,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,192,0,0,3,255,255,252,0,0,0,7,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,255,255,254,0,0,0,15,255,255,240,0,0,0,31,255,255,224,0,0,0,0,0,0,0,0,0,0,0,7,255,255,248,0,0,0,63,255,255,192,0,0,0,127,255,255,128,0,0,0,0,0,0,0,0,0,0,0,31,255,255,192,0,0,0,255,255,255,0,0,0,1,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,255,255,254,0,0,0,3,255,255,252,0,0,0,7,255,255,248,0,0,0,0,0,0,0,0,0,0,0,7,255,255,248,0,0,0,15,255,255,240,0,0,0,31,255,255,224,0,0,0,0,0,0,0,0,0,0,0,31,255,255,192,0,0,0,63,255,255,192,0,0,0,127,255,255,128,0,0,0,0,0,0,0,0,0,0,0,255,255,254,0,0,0,0,255,255,255,0,0,0,1,255,255,254,0,0,0,0,0,0,0,0,0,0,0,7,255,255,240,0,0,0,3,255,255,252,0,0,0,7,255,255,248,0,0,0,0,0,0,0,0,0,0,0,63,255,255,192,0,0,0,15,255,255,240,0,0,0,31,255,255,224,0,0,0,0,0,0,0,0,0,0,0,255,255,254,0,0,0,0,63,255,255,192,0,0,0,127,255,255,128,0,0,0,0,0,0,0,0,0,0,7,255,255,240,0,0,0,0,255,255,255,0,0,0,1,255,255,254,0,0,0,0,0,0,0,0,0,0,0,63,255,255,192,0,0,0,3,255,255,252,0,0,0,7,255,255,248,0,0,0,0,0,0,0,0,0,0,0,255,255,254,0,0,0,0,15,255,255,240,0,0,0,31,255,255,224,0,0,0,0,0,0,0,0,0,0,7,255,255,240,0,0,0,0,63,255,255,192,0,0,0,127,255,255,128,0,0,0,0,0,0,0,0,0,0,63,255,255,128,0,0,0,0,255,255,255,0,0,0,1,255,255,254,0,0,0,0,0,0,0,0,0,0,1,255,255,254,0,0,0,0,3,255,255,252,0,0,0,7,255,255,248,0,0,0,0,0,0,0,0,0,0,7,255,255,240,0,0,0,0,15,255,255,240,0,0,0,31,255,255,224,0,0,0,0,0,0,0,0,0,0,63,255,255,128,0,0,0,0,63,255,255,192,0,0,0,127,255,255,128,0,0,0,0,0,0,0,0,0,1,255,255,254,0,0,0,0,0,127,255,255,128,0,0,3,255,255,252,0,0,0,0,0,0,0,0,0,0,7,255,255,240,0,0,0,0,1,255,255,254,0,0,0,15,255,255,240,0,0,0,0,0,0,0,0,0,0,63,255,255,128,0,0,0,0,7,255,255,248,0,0,0,63,255,255,192,0,0,0,0,0,0,0,0,0,1,255,255,252,0,0,0,0,0,31,255,255,224,0,0,0,255,255,255,0,0,0,0,0,0,0,0,0,0,7,255,255,240,0,0,0,0,0,127,255,255,128,0,0,3,255,255,252,0,0,0,0,0,0,0,0,0,0,63,255,255,128,0,0,0,0,1,255,255,255,0,0,0,31,255,255,240,0,0,0,0,0,0,0,0,0,1,255,255,252,0,0,0,0,0,3,255,255,252,0,0,0,127,255,255,128,0,0,0,0,0,0,0,0,0,15,255,255,240,0,0,0,0,0,15,255,255,240,0,0,1,255,255,254,0,0,0,0,0,0,0,0,0,0,63,255,255,128,0,0,0,0,0,63,255,255,224,0,0,15,255,255,248,0,0,0,0,0,0,0,0,0,1,255,255,252,0,0,0,0,0,0,255,255,255,128,0,0,63,255,255,224,0,0,0,0,0,0,0,0,0,15,255,255,240,0,0,0,0,0,1,255,255,255,0,0,1,255,255,255,0,0,0,0,0,0,0,0,0,0,63,255,255,128,0,0,0,0,0,7,255,255,252,0,0,7,255,255,252,0,0,0,0,0,0,0,0,0,1,255,255,252,0,0,0,0,0,0,31,255,255,248,0,0,63,255,255,240,0,0,0,0,0,0,0,0,0,15,255,255,224,0,0,0,0,0,0,63,255,255,248,0,1,255,255,255,128,0,0,0,0,0,0,0,0,0,127,255,255,128,0,0,0,0,0,0,255,255,255,240,0,31,255,255,254,0,0,0,0,0,0,0,0,0,1,255,255,252,0,0,0,0,0,0,1,255,255,255,248,3,255,255,255,240,0,0,0,0,0,0,0,0,0,15,255,255,224,0,0,0,0,0,0,7,255,255,255,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,127,255,255,128,0,0,0,0,0,0,15,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,0,1,255,255,252,0,0,0,0,0,0,0,63,255,255,255,255,255,255,255,248,0,0,0,0,0,0,0,0,0,15,255,255,224,0,0,0,0,0,0,0,127,255,255,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,127,255,255,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,0,3,255,255,252,0,0,0,0,0,0,0,1,255,255,255,255,255,255,255,248,0,0,0,0,0,0,0,0,0,15,255,255,224,0,0,0,0,0,0,0,3,255,255,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,127,255,255,0,0,0,0,0,0,0,0,7,255,255,255,255,255,255,254,0,0,0,0,0,0,0,0,0,3,255,255,252,0,0,0,0,0,0,0,0,15,255,255,255,255,255,255,240,0,0,0,0,0,0,0,0,0,15,255,255,224,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0,127,255,255,0,0,0,0,0,0,0,0,0,63,255,255,255,255,255,248,0,0,0,0,0,0,0,0,0,3,255,255,248,0,0,0,0,0,0,0,0,0,63,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,15,255,255,224,0,0,0,0,0,0,0,0,0,63,255,255,255,255,252,0,0,0,0,0,0,0,0,0,0,127,255,255,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,255,255,0,0,0,0,0,0,0,0,0,3,255,255,255,255,0,0,0,0,0,15,255,255,255,252,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,255,255,255,255,192,0,0,0,0,3,255,255,255,255,0,0,0,0,0,15,255,255,255,252,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,255,255,255,255,192,0,0,0,0,3,255,255,255,255,0,0,0,0,0,15,255,255,255,252,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,255,255,255,255,192,0,0,0,0,3,255,255,255,255,0,0,0,0,0,15,255,255,255,252,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,255,255,255,255,192,0,0,0,0,3,255,255,255,255,0,0,0,0,0,15,255,255,255,252,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,255,255,255,255,192,0,0,0,0,3,255,255,255,255,0,0,0,0,0,15,255,255,255,252,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,255,255,255,255,192,0,0,0,0,3,255,255,255,255,0,0,0,0,0,15,255,255,255,252,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,255,255,255,255,192,0,0,0,0,3,255,255,255,255,0,0,0,0,0,15,255,255,255,252,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,255,255,255,255,192,0,0,0,0,3,255,255,255,255,0,0,0,0,0,15,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,255,255,255,255,192,0,0,0,0,3,255,255,255,255,0,0,0,0,0,15,255,255,255,252,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,255,255,255,255,192,0,0,0,0,3,255,255,255,255,0,0,0,0,0,15,255,255,255,252,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,255,255,255,255,192,0,0,0,0,3,255,255,255,255,0,0,0,0,0,15,255,255,255,252,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,255,255,255,255,192,0,0,0,0,3,255,255,255,255,0,0,0,0,0,15,255,255,255,252,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,255,255,255,255,192,0,0,0,0,3,255,255,255,255,0,0,0,0,0,15,255,255,255,252,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,255,255,255,255,192,0,0,0,0,3,255,255,255,255,0,0,0,0,0,15,255,255,255,252,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,255,255,255,255,192,0,0,0,0,3,255,255,255,255,0,0,0,0,0,15,255,255,255,252,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,255,255,255,255,192,0,0,0,0,3,255,255,255,255,0,0,0,0,0,15,255,255,255,252,0,0,0,0,0,63,255,255,255,240,0,0,0,0,0,255,255,255,255,192,0,0,0,127,255,255,255,255,255,255,248,0,1,255,255,255,255,255,255,255,224,0,7,255,255,255,255,255,255,255,128,0,31,255,255,255,255,255,255,254,0,0,127,255,255,255,255,255,255,248,0,1,255,255,255,255,255,255,255,224,0,7,255,255,255,255,255,255,255,128,0,31,255,255,255,255,255,255,254,0,0,127,255,255,255,255,255,255,248,0,1,255,255,255,255,255,255,255,224,0,7,255,255,255,255,255,255,255,128,0,31,255,255,255,255,255,255,254,0,0,127,255,255,255,255,255,255,248,0,1,255,255,255,255,255,255,255,224,0,7,255,255,255,255,255,255,255,128,0,31,255,255,255,255,255,255,254,0,0,127,255,255,255,255,255,255,248,0,1,255,255,255,255,255,255,255,224,0,7,255,255,255,255,255,255,255,128,0,31,255,255,255,255,255,255,254,0,0,127,255,255,255,255,255,255,248,0,1,255,255,255,255,255,255,255,224,0,7,255,255,255,255,255,255,255,128,0,31,255,255,255,255,255,255,254,0,0,127,255,255,255,255,255,255,248,0,1,255,255,255,255,255,255,255,224,0,7,255,255,255,255,255,255,255,128,0,31,255,255,255,255,255,255,254,0,0,127,255,255,255,255,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0,0,0,31,255,255,255,248,0,0}; +static const Glyph f0_236_glyphs[]={{0,140,168,0,0,140,48},{2940,140,162,0,3,140,49},{5775,141,165,0,0,141,50},{8684,141,168,0,0,141,51},{11645,141,165,0,0,141,52},{14554,140,165,0,3,140,53},{17442,141,168,0,0,141,54},{20403,141,162,0,3,141,55},{23259,140,168,0,0,140,56},{26199,141,168,0,0,141,57},{29160,230,167,0,1,230,37},{33962,78,124,0,41,78,58},{35171,78,79,0,86,78,45},{35942,72,34,0,131,72,46},{36248,46,0,0,165,46,32}}; +static const uint8_t f1_18_bits[]={12,1,128,48,6,0,192,24,3,0,96,12,0,0,0,2,0,224,25,131,48,102,12,193,152,0,0,0,0,0,0,0,0,0,0,0,12,193,144,34,31,243,254,19,6,96,200,127,207,248,68,9,131,48,4,0,128,48,31,135,120,203,25,3,160,62,1,224,30,2,198,88,235,15,192,96,4,0,128,56,51,99,27,48,217,135,216,29,128,8,0,252,15,240,217,134,204,102,102,30,0,30,7,224,196,24,1,0,112,27,51,52,103,140,97,142,63,195,204,12,1,128,48,6,0,192,0,0,0,0,0,0,0,0,0,0,0,1,0,96,24,6,1,128,48,6,0,192,24,3,0,96,12,1,128,24,3,128,56,1,0,0,16,3,0,48,3,0,32,6,0,192,24,3,0,96,12,1,128,48,12,3,128,224,16,0,0,12,1,128,48,118,231,248,24,7,129,152,33,0,0,0,0,12,1,128,48,63,231,252,24,3,0,96,0,0,0,14,1,128,48,14,1,128,48,0,31,131,240,0,0,0,0,0,0,0,14,1,192,56,0,1,128,48,6,1,128,48,6,1,128,48,12,1,128,48,12,1,128,48,12,1,128,96,0,30,7,225,134,48,198,24,203,27,99,12,97,140,49,134,31,129,224,14,3,192,216,19,0,96,12,1,128,48,6,0,192,24,31,227,252,30,7,225,134,48,192,24,7,0,192,48,12,3,0,192,63,199,248,63,135,240,12,7,0,192,30,0,224,12,1,140,49,134,31,129,224,3,0,192,56,6,1,128,99,28,99,12,127,143,240,6,0,192,24,63,135,240,192,24,3,112,127,12,112,6,0,192,24,195,31,192,240,6,0,192,48,12,1,128,126,31,227,6,96,204,25,199,31,193,240,127,207,241,134,48,192,48,6,0,192,48,6,1,128,48,14,1,128,30,7,225,134,48,198,24,126,7,129,152,97,140,49,134,31,129,224,31,7,241,199,48,102,12,227,143,224,252,3,0,224,24,6,1,192,14,1,192,16,0,0,0,0,0,0,32,14,1,192,14,1,192,16,0,0,0,0,0,0,112,12,1,128,48,12,1,128,0,0,112,60,30,7,0,224,15,0,120,3,128,16,0,0,63,199,248,0,0,3,252,127,128,0,0,0,0,0,14,0,240,7,128,56,3,3,193,224,112,8,0,0,0,62,7,224,14,0,192,24,7,7,192,224,24,3,0,0,4,1,192,15,135,248,195,176,54,118,223,219,27,99,108,109,141,177,179,182,0,96,15,128,112,14,1,192,120,13,129,176,54,12,65,140,63,135,241,134,48,102,12,63,7,240,195,24,99,12,127,15,225,142,48,198,24,195,31,195,240,30,7,225,134,48,198,0,192,24,3,0,96,12,49,134,31,129,224,126,15,225,142,48,198,24,195,24,99,12,97,140,49,142,63,135,224,63,199,240,192,24,3,0,127,15,225,128,48,6,0,192,31,195,252,63,199,248,192,24,3,0,96,15,241,252,48,6,0,192,24,3,0,30,7,225,134,48,198,0,192,25,227,60,97,140,49,134,31,129,224,48,198,24,195,24,99,12,127,143,241,134,48,198,24,195,24,99,12,63,135,240,24,3,0,96,12,1,128,48,6,0,192,24,31,195,248,15,129,240,6,0,192,24,3,0,96,12,1,140,49,134,31,129,224,48,198,24,198,24,195,48,126,15,193,152,51,134,48,198,24,99,12,48,6,0,192,24,3,0,96,12,1,128,48,6,0,192,31,195,252,113,206,57,199,61,230,172,221,155,179,6,96,204,25,131,48,102,12,113,142,49,198,60,198,152,211,27,99,44,101,140,241,142,49,198,56,30,7,225,134,48,198,24,195,24,99,12,97,140,49,134,31,129,224,63,135,248,195,152,51,6,97,207,241,248,48,6,0,192,24,3,0,30,7,225,134,48,198,24,195,24,99,12,97,140,49,134,31,129,224,6,0,192,12,63,7,240,195,24,99,12,99,143,225,248,51,6,112,198,24,195,12,30,7,225,134,48,6,0,112,7,128,56,1,140,49,134,31,129,224,127,207,248,48,6,0,192,24,3,0,96,12,1,128,48,6,0,192,97,140,49,134,48,198,24,195,24,99,12,97,140,49,134,31,129,224,96,204,25,134,24,195,24,99,12,64,216,27,3,96,120,7,0,224,204,89,153,51,39,100,172,213,154,179,212,114,142,113,206,25,195,56,96,198,48,198,13,129,224,28,3,128,112,27,3,96,198,56,198,12,192,204,49,134,25,131,48,60,7,128,96,12,1,128,48,6,0,192,127,143,240,14,1,128,96,12,3,0,192,24,6,1,192,63,199,248,15,1,240,48,6,0,192,24,3,0,96,12,1,128,48,6,0,192,24,3,0,120,15,128,96,6,0,192,12,1,128,48,3,0,96,12,0,192,24,3,128,48,6,0,96,12,1,128,31,3,224,12,1,128,48,6,0,192,24,3,0,96,12,1,128,48,6,0,192,248,31,0,14,1,192,104,13,131,16,99,8,96,0,0,0,0,0,0,0,0,127,207,248,24,1,128,24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,30,7,224,134,0,195,248,255,24,99,12,127,135,176,48,6,0,192,27,131,248,97,140,49,134,48,198,24,195,31,195,112,30,7,225,134,48,6,0,192,24,3,12,63,3,192,1,128,48,6,14,195,248,195,24,99,12,97,140,49,134,31,193,216,30,7,225,134,48,199,248,255,24,3,12,63,3,192,7,193,248,48,6,7,252,255,131,0,96,12,1,128,48,6,0,192,29,135,241,134,48,198,24,195,24,97,252,29,128,48,6,15,131,224,48,6,0,192,27,131,248,97,140,49,134,48,198,24,195,24,99,12,14,1,192,0,0,7,192,120,3,0,96,12,1,128,48,6,7,252,255,128,3,128,112,0,0,1,240,62,0,192,24,3,0,96,12,1,128,48,6,0,193,240,60,0,48,6,0,192,24,99,24,99,12,193,248,63,6,96,198,24,227,12,124,15,128,48,6,0,192,24,3,0,96,12,1,128,48,7,224,124,127,141,153,179,54,102,204,217,155,51,102,108,205,152,55,7,240,195,24,99,12,97,140,49,134,48,198,24,30,7,225,134,48,198,24,195,24,99,12,63,3,192,55,7,240,195,24,99,12,97,140,49,134,63,134,224,192,24,3,0,29,135,241,134,48,198,24,195,24,99,12,63,131,176,6,0,192,24,55,7,240,195,24,99,0,96,12,1,128,48,6,0,31,7,240,194,24,1,240,15,0,49,134,63,195,224,8,1,128,48,63,231,252,24,3,0,96,12,1,128,48,7,224,124,97,140,49,134,48,198,24,195,24,99,12,63,3,192,96,204,16,198,24,195,16,54,6,192,240,14,1,192,68,73,137,59,53,102,172,213,26,161,156,51,134,48,113,134,48,108,15,0,224,28,7,193,216,49,140,56,96,206,48,198,24,193,176,54,7,192,112,14,1,128,48,6,1,128,63,199,248,6,1,192,112,28,7,0,192,63,199,248,1,128,112,24,3,0,96,12,1,128,48,124,7,128,24,3,0,96,12,1,128,60,3,128,12,1,128,48,6,0,192,24,3,0,96,12,1,128,48,6,0,192,24,3,0,96,12,0,48,15,0,48,6,0,192,24,3,0,96,7,192,240,48,6,0,192,24,3,1,224,120,0,56,207,153,159,49,192,0,0,0,0,0}; +static const Glyph f1_18_glyphs[]={{0,11,0,0,13,11,32},{0,11,13,0,0,11,33},{18,11,13,0,0,11,34},{36,11,13,0,0,11,35},{54,11,18,0,-3,11,36},{79,13,13,0,0,11,37},{101,11,13,0,0,11,38},{119,11,13,0,0,11,39},{137,11,18,0,-2,11,40},{162,11,18,0,-2,11,41},{187,11,11,0,2,11,42},{203,11,10,0,3,11,43},{217,11,6,0,10,11,44},{226,11,7,0,6,11,45},{236,11,3,0,10,11,46},{241,11,17,0,-2,11,47},{265,11,13,0,0,11,48},{283,11,13,0,0,11,49},{301,11,13,0,0,11,50},{319,11,13,0,0,11,51},{337,11,13,0,0,11,52},{355,11,13,0,0,11,53},{373,11,13,0,0,11,54},{391,11,13,0,0,11,55},{409,11,13,0,0,11,56},{427,11,13,0,0,11,57},{445,11,10,0,3,11,58},{459,11,13,0,3,11,59},{477,11,11,0,2,11,60},{493,11,9,0,4,11,61},{506,11,11,0,2,11,62},{522,11,13,0,0,11,63},{540,11,16,0,0,11,64},{562,11,13,0,0,11,65},{580,11,13,0,0,11,66},{598,11,13,0,0,11,67},{616,11,13,0,0,11,68},{634,11,13,0,0,11,69},{652,11,13,0,0,11,70},{670,11,13,0,0,11,71},{688,11,13,0,0,11,72},{706,11,13,0,0,11,73},{724,11,13,0,0,11,74},{742,11,13,0,0,11,75},{760,11,13,0,0,11,76},{778,11,13,0,0,11,77},{796,11,13,0,0,11,78},{814,11,13,0,0,11,79},{832,11,13,0,0,11,80},{850,11,16,0,0,11,81},{872,11,13,0,0,11,82},{890,11,13,0,0,11,83},{908,11,13,0,0,11,84},{926,11,13,0,0,11,85},{944,11,13,0,0,11,86},{962,11,13,0,0,11,87},{980,11,13,0,0,11,88},{998,11,13,0,0,11,89},{1016,11,13,0,0,11,90},{1034,11,17,0,-2,11,91},{1058,11,17,0,-2,11,92},{1082,11,17,0,-2,11,93},{1106,11,13,0,0,11,94},{1124,11,2,0,13,11,95},{1127,11,15,0,-2,11,96},{1148,11,10,0,3,11,97},{1162,11,13,0,0,11,98},{1180,11,10,0,3,11,99},{1194,11,13,0,0,11,100},{1212,11,10,0,3,11,101},{1226,11,13,0,0,11,102},{1244,11,13,0,3,11,103},{1262,11,13,0,0,11,104},{1280,11,14,0,-1,11,105},{1300,11,17,0,-1,11,106},{1324,11,13,0,0,11,107},{1342,11,13,0,0,11,108},{1360,11,10,0,3,11,109},{1374,11,10,0,3,11,110},{1388,11,10,0,3,11,111},{1402,11,13,0,3,11,112},{1420,11,13,0,3,11,113},{1438,11,10,0,3,11,114},{1452,11,10,0,3,11,115},{1466,11,13,0,0,11,116},{1484,11,10,0,3,11,117},{1498,11,10,0,3,11,118},{1512,11,10,0,3,11,119},{1526,11,10,0,3,11,120},{1540,11,13,0,3,11,121},{1558,11,10,0,3,11,122},{1572,11,17,0,-2,11,123},{1596,11,17,0,-2,11,124},{1620,11,17,0,-2,11,125},{1644,11,8,0,5,11,126}}; +static const uint8_t f2_16_bits[]={24,6,1,128,96,24,6,1,128,96,0,0,1,128,112,54,13,131,96,216,54,0,0,0,0,0,0,0,0,0,9,130,96,144,255,63,198,65,49,254,127,132,131,32,200,8,2,1,224,252,105,154,6,128,224,31,2,224,153,166,63,7,128,128,32,56,205,177,182,55,131,224,12,3,224,126,30,198,216,219,49,192,30,15,195,48,192,24,14,7,217,158,103,24,199,240,246,12,3,0,192,48,12,0,0,0,0,0,0,0,0,0,0,0,192,112,48,28,6,1,128,96,24,6,1,128,96,12,3,128,112,4,0,12,3,128,48,14,1,128,96,24,6,1,128,96,24,12,7,3,128,128,12,3,4,201,254,127,131,1,224,204,0,0,0,12,3,0,193,254,127,131,0,192,48,0,0,12,7,1,128,96,16,0,62,15,128,0,0,0,0,0,28,7,1,192,3,0,192,48,24,6,1,128,192,48,24,6,1,128,192,48,12,6,0,28,15,134,49,140,99,27,198,241,140,99,24,195,224,112,28,15,6,193,48,12,3,0,192,48,12,3,7,241,252,28,15,134,49,140,3,1,128,224,112,24,12,7,241,252,127,31,192,32,48,14,7,192,56,6,1,152,99,240,120,6,3,128,192,96,56,12,199,49,140,127,31,192,48,12,127,31,134,1,128,110,31,198,48,12,3,24,199,224,240,6,3,1,192,96,48,15,199,57,134,97,152,99,240,120,127,159,230,25,140,3,1,128,96,56,12,3,1,128,96,28,31,198,49,140,119,15,131,225,140,99,24,199,240,240,30,15,198,25,134,97,156,227,240,124,6,3,128,192,96,28,7,0,128,0,0,0,0,128,112,28,0,28,7,0,128,0,0,0,0,0,112,24,6,1,128,192,0,0,193,225,224,96,28,1,192,28,1,0,0,127,31,192,0,0,127,31,192,0,0,0,24,3,192,60,3,1,193,193,192,64,0,0,120,15,0,96,24,6,15,131,192,192,48,0,1,0,224,31,15,231,29,131,103,219,54,205,179,108,219,54,125,128,112,15,129,224,28,7,1,224,88,54,12,131,48,252,127,24,198,25,134,124,31,198,49,140,99,31,135,241,134,97,152,103,241,248,28,15,134,49,128,96,24,6,1,128,96,24,195,224,112,124,31,134,49,140,99,24,198,49,140,99,24,199,225,240,127,31,198,1,128,96,31,199,225,128,96,24,7,241,252,127,159,198,1,128,96,31,199,241,128,96,24,6,1,128,28,15,134,49,128,96,27,198,241,140,99,24,195,224,112,99,24,198,49,140,99,31,199,241,140,99,24,198,49,140,127,31,193,128,96,24,6,1,128,96,24,6,7,241,252,15,131,224,24,6,1,128,96,24,6,97,152,99,240,120,99,152,198,113,152,102,31,7,193,152,102,24,198,49,142,48,12,3,0,192,48,12,3,0,192,48,12,3,248,254,115,156,231,57,254,125,155,102,217,134,97,152,102,25,134,115,28,199,49,204,123,26,198,177,188,103,25,198,113,156,28,15,134,49,140,99,24,198,49,140,99,24,195,224,112,126,31,198,25,134,97,152,231,241,240,96,24,6,1,128,28,15,134,49,140,99,24,198,49,140,99,24,195,224,120,6,1,128,48,126,31,198,25,134,97,159,199,225,152,103,24,198,57,134,30,15,198,25,128,112,15,129,240,14,1,152,99,240,120,127,159,224,192,48,12,3,0,192,48,12,3,0,192,48,99,24,198,49,140,99,24,198,49,140,99,24,195,224,112,97,152,102,49,140,51,12,195,32,216,30,7,129,192,112,76,147,36,201,182,109,155,103,249,206,115,156,195,48,204,97,156,195,48,248,30,7,1,192,120,54,12,198,49,134,97,152,99,48,204,30,7,129,224,48,12,3,0,192,48,127,31,192,112,24,12,3,1,128,96,48,28,7,241,252,31,7,129,128,96,24,6,1,128,96,24,6,1,128,96,24,7,129,240,96,12,3,0,192,24,6,1,128,48,12,3,128,96,24,3,0,192,48,62,7,128,96,24,6,1,128,96,24,6,1,128,96,24,6,7,131,224,8,7,1,224,88,50,12,198,48,0,0,0,0,0,0,127,159,224,24,3,0,0,0,0,0,0,0,0,0,0,0,0,0,30,15,226,24,126,63,152,102,25,254,61,128,96,24,6,1,184,127,24,198,49,140,99,24,199,241,184,28,15,134,49,128,96,24,6,48,252,28,0,3,0,192,48,236,127,24,198,49,140,99,24,199,240,236,28,15,134,49,252,127,24,6,32,248,28,0,15,135,225,128,96,127,159,225,128,96,24,6,1,128,96,59,31,198,49,140,99,24,198,112,252,27,0,195,240,248,96,24,6,1,184,127,28,198,49,140,99,24,198,49,140,12,3,0,1,240,60,3,0,192,48,12,3,7,249,254,6,1,128,0,248,62,1,128,96,24,6,1,128,96,24,6,15,7,128,96,24,6,1,140,99,25,135,225,240,102,25,134,49,142,124,31,0,192,48,12,3,0,192,48,12,3,0,248,31,127,31,230,217,182,109,155,102,217,182,109,128,110,31,198,49,140,99,24,198,49,140,99,0,28,15,134,49,140,99,24,198,48,248,28,0,110,31,198,49,140,99,24,198,49,252,110,24,6,1,128,59,31,198,49,140,99,24,198,49,252,59,0,192,48,12,110,31,198,49,140,96,24,6,1,128,96,0,62,31,198,49,192,62,0,198,49,252,62,0,24,6,1,129,254,127,134,1,128,96,24,6,1,248,62,99,24,198,49,140,99,24,198,48,248,28,0,97,152,198,48,204,51,13,129,224,120,28,0,204,147,100,217,54,117,157,199,113,204,51,0,99,12,195,224,120,28,7,131,96,204,99,128,97,152,195,48,204,54,7,129,224,112,12,3,1,128,96,127,31,192,96,56,28,14,3,1,252,127,0,7,3,192,192,48,12,3,7,129,224,12,3,0,192,48,12,3,192,112,12,3,0,192,48,12,3,0,192,48,12,3,0,192,48,12,3,0,192,112,30,1,128,96,24,6,0,240,60,24,6,1,128,96,24,30,7,0,48,30,102,249,156,0,0,0,0,0}; +static const Glyph f2_16_glyphs[]={{0,10,0,0,12,10,32},{0,10,12,0,0,10,33},{15,10,12,0,0,10,34},{30,10,12,0,0,10,35},{45,10,16,0,-2,10,36},{65,11,12,0,0,10,37},{82,10,12,0,0,10,38},{97,10,12,0,0,10,39},{112,10,16,0,-2,10,40},{132,10,16,0,-2,10,41},{152,10,10,0,2,10,42},{165,10,9,0,3,10,43},{177,10,5,0,10,10,44},{184,10,6,0,6,10,45},{192,10,3,0,9,10,46},{196,10,15,0,-1,10,47},{215,10,12,0,0,10,48},{230,10,12,0,0,10,49},{245,10,12,0,0,10,50},{260,10,12,0,0,10,51},{275,10,12,0,0,10,52},{290,10,12,0,0,10,53},{305,10,12,0,0,10,54},{320,10,12,0,0,10,55},{335,10,12,0,0,10,56},{350,10,12,0,0,10,57},{365,10,9,0,3,10,58},{377,10,12,0,3,10,59},{392,10,10,0,2,10,60},{405,10,8,0,4,10,61},{415,10,10,0,2,10,62},{428,10,12,0,0,10,63},{443,10,15,0,0,10,64},{462,10,12,0,0,10,65},{477,10,12,0,0,10,66},{492,10,12,0,0,10,67},{507,10,12,0,0,10,68},{522,10,12,0,0,10,69},{537,10,12,0,0,10,70},{552,10,12,0,0,10,71},{567,10,12,0,0,10,72},{582,10,12,0,0,10,73},{597,10,12,0,0,10,74},{612,10,12,0,0,10,75},{627,10,12,0,0,10,76},{642,10,12,0,0,10,77},{657,10,12,0,0,10,78},{672,10,12,0,0,10,79},{687,10,12,0,0,10,80},{702,10,15,0,0,10,81},{721,10,12,0,0,10,82},{736,10,12,0,0,10,83},{751,10,12,0,0,10,84},{766,10,12,0,0,10,85},{781,10,12,0,0,10,86},{796,10,12,0,0,10,87},{811,10,12,0,0,10,88},{826,10,12,0,0,10,89},{841,10,12,0,0,10,90},{856,10,15,0,-1,10,91},{875,10,15,0,-1,10,92},{894,10,15,0,-1,10,93},{913,10,12,0,0,10,94},{928,10,2,0,12,10,95},{931,10,12,0,0,10,96},{946,10,9,0,3,10,97},{958,10,12,0,0,10,98},{973,10,9,0,3,10,99},{985,10,12,0,0,10,100},{1000,10,9,0,3,10,101},{1012,10,12,0,0,10,102},{1027,10,12,0,3,10,103},{1042,10,12,0,0,10,104},{1057,10,12,0,0,10,105},{1072,10,15,0,0,10,106},{1091,10,12,0,0,10,107},{1106,10,12,0,0,10,108},{1121,10,9,0,3,10,109},{1133,10,9,0,3,10,110},{1145,10,9,0,3,10,111},{1157,10,12,0,3,10,112},{1172,10,12,0,3,10,113},{1187,10,9,0,3,10,114},{1199,10,9,0,3,10,115},{1211,10,12,0,0,10,116},{1226,10,9,0,3,10,117},{1238,10,9,0,3,10,118},{1250,10,9,0,3,10,119},{1262,10,9,0,3,10,120},{1274,10,12,0,3,10,121},{1289,10,9,0,3,10,122},{1301,10,15,0,-1,10,123},{1320,10,15,0,-1,10,124},{1339,10,15,0,-1,10,125},{1358,10,8,0,4,10,126}}; +static const uint8_t f2_18_bits[]={6,0,192,24,3,0,96,12,1,128,48,6,0,0,0,7,0,224,51,6,96,204,25,131,48,0,0,0,0,0,0,0,0,0,0,0,12,193,144,34,31,243,254,19,6,96,200,127,207,248,68,9,131,48,4,0,128,56,31,199,88,203,25,3,160,62,1,224,30,2,198,88,255,15,192,96,4,0,128,56,51,99,27,48,217,135,216,29,128,8,0,252,15,240,217,132,204,102,102,30,0,30,7,224,198,24,3,128,112,31,51,54,103,140,113,206,31,225,204,12,1,128,48,6,0,192,0,0,0,0,0,0,0,0,0,0,0,1,0,112,28,6,1,192,48,6,0,192,24,3,0,96,12,1,192,24,3,192,60,1,0,0,16,7,128,120,3,0,112,6,0,192,24,3,0,96,12,1,128,112,12,3,129,224,16,0,0,12,1,128,48,118,231,248,24,6,193,152,49,0,0,0,0,12,1,128,48,63,231,252,24,3,0,96,0,0,0,14,1,128,48,14,1,128,48,0,31,131,240,0,0,0,0,0,0,0,14,1,192,56,0,1,192,48,14,1,128,48,14,1,128,48,14,1,128,112,12,1,128,112,12,1,128,112,0,30,7,225,206,48,198,24,219,27,99,12,97,140,49,206,31,193,224,7,3,224,236,17,128,48,6,0,192,24,3,0,96,12,31,227,252,30,7,225,206,48,192,24,7,1,192,112,28,7,1,224,63,199,248,63,135,240,12,7,0,224,30,0,224,12,1,140,49,206,31,129,224,3,0,192,56,14,1,128,115,28,99,12,127,143,240,6,0,192,24,63,199,240,192,24,3,112,127,14,112,6,0,192,24,199,31,193,240,7,1,192,48,14,3,128,126,31,227,142,96,204,25,199,31,193,240,127,207,241,134,48,192,48,6,1,192,48,6,1,128,48,14,1,128,30,7,241,198,48,199,24,126,7,129,216,97,140,49,206,31,193,224,31,7,241,199,48,102,12,227,143,240,252,3,128,224,24,7,1,192,14,1,192,56,0,0,0,0,0,0,112,14,1,192,14,1,192,56,0,0,0,0,0,0,112,12,1,128,48,12,1,128,0,128,112,62,30,7,0,224,15,0,120,3,128,16,0,0,127,143,240,0,0,7,248,255,0,0,0,0,0,64,14,1,240,7,128,56,7,3,193,224,112,8,0,0,0,62,7,224,14,0,192,56,62,7,128,192,24,0,0,0,14,1,192,15,135,248,195,184,54,118,223,219,59,227,124,109,141,177,179,183,0,96,15,128,112,14,1,192,120,13,129,176,54,12,225,140,63,135,241,199,48,102,12,126,15,241,134,48,198,24,254,31,195,28,97,140,49,142,63,135,224,30,7,241,206,48,198,0,192,24,3,0,96,12,49,206,31,193,224,126,15,225,142,48,198,24,195,24,99,12,97,140,49,142,63,135,224,63,199,248,192,24,3,0,127,15,241,128,48,6,0,192,31,227,252,127,143,241,128,48,6,0,192,31,227,248,96,12,1,128,48,6,0,30,7,241,206,48,198,0,192,25,227,60,97,140,49,206,31,193,224,97,140,49,134,48,198,24,255,31,227,12,97,140,49,134,48,198,24,63,199,248,24,3,0,96,12,1,128,48,6,0,192,24,31,227,252,15,193,248,3,0,96,12,1,128,48,6,0,204,25,199,31,193,240,97,140,49,140,49,134,112,252,31,131,48,103,12,97,142,48,198,28,48,6,0,192,24,3,0,96,12,1,128,48,6,0,192,31,227,252,113,206,57,239,61,230,172,221,155,179,6,96,204,25,131,48,102,12,113,142,49,230,60,199,152,219,27,99,44,103,140,241,142,49,198,56,30,7,241,206,48,198,24,195,24,99,12,97,140,49,206,31,193,224,127,15,241,135,48,102,12,195,159,227,248,96,12,1,128,48,6,0,30,7,225,206,48,198,24,195,24,99,12,97,140,49,206,31,129,240,6,0,224,12,126,15,225,142,48,198,24,199,31,195,240,102,12,225,140,49,198,24,31,7,241,199,48,103,0,120,7,192,60,1,204,25,199,31,193,240,127,207,248,24,3,0,96,12,1,128,48,6,0,192,24,3,0,96,97,140,49,134,48,198,24,195,24,99,12,97,140,49,206,31,193,224,96,204,25,199,24,195,24,99,12,224,216,27,3,96,120,7,0,224,204,217,155,51,39,102,172,213,154,179,212,123,142,113,206,57,199,56,97,198,48,238,13,129,240,28,3,128,112,31,7,96,206,56,198,12,224,204,49,198,25,131,176,62,7,128,112,12,1,128,48,6,0,192,127,143,240,14,1,128,96,28,3,0,224,56,6,1,192,63,199,248,31,3,224,96,12,1,128,48,6,0,192,24,3,0,96,12,1,128,48,6,0,248,31,0,112,6,0,192,28,1,128,48,7,0,96,14,0,192,24,3,128,48,6,0,224,12,1,192,62,7,192,24,3,0,96,12,1,128,48,6,0,192,24,3,0,96,12,1,129,240,62,0,14,1,192,108,13,131,48,99,28,96,0,0,0,0,0,0,0,0,127,207,248,28,1,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,30,7,240,198,0,193,248,255,24,99,140,127,135,176,96,12,1,128,55,135,248,231,24,99,12,97,140,49,206,63,198,240,30,7,241,198,48,198,0,192,24,99,140,63,131,192,1,128,48,6,14,195,248,231,24,99,12,97,140,49,206,31,193,216,30,7,225,206,48,199,248,255,24,3,140,63,131,192,7,193,248,48,6,7,252,255,131,0,96,12,1,128,48,6,0,192,29,135,241,206,48,198,24,195,28,225,252,29,128,48,14,31,131,224,112,14,1,192,59,135,240,231,28,99,140,113,142,49,198,56,199,24,12,1,128,0,0,7,192,248,3,0,96,12,1,128,48,6,7,252,255,128,7,0,224,0,0,3,240,126,0,192,24,3,0,96,12,1,128,48,6,1,195,240,124,0,96,12,1,128,48,198,56,198,25,195,240,126,12,225,140,49,198,24,124,15,128,48,6,0,192,24,3,0,96,12,1,128,48,7,224,126,111,143,249,179,54,102,204,217,155,51,102,108,205,152,110,15,225,206,48,198,24,195,24,99,12,97,140,48,30,7,225,206,48,198,24,195,24,99,156,63,3,192,111,15,241,206,48,198,24,195,24,99,156,127,141,225,128,48,6,0,29,135,241,198,48,198,24,195,24,99,140,63,131,176,6,0,192,24,55,135,248,231,24,99,0,96,12,1,128,48,6,0,63,15,241,134,48,3,224,31,0,99,12,127,135,224,12,1,128,48,63,231,252,24,3,0,96,12,1,128,48,7,224,124,97,140,49,134,48,198,24,195,24,99,156,63,3,192,96,204,56,198,24,195,56,118,6,192,248,30,1,192,204,73,217,187,53,102,172,213,158,227,220,51,134,112,113,198,112,108,15,128,224,28,7,193,216,49,142,56,96,206,56,198,24,195,176,54,7,192,112,14,1,192,48,6,1,192,127,143,240,14,3,128,224,56,14,3,128,127,143,240,1,128,248,28,3,0,96,12,1,128,48,124,7,128,24,3,0,96,12,1,192,60,3,192,12,1,128,48,6,0,192,24,3,0,96,12,1,128,48,6,0,192,24,3,0,96,12,0,112,15,0,96,12,1,128,48,6,0,224,15,129,240,112,12,1,128,48,6,3,192,112,0,56,207,153,159,49,192,0,0,0,0,0}; +static const Glyph f2_18_glyphs[]={{0,11,0,0,13,11,32},{0,11,13,0,0,11,33},{18,11,13,0,0,11,34},{36,11,13,0,0,11,35},{54,11,18,0,-2,11,36},{79,13,13,0,0,11,37},{101,11,13,0,0,11,38},{119,11,13,0,0,11,39},{137,11,18,0,-2,11,40},{162,11,18,0,-2,11,41},{187,11,11,0,2,11,42},{203,11,10,0,3,11,43},{217,11,6,0,10,11,44},{226,11,7,0,6,11,45},{236,11,3,0,10,11,46},{241,11,17,0,-2,11,47},{265,11,13,0,0,11,48},{283,11,13,0,0,11,49},{301,11,13,0,0,11,50},{319,11,13,0,0,11,51},{337,11,13,0,0,11,52},{355,11,13,0,0,11,53},{373,11,13,0,0,11,54},{391,11,13,0,0,11,55},{409,11,13,0,0,11,56},{427,11,13,0,0,11,57},{445,11,10,0,3,11,58},{459,11,13,0,3,11,59},{477,11,11,0,2,11,60},{493,11,9,0,4,11,61},{506,11,11,0,2,11,62},{522,11,13,0,0,11,63},{540,11,16,0,0,11,64},{562,11,13,0,0,11,65},{580,11,13,0,0,11,66},{598,11,13,0,0,11,67},{616,11,13,0,0,11,68},{634,11,13,0,0,11,69},{652,11,13,0,0,11,70},{670,11,13,0,0,11,71},{688,11,13,0,0,11,72},{706,11,13,0,0,11,73},{724,11,13,0,0,11,74},{742,11,13,0,0,11,75},{760,11,13,0,0,11,76},{778,11,13,0,0,11,77},{796,11,13,0,0,11,78},{814,11,13,0,0,11,79},{832,11,13,0,0,11,80},{850,11,16,0,0,11,81},{872,11,13,0,0,11,82},{890,11,13,0,0,11,83},{908,11,13,0,0,11,84},{926,11,13,0,0,11,85},{944,11,13,0,0,11,86},{962,11,13,0,0,11,87},{980,11,13,0,0,11,88},{998,11,13,0,0,11,89},{1016,11,13,0,0,11,90},{1034,11,17,0,-2,11,91},{1058,11,17,0,-2,11,92},{1082,11,17,0,-2,11,93},{1106,11,13,0,0,11,94},{1124,11,2,0,13,11,95},{1127,11,14,0,-1,11,96},{1147,11,10,0,3,11,97},{1161,11,13,0,0,11,98},{1179,11,10,0,3,11,99},{1193,11,13,0,0,11,100},{1211,11,10,0,3,11,101},{1225,11,13,0,0,11,102},{1243,11,13,0,3,11,103},{1261,11,13,0,0,11,104},{1279,11,14,0,-1,11,105},{1299,11,17,0,-1,11,106},{1323,11,13,0,0,11,107},{1341,11,13,0,0,11,108},{1359,11,10,0,3,11,109},{1373,11,10,0,3,11,110},{1387,11,10,0,3,11,111},{1401,11,13,0,3,11,112},{1419,11,13,0,3,11,113},{1437,11,10,0,3,11,114},{1451,11,10,0,3,11,115},{1465,11,13,0,0,11,116},{1483,11,10,0,3,11,117},{1497,11,10,0,3,11,118},{1511,11,10,0,3,11,119},{1525,11,10,0,3,11,120},{1539,11,13,0,3,11,121},{1557,11,10,0,3,11,122},{1571,11,17,0,-2,11,123},{1595,11,17,0,-2,11,124},{1619,11,17,0,-2,11,125},{1643,11,8,0,5,11,126}}; +static const uint8_t f2_19_bits[]={6,0,192,24,3,0,96,12,1,128,48,6,0,192,0,0,0,224,28,0,51,6,96,204,25,131,48,102,0,0,0,0,0,0,0,0,0,0,0,0,12,97,152,99,63,247,254,49,134,32,140,49,143,253,255,152,195,48,198,0,0,0,128,16,15,131,248,251,153,51,32,116,7,128,124,7,192,92,9,153,51,174,63,131,224,16,2,0,0,60,51,51,25,152,205,135,248,30,128,12,0,248,7,240,121,134,204,102,99,51,48,240,15,1,248,57,131,8,56,1,128,60,7,230,103,198,60,97,135,60,63,193,230,12,1,128,48,6,0,192,24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,48,30,3,128,224,56,7,0,192,24,3,0,96,12,1,128,56,7,0,112,7,0,240,6,0,0,0,7,0,112,7,0,112,14,0,192,24,3,0,96,12,1,128,48,6,1,192,48,30,7,128,224,0,0,6,0,96,6,6,102,126,227,252,7,0,216,25,131,156,0,128,0,6,0,192,24,63,231,252,12,1,128,48,6,0,0,0,0,14,1,192,48,14,1,192,48,0,0,3,240,126,0,0,0,0,0,0,0,6,1,224,60,3,0,0,192,56,7,0,192,56,6,0,192,56,6,0,192,56,6,1,192,56,6,1,192,56,6,1,192,0,31,7,241,199,56,230,12,193,155,51,102,96,204,25,199,56,227,248,62,0,15,3,224,252,25,128,48,6,0,192,24,3,0,96,12,1,131,254,127,192,31,7,241,199,56,224,12,3,128,96,28,15,3,192,240,60,7,252,255,128,127,143,240,14,3,128,224,30,3,224,14,0,192,25,131,56,227,248,62,0,3,0,224,24,7,1,192,112,14,99,140,97,143,241,254,0,192,24,3,0,63,199,248,192,24,3,120,127,142,112,6,0,192,24,195,28,227,248,30,0,7,0,192,56,14,1,128,126,15,243,142,112,204,25,195,60,227,248,62,0,127,207,249,135,48,192,24,7,0,192,56,6,0,192,56,6,1,192,48,0,31,7,241,199,48,103,28,99,7,192,248,49,140,25,131,56,227,248,62,0,31,7,241,199,56,102,12,225,156,113,254,31,128,112,28,3,0,224,24,0,6,1,224,24,0,0,0,0,0,0,0,6,1,224,24,0,6,1,224,24,0,0,0,0,0,0,0,0,1,224,56,7,0,192,56,6,0,0,0,56,30,15,7,128,192,30,0,240,7,128,56,0,0,0,63,199,248,0,0,0,0,127,143,240,0,0,0,0,64,14,1,240,15,128,120,3,129,224,248,124,14,1,0,0,0,62,7,224,14,0,192,24,7,3,224,120,12,1,128,0,0,1,192,56,0,15,129,252,56,231,6,112,102,54,102,102,102,102,102,102,102,102,102,103,231,0,112,3,128,31,0,240,14,1,224,60,15,129,176,55,6,97,204,49,135,248,255,56,102,12,193,192,126,15,241,142,48,198,24,199,31,195,252,97,204,25,131,48,231,248,254,0,31,7,241,199,48,102,0,192,24,3,0,96,12,1,131,56,227,248,62,0,127,15,241,135,48,230,12,193,152,51,6,96,204,25,135,48,231,248,254,0,63,231,252,192,24,3,0,96,15,241,254,48,6,0,192,24,3,254,127,192,63,231,252,192,24,3,0,96,15,241,254,48,6,0,192,24,3,0,96,0,31,7,241,199,48,102,0,192,25,243,62,96,204,25,135,56,227,248,62,0,97,140,49,134,48,198,24,195,31,227,252,97,140,49,134,48,198,24,195,0,63,199,248,24,3,0,96,12,1,128,48,6,0,192,24,3,3,252,127,128,15,193,248,3,0,96,12,1,128,48,6,0,192,25,199,56,227,248,62,0,97,204,57,134,49,198,48,206,31,131,240,103,12,97,142,48,198,28,193,128,48,6,0,192,24,3,0,96,12,1,128,48,6,0,192,28,3,254,127,192,113,206,57,231,61,230,188,221,155,179,54,96,204,25,131,48,102,12,193,128,112,207,25,227,60,103,204,217,155,51,54,102,204,249,143,49,230,60,195,128,30,7,241,206,48,198,24,195,24,99,12,97,140,49,134,57,195,248,60,0,127,15,241,135,48,230,12,195,159,227,248,96,12,1,128,48,6,0,192,0,31,7,241,199,48,230,12,193,152,51,6,96,204,25,131,56,227,248,62,0,192,28,1,128,56,127,15,241,135,48,102,12,195,159,227,248,103,12,97,142,48,198,28,195,128,31,7,241,199,48,103,0,240,15,192,124,1,192,57,131,56,227,248,62,0,127,239,252,24,3,0,96,12,1,128,48,6,0,192,24,3,0,96,12,0,97,140,49,134,48,198,24,195,24,99,12,97,140,49,134,57,195,248,60,0,96,236,25,195,24,99,28,99,14,96,204,27,131,96,124,7,128,240,28,0,206,121,207,57,231,60,164,213,154,179,86,123,207,121,239,56,231,28,227,0,112,198,56,230,13,193,240,30,3,128,112,31,3,96,238,24,231,28,193,192,112,99,14,56,193,156,25,129,216,15,0,240,7,0,96,6,0,96,6,0,96,127,143,240,14,1,128,112,12,3,128,224,24,7,0,192,56,7,248,255,0,0,1,248,63,6,0,192,24,3,0,96,12,1,128,48,6,0,192,24,3,0,96,12,1,248,63,0,32,14,0,192,28,1,128,48,7,0,96,12,1,192,24,3,128,112,6,0,224,12,1,128,56,3,0,0,3,224,124,1,128,48,6,0,192,24,3,0,96,12,1,128,48,6,0,192,24,3,3,224,124,0,4,1,192,60,13,129,152,115,12,97,134,0,0,0,0,0,0,0,0,0,0,0,15,249,255,0,0,1,128,24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,7,240,199,0,97,252,127,156,51,6,113,199,248,115,0,96,12,1,128,55,135,248,231,24,99,12,97,140,49,134,57,199,248,222,0,31,7,241,199,56,102,0,192,24,3,134,113,199,240,124,0,1,128,48,6,14,195,248,231,24,99,12,97,140,49,134,57,195,248,59,0,31,7,241,199,48,103,252,255,152,3,128,113,199,240,124,0,7,193,248,56,6,7,252,255,131,0,96,12,1,128,48,6,0,192,24,0,29,135,241,206,56,198,24,195,24,99,140,115,135,240,118,0,192,56,63,7,192,112,14,1,192,59,135,248,247,28,99,140,113,142,49,198,56,199,24,227,0,12,1,128,48,0,0,0,248,31,0,96,12,1,128,48,6,0,192,24,31,243,254,3,128,112,4,0,3,248,127,0,96,12,1,128,48,6,0,192,24,3,0,96,28,3,143,225,248,0,48,3,0,48,3,14,48,195,28,51,131,240,63,3,56,49,131,28,48,195,14,252,31,128,48,6,0,192,24,3,0,96,12,1,128,48,7,0,126,7,192,111,143,249,219,51,102,108,205,153,179,54,102,204,217,155,0,111,15,241,206,56,198,24,195,24,99,12,97,140,49,134,0,31,7,241,199,56,230,12,193,152,51,142,113,199,240,124,0,103,15,241,206,56,198,24,195,24,99,12,115,143,241,188,48,6,0,192,24,0,28,135,241,198,56,198,8,193,24,35,140,113,135,240,114,0,64,8,1,0,32,51,135,248,227,156,115,6,96,12,1,128,48,6,0,192,0,31,15,241,206,48,7,128,126,0,224,12,113,135,240,124,0,12,1,128,48,63,231,252,24,3,0,96,12,1,128,48,7,0,252,15,128,97,140,49,134,48,198,24,195,24,99,140,115,135,224,120,0,96,206,24,195,24,227,152,115,6,224,216,31,1,224,56,0,198,102,102,110,102,228,106,198,172,107,199,188,57,195,156,49,128,112,199,56,110,15,128,240,28,3,192,248,57,134,57,195,0,96,238,24,199,28,195,152,55,6,192,248,15,1,192,24,7,0,192,24,7,0,127,143,240,14,3,128,224,24,7,1,192,112,15,241,254,0,0,0,120,31,3,128,96,12,1,128,48,6,15,129,248,3,0,96,12,1,128,48,7,0,248,15,0,12,1,128,48,6,0,192,24,3,0,96,12,1,128,48,6,0,192,24,3,0,96,12,1,128,0,15,1,240,6,0,192,24,3,0,96,14,0,248,31,7,0,192,24,3,0,96,28,15,129,224,0,56,207,153,219,51,230,56,0,0,0,0,0,0,0}; +static const Glyph f2_19_glyphs[]={{0,11,0,0,14,11,32},{0,11,14,0,0,11,33},{20,11,14,0,0,11,34},{40,11,14,0,0,11,35},{60,11,21,0,-4,11,36},{89,13,14,0,0,11,37},{112,12,14,0,0,11,38},{133,11,14,0,0,11,39},{153,11,20,0,-3,11,40},{181,11,20,0,-3,11,41},{209,12,12,0,2,11,42},{227,11,11,0,3,11,43},{243,11,6,0,12,11,44},{252,11,8,0,6,11,45},{263,11,4,0,10,11,46},{269,11,19,0,-3,11,47},{296,11,14,0,0,11,48},{316,11,14,0,0,11,49},{336,11,14,0,0,11,50},{356,11,14,0,0,11,51},{376,11,14,0,0,11,52},{396,11,14,0,0,11,53},{416,11,14,0,0,11,54},{436,11,14,0,0,11,55},{456,11,14,0,0,11,56},{476,11,14,0,0,11,57},{496,11,11,0,3,11,58},{512,11,15,0,3,11,59},{533,11,12,0,2,11,60},{550,11,10,0,4,11,61},{564,11,12,0,2,11,62},{581,11,14,0,0,11,63},{601,12,18,0,0,11,64},{628,11,14,0,0,11,65},{648,11,14,0,0,11,66},{668,11,14,0,0,11,67},{688,11,14,0,0,11,68},{708,11,14,0,0,11,69},{728,11,14,0,0,11,70},{748,11,14,0,0,11,71},{768,11,14,0,0,11,72},{788,11,14,0,0,11,73},{808,11,14,0,0,11,74},{828,11,14,0,0,11,75},{848,11,14,0,0,11,76},{868,11,14,0,0,11,77},{888,11,14,0,0,11,78},{908,11,14,0,0,11,79},{928,11,14,0,0,11,80},{948,11,18,0,0,11,81},{973,11,14,0,0,11,82},{993,11,14,0,0,11,83},{1013,11,14,0,0,11,84},{1033,11,14,0,0,11,85},{1053,11,14,0,0,11,86},{1073,11,14,0,0,11,87},{1093,11,14,0,0,11,88},{1113,12,14,-1,0,11,89},{1134,11,14,0,0,11,90},{1154,11,19,0,-3,11,91},{1181,11,19,0,-3,11,92},{1208,11,19,0,-3,11,93},{1235,11,15,0,-1,11,94},{1256,11,3,0,14,11,95},{1261,11,16,0,-2,11,96},{1283,11,11,0,3,11,97},{1299,11,14,0,0,11,98},{1319,11,11,0,3,11,99},{1335,11,14,0,0,11,100},{1355,11,11,0,3,11,101},{1371,11,14,0,0,11,102},{1391,11,15,0,3,11,103},{1412,11,14,0,0,11,104},{1432,11,16,0,-2,11,105},{1454,11,19,0,-1,11,106},{1481,12,14,0,0,11,107},{1502,11,14,0,0,11,108},{1522,11,11,0,3,11,109},{1538,11,11,0,3,11,110},{1554,11,11,0,3,11,111},{1570,11,15,0,3,11,112},{1591,11,15,0,3,11,113},{1612,11,11,0,3,11,114},{1628,11,11,0,3,11,115},{1644,11,14,0,0,11,116},{1664,11,11,0,3,11,117},{1680,11,11,0,3,11,118},{1696,12,11,0,3,11,119},{1713,11,11,0,3,11,120},{1729,11,15,0,3,11,121},{1750,11,11,0,3,11,122},{1766,11,19,0,-3,11,123},{1793,11,18,0,-2,11,124},{1818,11,19,0,-3,11,125},{1845,11,10,0,4,11,126}}; +static const uint8_t f2_20_bits[]={14,0,224,14,0,224,14,0,224,14,0,224,14,0,224,0,0,0,14,0,224,29,193,220,28,193,204,28,193,204,0,0,0,0,0,0,0,0,0,0,0,0,4,96,196,12,195,255,63,240,204,8,193,136,24,135,254,127,225,152,17,131,16,0,0,64,4,0,240,63,131,220,52,195,64,60,3,192,31,128,252,5,192,76,116,199,220,63,129,240,4,0,64,0,0,60,25,152,198,102,25,144,126,192,246,0,48,0,152,7,240,54,97,153,132,102,49,153,131,192,15,1,248,57,195,140,56,1,128,60,7,230,119,231,60,113,199,60,63,225,230,7,0,112,7,0,96,6,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,3,128,112,14,0,192,28,1,192,28,1,192,28,1,192,28,1,192,28,0,224,7,0,60,1,192,0,0,3,0,60,1,224,7,0,112,3,0,48,3,0,56,3,128,48,3,0,48,7,0,112,30,3,192,48,0,0,7,0,96,6,6,102,122,243,252,7,0,216,25,195,140,0,128,0,14,0,224,14,7,254,127,224,224,14,0,224,14,0,0,0,0,15,0,224,14,0,192,28,1,128,0,1,248,31,128,0,0,0,0,0,0,0,6,0,240,15,0,96,0,64,12,1,192,28,1,128,56,3,128,48,7,0,96,14,0,224,12,1,192,24,1,128,56,3,0,112,0,31,3,252,113,199,12,112,199,14,118,231,110,112,231,12,112,199,28,63,193,240,7,129,248,59,131,56,3,128,56,3,128,56,3,128,56,3,128,56,63,227,254,31,3,248,113,199,28,1,192,28,3,128,120,15,1,224,60,7,128,127,199,252,63,195,252,1,192,48,14,0,248,15,192,28,0,192,12,112,199,28,63,193,240,3,128,48,7,0,224,28,1,192,57,199,28,113,199,252,127,192,28,1,192,28,63,195,252,56,3,128,63,131,252,56,224,14,0,224,14,48,227,142,63,192,248,3,0,112,14,0,192,24,3,248,63,199,14,112,231,14,112,231,156,63,192,240,127,231,254,112,199,28,1,192,24,3,128,48,7,0,96,14,0,224,12,1,192,31,3,248,113,199,28,113,195,152,31,1,248,49,199,12,112,199,28,63,193,240,15,3,252,57,199,14,112,231,14,112,227,252,31,192,56,3,0,112,14,0,192,6,0,240,6,0,0,0,0,0,0,0,0,6,0,240,6,0,6,0,240,6,0,0,0,0,0,0,0,0,0,0,240,14,0,224,12,1,192,24,0,0,32,14,3,224,248,60,3,128,60,0,248,3,224,14,0,32,0,63,227,254,0,0,0,0,3,254,63,224,0,0,0,0,32,3,128,62,0,248,3,224,14,1,224,120,62,3,128,32,0,0,63,3,248,1,192,28,1,192,28,31,129,240,28,0,192,0,0,0,28,1,192,15,129,254,56,231,7,112,119,59,119,119,103,126,119,231,118,119,103,119,183,0,112,3,128,31,0,240,15,0,240,15,1,248,31,129,152,25,131,156,57,195,252,63,199,14,112,230,6,63,131,252,56,227,142,56,227,140,63,131,252,56,227,134,56,99,142,63,227,248,31,3,252,113,199,12,112,7,0,112,7,0,112,7,0,112,199,28,63,193,240,127,7,248,115,199,28,113,199,28,113,199,28,113,199,28,113,199,28,127,135,240,63,227,254,56,3,128,56,3,128,63,195,252,56,3,128,56,3,128,63,227,254,63,227,254,56,3,128,56,3,128,63,227,254,56,3,128,56,3,128,56,3,128,31,3,252,113,199,12,112,7,0,119,231,126,112,231,12,112,199,28,63,129,240,56,227,142,56,227,142,56,227,142,63,227,254,56,227,142,56,227,142,56,227,142,63,195,252,7,0,112,7,0,112,7,0,112,7,0,112,7,0,112,63,195,252,7,224,126,0,224,14,0,224,14,0,224,14,0,224,12,112,199,28,63,193,240,56,99,142,56,227,156,57,195,184,63,131,248,59,131,156,57,195,142,56,227,135,56,3,128,56,3,128,56,3,128,56,3,128,56,3,128,56,3,128,63,227,254,112,231,158,121,231,158,107,230,246,110,230,110,112,231,14,112,231,14,112,231,14,113,199,156,121,199,156,125,198,220,125,199,124,118,199,124,115,199,60,115,199,28,31,3,248,113,199,28,113,199,28,113,199,28,113,199,28,113,199,28,63,129,240,63,131,254,56,227,134,56,99,142,63,227,248,56,3,128,56,3,128,56,3,128,31,3,252,113,199,12,112,199,14,112,231,14,112,231,12,112,199,28,63,193,248,3,128,24,1,192,12,63,131,254,56,227,134,56,99,142,63,227,248,59,131,156,57,195,142,56,227,134,15,3,252,57,195,12,48,3,128,31,0,248,1,192,12,112,199,28,63,193,240,127,231,254,7,0,112,7,0,112,7,0,112,7,0,112,7,0,112,7,0,112,113,199,28,113,199,28,113,199,28,113,199,28,113,199,28,113,199,28,63,129,240,96,103,14,112,227,12,48,195,156,57,193,152,25,129,216,31,128,240,15,0,240,103,51,57,153,204,206,102,83,58,176,213,134,172,61,225,239,15,120,113,195,142,28,112,112,227,12,57,193,152,31,128,240,15,0,240,15,1,248,25,131,156,112,231,14,112,119,14,56,227,140,25,193,216,15,128,240,7,0,112,7,0,112,7,0,112,63,227,254,0,224,28,1,192,56,7,0,112,14,1,192,28,3,128,63,227,254,0,0,252,15,192,224,14,0,224,14,0,224,14,0,224,14,0,224,14,0,224,14,0,224,14,0,252,15,192,32,3,0,56,3,128,24,1,192,28,0,192,14,0,96,7,0,112,3,0,56,3,128,24,1,192,12,0,224,0,1,248,31,128,56,3,128,56,3,128,56,3,128,56,3,128,56,3,128,56,3,128,56,3,129,248,31,128,6,0,240,15,1,248,25,129,152,48,195,12,0,0,0,0,0,0,0,0,0,0,0,0,7,254,127,224,0,0,224,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,3,252,57,192,12,31,227,254,112,231,14,113,227,254,30,224,56,3,128,56,3,184,63,195,142,56,227,142,56,227,142,56,227,142,63,195,184,31,3,248,113,199,28,112,7,0,112,7,28,113,195,248,31,0,1,192,28,1,193,220,63,199,28,113,199,28,113,199,28,113,199,28,63,193,220,31,3,248,113,199,28,127,199,252,112,7,0,113,195,248,31,0,7,224,254,14,0,224,127,231,254,14,0,224,14,0,224,14,0,224,14,0,224,29,195,252,113,199,28,113,199,28,113,199,28,113,195,252,29,192,28,1,193,248,31,0,56,3,128,56,3,184,63,195,142,56,227,142,56,227,142,56,227,142,56,227,142,2,0,48,3,0,0,0,1,240,31,0,112,7,0,112,7,0,112,7,0,112,63,243,255,3,128,56,1,0,0,63,131,248,3,128,56,3,128,56,3,128,56,3,128,56,3,128,56,3,135,240,126,0,56,3,128,56,3,134,56,227,156,57,131,248,63,131,184,57,195,140,56,227,134,254,15,224,14,0,224,14,0,224,14,0,224,14,0,224,14,0,224,7,224,62,109,199,254,118,231,110,118,231,110,118,231,110,118,231,110,118,224,59,131,252,56,227,142,56,227,142,56,227,142,56,227,142,56,224,31,3,248,113,199,28,113,199,28,113,199,28,113,195,248,31,0,59,131,252,56,227,142,56,227,142,56,227,142,56,227,252,59,131,128,56,3,128,56,0,30,195,236,113,199,12,112,199,12,112,199,12,113,195,252,30,192,12,0,192,12,0,192,59,131,252,56,227,142,56,99,128,56,3,128,56,3,128,56,0,15,131,252,56,227,128,60,1,252,0,224,14,56,227,252,31,128,14,0,224,14,7,254,127,224,224,14,0,224,14,0,224,14,0,224,7,224,62,113,199,28,113,199,28,113,199,28,113,199,28,113,195,248,31,0,96,103,14,48,195,12,57,193,152,25,129,248,15,0,240,15,0,230,54,102,102,102,246,111,102,150,105,99,156,57,195,156,57,192,112,227,156,25,129,248,15,0,240,15,1,248,57,195,156,112,224,112,231,14,48,195,156,57,193,152,31,128,248,15,0,240,7,0,96,14,0,192,28,0,63,227,254,0,224,28,3,128,112,14,1,192,56,3,254,63,224,0,0,30,3,224,56,3,128,56,3,128,48,7,3,224,63,0,48,3,0,56,3,128,56,3,128,30,0,224,7,0,112,7,0,112,7,0,112,7,0,112,7,0,112,7,0,112,7,0,112,7,0,112,7,0,112,0,7,128,124,0,192,12,0,224,14,0,224,14,0,126,7,224,224,14,0,224,14,0,192,28,7,192,120,0,56,103,206,118,231,62,97,192,0,0,0,0,0,0,0}; +static const Glyph f2_20_glyphs[]={{0,12,0,0,14,12,32},{0,12,14,0,0,12,33},{21,12,14,0,0,12,34},{42,12,14,0,0,12,35},{63,12,21,0,-4,12,36},{95,14,14,0,0,12,37},{120,12,14,0,0,12,38},{141,12,14,0,0,12,39},{162,12,20,0,-3,12,40},{192,12,20,0,-3,12,41},{222,12,12,0,2,12,42},{240,12,11,0,3,12,43},{257,12,6,0,12,12,44},{266,12,8,0,6,12,45},{278,12,4,0,10,12,46},{284,12,19,0,-3,12,47},{313,12,14,0,0,12,48},{334,12,14,0,0,12,49},{355,12,14,0,0,12,50},{376,12,14,0,0,12,51},{397,12,14,0,0,12,52},{418,12,14,0,0,12,53},{439,12,14,0,0,12,54},{460,12,14,0,0,12,55},{481,12,14,0,0,12,56},{502,12,14,0,0,12,57},{523,12,11,0,3,12,58},{540,12,15,0,3,12,59},{563,12,12,0,2,12,60},{581,12,10,0,4,12,61},{596,12,12,0,2,12,62},{614,12,14,0,0,12,63},{635,12,18,0,0,12,64},{662,12,14,0,0,12,65},{683,12,14,0,0,12,66},{704,12,14,0,0,12,67},{725,12,14,0,0,12,68},{746,12,14,0,0,12,69},{767,12,14,0,0,12,70},{788,12,14,0,0,12,71},{809,12,14,0,0,12,72},{830,12,14,0,0,12,73},{851,12,14,0,0,12,74},{872,12,14,0,0,12,75},{893,12,14,0,0,12,76},{914,12,14,0,0,12,77},{935,12,14,0,0,12,78},{956,12,14,0,0,12,79},{977,12,14,0,0,12,80},{998,12,18,0,0,12,81},{1025,12,14,0,0,12,82},{1046,12,14,0,0,12,83},{1067,12,14,0,0,12,84},{1088,12,14,0,0,12,85},{1109,12,14,0,0,12,86},{1130,13,14,-1,0,12,87},{1153,12,14,0,0,12,88},{1174,12,14,0,0,12,89},{1195,12,14,0,0,12,90},{1216,12,19,0,-3,12,91},{1245,12,19,0,-3,12,92},{1274,12,19,0,-3,12,93},{1303,12,15,0,-1,12,94},{1326,12,3,0,14,12,95},{1331,12,16,0,-2,12,96},{1355,12,11,0,3,12,97},{1372,12,14,0,0,12,98},{1393,12,11,0,3,12,99},{1410,12,14,0,0,12,100},{1431,12,11,0,3,12,101},{1448,12,14,0,0,12,102},{1469,12,15,0,3,12,103},{1492,12,14,0,0,12,104},{1513,12,16,0,-2,12,105},{1537,12,19,0,-1,12,106},{1566,12,14,0,0,12,107},{1587,12,14,0,0,12,108},{1608,12,11,0,3,12,109},{1625,12,11,0,3,12,110},{1642,12,11,0,3,12,111},{1659,12,15,0,3,12,112},{1682,12,15,0,3,12,113},{1705,12,11,0,3,12,114},{1722,12,11,0,3,12,115},{1739,12,14,0,0,12,116},{1760,12,11,0,3,12,117},{1777,12,11,0,3,12,118},{1794,12,11,0,3,12,119},{1811,12,11,0,3,12,120},{1828,12,15,0,3,12,121},{1851,12,11,0,3,12,122},{1868,12,19,0,-3,12,123},{1897,12,18,0,-2,12,124},{1924,12,19,0,-3,12,125},{1953,12,10,0,4,12,126}}; +static const uint8_t f2_21_bits[]={7,0,56,1,192,14,0,112,3,128,28,0,224,7,0,56,1,192,0,0,0,0,0,30,0,240,29,192,238,7,112,59,129,204,14,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,48,49,1,24,24,195,255,159,252,51,129,136,8,192,198,31,252,255,225,156,12,64,102,6,48,0,0,16,0,128,12,1,248,31,224,235,142,92,114,1,208,15,192,63,0,124,1,112,11,142,92,114,225,254,7,240,14,0,32,1,0,0,0,60,25,248,198,99,25,152,102,193,155,3,248,6,192,3,0,27,192,223,131,102,25,152,198,99,31,152,60,15,0,254,15,112,113,131,128,12,0,112,7,192,126,115,187,28,248,227,135,28,61,240,255,131,206,7,0,56,1,192,14,0,112,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,30,1,224,30,0,224,14,0,112,3,128,28,0,224,7,0,56,1,192,14,0,112,1,128,14,0,60,0,240,3,128,0,0,32,1,192,15,0,28,0,112,3,128,12,0,112,3,128,28,0,224,7,0,56,1,192,12,0,224,15,0,240,15,0,112,0,0,0,7,0,56,1,192,204,231,175,31,240,28,1,176,29,193,198,4,48,0,0,0,0,7,0,56,1,192,14,7,254,63,240,28,0,224,7,0,56,0,0,0,0,7,0,120,3,128,28,0,192,6,0,112,0,0,0,254,7,240,0,0,0,0,0,0,0,0,0,0,6,0,120,3,192,12,0,0,224,7,0,48,3,128,28,0,192,14,0,112,7,0,56,1,128,28,0,224,6,0,112,3,128,24,1,192,14,0,224,0,15,1,254,30,112,225,199,14,56,113,195,143,220,118,227,135,28,56,225,199,14,60,224,255,1,224,7,128,124,15,224,119,2,56,1,192,14,0,112,3,128,28,0,224,7,0,56,1,192,255,199,254,15,1,254,14,120,225,199,14,0,112,3,128,56,3,192,28,1,192,28,1,192,28,0,255,135,252,127,195,254,0,112,7,0,112,7,0,62,1,248,0,224,7,0,56,193,199,14,60,240,255,1,224,1,192,28,1,192,14,0,224,14,0,112,7,28,112,227,135,28,56,255,199,254,0,112,3,128,28,63,193,254,14,0,112,3,128,28,0,255,7,248,56,224,7,0,56,1,195,14,28,240,255,1,240,3,0,56,3,128,28,1,192,12,0,254,7,248,121,227,135,28,56,225,199,14,28,224,255,1,224,127,227,255,28,56,225,198,12,0,224,6,0,112,3,128,24,1,192,12,0,224,7,0,48,3,128,15,1,254,14,112,225,199,14,24,96,255,3,240,31,129,206,28,56,225,199,14,60,240,255,3,240,15,1,254,14,112,225,199,14,56,113,195,143,60,63,192,254,0,96,7,0,112,3,128,56,1,128,6,0,120,3,192,12,0,0,0,0,0,0,0,6,0,120,3,192,12,0,6,0,120,3,192,12,0,0,0,0,0,0,0,0,0,120,3,128,28,0,224,6,0,112,3,0,0,0,3,128,248,31,3,224,28,0,224,7,224,7,192,15,0,28,0,0,0,0,0,1,255,15,248,0,0,0,0,0,255,135,252,0,0,0,0,0,32,1,192,15,128,31,0,62,0,120,3,192,124,15,129,240,14,0,64,0,0,0,31,0,254,0,112,1,192,14,0,112,7,129,248,15,128,112,3,128,12,0,0,0,0,60,1,224,7,192,255,15,60,112,103,3,185,221,223,238,231,119,59,185,221,206,238,119,115,185,253,192,14,0,56,1,224,7,224,15,0,15,0,120,3,224,31,0,216,14,192,103,3,56,24,193,198,15,248,127,195,14,56,49,193,142,14,63,129,255,14,56,112,227,135,28,48,227,135,248,63,225,199,14,28,112,227,135,28,120,255,135,240,15,128,254,15,56,112,195,134,28,0,224,7,0,56,1,192,14,0,112,195,134,30,112,127,1,240,63,129,254,14,120,112,195,135,28,56,225,199,14,56,113,195,142,28,112,227,134,28,240,255,7,240,63,225,255,14,0,112,3,128,28,0,224,7,252,63,225,192,14,0,112,3,128,28,0,255,135,252,63,241,255,142,0,112,3,128,28,0,224,7,252,63,225,192,14,0,112,3,128,28,0,224,7,0,15,128,254,15,56,112,195,134,28,0,224,7,0,59,241,223,142,28,112,227,134,30,112,127,129,240,56,113,195,142,28,112,227,135,28,56,225,199,254,63,241,195,142,28,112,227,135,28,56,225,199,14,63,225,255,1,192,14,0,112,3,128,28,0,224,7,0,56,1,192,14,0,112,3,128,255,135,252,7,224,63,0,56,1,192,14,0,112,3,128,28,0,224,7,0,56,225,199,14,28,224,255,1,224,56,113,195,142,56,113,195,156,28,224,238,7,240,63,129,206,14,112,115,131,142,28,112,225,199,14,56,1,192,14,0,112,3,128,28,0,224,7,0,56,1,192,14,0,112,3,128,28,0,255,135,252,112,243,199,158,60,243,231,223,54,185,253,206,238,119,115,131,156,28,224,231,7,56,57,193,206,14,60,113,227,143,28,120,227,231,31,56,249,199,110,59,113,219,142,124,115,227,159,28,248,227,199,30,15,128,255,15,56,112,195,135,28,56,225,199,14,56,113,195,142,28,112,227,134,30,112,127,129,240,63,129,255,14,56,112,227,135,28,56,225,199,28,63,225,252,14,0,112,3,128,28,0,224,7,0,15,1,252,30,112,225,135,14,56,113,195,142,28,112,227,135,28,56,225,199,12,60,224,254,1,240,3,128,12,0,112,1,192,63,129,255,14,56,112,227,135,28,56,225,199,28,63,225,254,14,112,115,131,142,28,112,225,199,14,15,129,254,14,120,225,199,0,24,0,240,3,240,15,192,15,0,56,1,199,14,60,240,255,1,224,127,243,255,129,192,14,0,112,3,128,28,0,224,7,0,56,1,192,14,0,112,3,128,28,0,224,56,113,195,142,28,112,227,135,28,56,225,199,14,56,113,195,142,28,112,227,134,30,112,127,129,240,112,115,131,28,24,97,195,14,28,112,227,3,24,25,192,238,7,96,27,0,248,7,192,60,1,224,99,59,57,217,204,206,102,115,50,217,214,206,182,53,177,237,143,44,121,227,206,30,112,227,135,28,112,113,199,14,48,51,129,216,7,192,60,0,224,15,0,124,7,224,59,131,156,28,113,195,142,14,96,115,131,12,56,113,195,140,14,224,118,1,240,15,0,56,1,192,14,0,112,3,128,28,0,224,63,225,255,0,56,3,128,28,1,192,14,0,224,7,0,112,3,128,56,1,128,28,0,255,135,252,15,192,126,3,128,28,0,224,7,0,56,1,192,14,0,112,3,128,28,0,224,7,0,56,1,192,14,0,112,3,240,31,128,112,1,192,14,0,48,1,192,14,0,48,1,192,14,0,48,1,192,14,0,56,1,192,6,0,56,1,192,6,0,56,1,192,63,129,252,0,224,7,0,56,1,192,14,0,112,3,128,28,0,224,7,0,56,1,192,14,0,112,3,128,28,15,224,127,0,2,0,56,3,192,27,1,216,12,224,227,6,28,48,96,0,0,0,0,0,0,0,0,0,0,0,0,3,255,31,248,14,0,48,1,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,128,254,14,120,97,192,14,15,240,255,142,28,112,227,207,15,248,61,192,56,1,192,14,0,112,3,188,31,240,243,135,14,56,113,195,142,28,112,227,135,30,112,255,135,120,15,1,254,30,112,225,199,0,56,1,192,14,0,112,227,206,15,240,30,0,0,224,7,0,56,1,193,238,31,241,231,142,28,112,227,135,28,56,225,199,14,60,240,251,131,220,15,1,252,30,112,225,135,14,63,241,255,142,0,112,3,206,15,240,62,0,3,224,63,3,128,28,0,224,7,1,255,143,252,14,0,112,3,128,28,0,224,7,0,56,1,192,15,112,251,143,60,112,227,135,28,56,225,199,14,56,240,255,135,220,0,224,7,0,112,63,129,248,56,1,192,14,0,112,3,188,31,240,243,135,14,56,113,195,142,28,112,227,135,28,56,225,199,14,3,128,28,0,224,0,0,0,15,192,126,0,112,3,128,28,0,224,7,0,56,1,192,14,7,254,63,240,1,192,14,0,112,0,0,0,15,224,127,0,56,1,192,14,0,112,3,128,28,0,224,7,0,56,1,192,14,0,240,127,3,240,0,56,1,192,14,0,112,3,135,28,112,227,135,56,59,129,252,15,224,115,131,156,28,112,227,135,14,254,7,240,3,128,28,0,224,7,0,56,1,192,14,0,112,3,128,28,0,224,7,0,31,192,126,127,195,255,29,156,236,231,103,59,57,217,206,206,118,115,179,157,156,236,224,59,193,255,15,56,112,227,135,28,56,225,199,14,56,113,195,142,28,112,224,31,1,254,30,112,225,135,14,56,113,195,142,28,112,195,206,15,240,62,0,59,193,255,15,56,112,227,135,28,56,225,199,14,56,113,231,15,248,119,131,128,28,0,224,7,0,15,112,251,143,60,112,227,135,28,56,225,199,14,56,113,231,135,252,30,224,7,0,56,1,192,14,59,193,255,15,56,112,227,135,28,0,224,7,0,56,1,192,14,0,112,0,15,192,255,14,56,112,3,128,15,192,31,128,12,48,113,199,15,248,31,128,14,0,112,3,128,255,199,254,7,0,56,1,192,14,0,112,3,128,28,0,224,3,240,15,128,56,113,195,142,28,112,227,135,28,56,225,199,14,56,97,231,7,248,31,0,112,115,131,12,56,113,195,140,12,224,103,3,176,13,128,124,3,192,30,0,230,51,57,153,204,206,102,87,54,177,181,135,172,57,225,207,14,120,113,128,112,225,199,7,112,63,0,248,3,128,60,1,240,29,193,206,14,56,224,224,112,115,131,14,56,113,195,140,14,224,119,1,240,15,128,120,1,192,14,0,96,7,0,56,1,128,63,225,255,0,120,3,128,56,3,128,28,1,192,28,1,192,15,248,127,192,0,224,15,0,224,7,0,56,1,192,14,0,112,7,1,240,15,192,6,0,56,1,192,14,0,112,3,128,28,0,120,1,192,7,0,56,1,192,14,0,112,3,128,28,0,224,7,0,56,1,192,14,0,112,3,128,28,0,224,7,0,56,1,192,14,0,120,3,224,7,0,28,0,224,7,0,56,1,192,15,0,63,1,248,28,0,224,7,0,56,1,192,14,0,224,31,0,240,0,60,99,243,157,220,231,198,28,0,0,0,0,0,0,0,0,0}; +static const Glyph f2_21_glyphs[]={{0,13,0,0,16,13,32},{0,13,16,0,0,13,33},{26,13,16,0,0,13,34},{52,13,16,0,0,13,35},{78,13,23,0,-3,13,36},{116,14,16,0,0,13,37},{144,13,16,0,0,13,38},{170,13,16,0,0,13,39},{196,13,21,0,-2,13,40},{231,13,21,0,-2,13,41},{266,13,13,0,3,13,42},{288,13,12,0,4,13,43},{308,13,7,0,13,13,44},{320,13,9,0,7,13,45},{335,13,4,0,12,13,46},{342,13,20,0,-2,13,47},{375,13,16,0,0,13,48},{401,13,16,0,0,13,49},{427,13,16,0,0,13,50},{453,13,16,0,0,13,51},{479,13,16,0,0,13,52},{505,13,16,0,0,13,53},{531,13,16,0,0,13,54},{557,13,16,0,0,13,55},{583,13,16,0,0,13,56},{609,13,16,0,0,13,57},{635,13,12,0,4,13,58},{655,13,16,0,4,13,59},{681,13,13,0,3,13,60},{703,13,11,0,5,13,61},{721,13,13,0,3,13,62},{743,13,16,0,0,13,63},{769,13,20,0,0,13,64},{802,13,16,0,0,13,65},{828,13,16,0,0,13,66},{854,13,16,0,0,13,67},{880,13,16,0,0,13,68},{906,13,16,0,0,13,69},{932,13,16,0,0,13,70},{958,13,16,0,0,13,71},{984,13,16,0,0,13,72},{1010,13,16,0,0,13,73},{1036,13,16,0,0,13,74},{1062,13,16,0,0,13,75},{1088,13,16,0,0,13,76},{1114,13,16,0,0,13,77},{1140,13,16,0,0,13,78},{1166,13,16,0,0,13,79},{1192,13,16,0,0,13,80},{1218,13,20,0,0,13,81},{1251,13,16,0,0,13,82},{1277,13,16,0,0,13,83},{1303,13,16,0,0,13,84},{1329,13,16,0,0,13,85},{1355,13,16,0,0,13,86},{1381,13,16,0,0,13,87},{1407,13,16,0,0,13,88},{1433,13,16,0,0,13,89},{1459,13,16,0,0,13,90},{1485,13,20,0,-2,13,91},{1518,13,20,0,-2,13,92},{1551,13,20,0,-2,13,93},{1584,13,16,0,0,13,94},{1610,13,3,0,16,13,95},{1615,13,17,0,-1,13,96},{1643,13,12,0,4,13,97},{1663,13,16,0,0,13,98},{1689,13,12,0,4,13,99},{1709,13,16,0,0,13,100},{1735,13,12,0,4,13,101},{1755,13,16,0,0,13,102},{1781,13,16,0,4,13,103},{1807,13,16,0,0,13,104},{1833,13,17,0,-1,13,105},{1861,13,21,0,-1,13,106},{1896,13,16,0,0,13,107},{1922,13,16,0,0,13,108},{1948,13,12,0,4,13,109},{1968,13,12,0,4,13,110},{1988,13,12,0,4,13,111},{2008,13,16,0,4,13,112},{2034,13,16,0,4,13,113},{2060,13,12,0,4,13,114},{2080,13,12,0,4,13,115},{2100,13,15,0,1,13,116},{2125,13,12,0,4,13,117},{2145,13,12,0,4,13,118},{2165,13,12,0,4,13,119},{2185,13,12,0,4,13,120},{2205,13,16,0,4,13,121},{2231,13,12,0,4,13,122},{2251,13,20,0,-2,13,123},{2284,13,20,0,-2,13,124},{2317,13,20,0,-2,13,125},{2350,13,10,0,6,13,126}}; +static const uint8_t f2_22_bits[]={7,0,56,1,192,14,0,112,3,128,28,0,224,7,0,56,1,192,0,0,0,0,0,30,0,240,28,224,231,7,56,57,193,206,14,112,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,48,49,1,24,24,195,255,159,252,51,129,136,8,192,198,31,252,255,225,156,12,64,102,6,48,0,0,16,0,128,12,1,252,31,241,235,142,78,114,1,208,15,192,63,128,126,1,112,9,206,78,114,241,255,7,240,14,0,32,1,0,0,0,60,12,252,49,152,195,49,134,102,12,216,15,48,12,192,3,0,12,224,31,240,108,97,152,198,49,140,127,48,56,15,0,254,15,112,113,131,128,12,0,112,7,192,126,115,187,156,248,227,135,28,61,240,255,131,206,7,0,56,1,192,14,0,112,3,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,56,7,192,56,3,128,24,1,192,14,0,112,3,128,28,0,224,7,0,56,1,192,7,0,56,0,240,3,192,14,0,0,0,0,128,7,0,60,0,112,1,192,14,0,56,1,192,14,0,112,3,128,28,0,224,7,0,48,3,128,60,3,192,60,1,192,0,0,7,0,56,1,192,204,111,175,31,240,28,1,176,29,193,199,4,48,0,0,0,0,7,0,56,1,192,14,7,255,63,248,28,0,224,7,0,56,0,0,0,0,7,0,56,3,192,28,0,224,6,0,112,0,0,0,254,7,240,0,0,0,0,0,0,0,0,0,0,3,0,60,1,224,6,0,0,112,7,0,56,1,192,28,0,224,6,0,112,3,128,56,1,192,12,0,224,7,0,112,3,128,28,1,192,14,0,96,0,15,1,254,30,120,225,199,14,56,113,195,142,252,118,227,135,28,56,225,199,14,60,240,255,1,224,7,128,124,15,224,119,2,56,1,192,14,0,112,3,128,28,0,224,7,0,56,1,192,255,199,254,15,1,254,14,120,225,199,14,0,112,3,128,56,3,192,60,3,192,60,3,192,60,1,255,143,252,63,225,255,0,120,7,128,120,7,0,62,1,248,1,224,7,0,56,193,199,14,60,240,255,1,224,1,192,28,0,224,14,0,224,15,0,112,7,28,120,227,135,28,56,255,199,254,0,112,3,128,28,63,225,255,14,0,112,3,128,28,0,255,7,252,56,240,3,128,28,0,227,135,30,112,255,129,240,3,128,56,1,192,28,1,192,14,0,254,7,252,120,227,131,156,28,224,231,6,30,240,127,1,240,127,243,255,156,56,225,199,14,0,224,7,0,48,3,128,28,1,192,14,0,96,7,0,56,3,128,15,129,254,14,120,225,199,14,24,112,247,1,240,31,193,199,28,24,224,231,7,60,240,255,129,240,15,129,254,15,120,224,199,7,56,57,193,207,188,63,224,255,0,112,7,0,56,3,128,56,1,192,3,0,60,1,224,6,0,0,0,0,0,0,0,3,0,60,1,224,6,0,3,0,60,1,224,6,0,0,0,0,0,0,0,0,0,60,1,192,14,0,112,7,0,56,1,128,0,16,3,128,124,31,131,240,28,0,240,7,224,7,192,15,128,28,0,32,0,0,0,1,255,143,252,0,0,0,0,0,255,199,254,0,0,0,0,0,32,1,192,15,128,31,128,63,0,56,3,192,126,15,129,240,14,0,64,0,0,0,31,128,254,0,120,0,224,7,0,56,3,129,252,15,192,112,3,128,0,0,0,0,0,60,1,224,7,192,255,143,60,112,119,3,184,221,223,238,231,119,59,185,221,206,238,119,115,185,237,192,14,0,56,1,224,7,224,15,0,7,0,124,3,224,31,0,220,14,224,119,3,152,24,225,199,15,248,127,195,7,56,57,193,206,14,63,193,255,14,60,112,227,135,28,56,227,135,248,63,225,199,142,28,112,227,135,28,120,255,135,240,7,128,255,15,60,112,227,131,28,0,224,7,0,56,1,192,14,0,112,99,135,30,120,127,128,240,63,129,255,14,56,112,227,135,28,56,225,199,14,56,113,195,142,28,112,227,135,28,112,255,135,240,63,241,255,142,0,112,3,128,28,0,224,7,252,63,225,192,14,0,112,3,128,28,0,255,199,254,63,241,255,142,0,112,3,128,28,0,224,7,254,63,241,192,14,0,112,3,128,28,0,224,7,0,7,128,255,15,56,112,227,135,28,0,224,7,0,57,241,207,142,28,112,227,135,30,120,127,128,240,56,113,195,142,28,112,227,135,28,56,225,199,254,63,241,195,142,28,112,227,135,28,56,225,199,14,63,225,255,1,192,14,0,112,3,128,28,0,224,7,0,56,1,192,14,0,112,3,128,255,135,252,7,240,63,128,28,0,224,7,0,56,1,192,14,0,112,3,128,28,224,231,7,28,240,255,1,240,56,113,195,142,28,113,195,142,28,224,231,7,240,63,129,206,14,112,113,195,142,28,56,225,199,7,56,1,192,14,0,112,3,128,28,0,224,7,0,56,1,192,14,0,112,3,128,28,0,255,199,254,120,243,199,158,60,243,231,223,62,249,221,206,238,119,115,131,156,28,224,231,7,56,57,193,206,14,60,113,227,143,28,124,227,231,31,56,237,199,110,59,113,219,142,124,115,227,159,28,120,227,199,30,7,128,255,15,60,112,227,135,28,56,225,199,14,56,113,195,142,28,112,227,135,30,120,127,128,240,63,193,255,14,60,112,115,131,156,28,224,231,30,63,225,254,14,0,112,3,128,28,0,224,7,0,15,1,254,30,112,225,199,14,56,113,195,142,28,112,227,135,28,56,225,199,14,60,224,255,1,240,3,128,14,0,112,1,192,63,129,255,14,56,112,227,135,28,56,225,199,28,63,225,252,14,112,115,131,140,28,112,225,135,14,15,129,255,14,120,224,231,0,56,0,240,3,240,15,224,15,0,28,0,231,7,60,240,255,1,240,127,243,255,129,192,14,0,112,3,128,28,0,224,7,0,56,1,192,14,0,112,3,128,28,0,224,56,113,195,142,28,112,227,135,28,56,225,199,14,56,113,195,142,28,112,227,135,30,120,127,128,248,112,51,131,156,28,96,227,134,28,112,227,131,28,28,192,238,7,112,27,128,248,7,192,62,0,240,99,27,57,217,206,206,118,115,50,217,214,206,182,117,177,237,143,44,121,227,207,30,112,227,135,28,112,113,195,142,56,57,129,220,7,192,62,0,224,7,128,124,3,240,59,129,142,28,113,193,206,14,96,115,131,156,56,113,195,140,14,224,118,1,240,15,128,56,1,192,14,0,112,3,128,28,0,224,63,225,255,0,56,3,128,28,1,192,14,0,224,7,0,112,3,128,56,1,128,28,0,255,199,254,15,192,126,3,128,28,0,224,7,0,56,1,192,14,0,112,3,128,28,0,224,7,0,56,1,192,14,0,112,3,240,31,128,48,1,192,14,0,56,1,192,14,0,56,1,192,6,0,56,1,192,7,0,56,1,192,7,0,56,0,224,7,0,56,0,224,63,129,252,0,224,7,0,56,1,192,14,0,112,3,128,28,0,224,7,0,56,1,192,14,0,112,3,128,28,15,224,127,0,2,0,56,3,224,27,0,220,14,96,99,135,12,48,96,0,0,0,0,0,0,0,0,0,0,0,0,3,255,159,252,14,0,56,0,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,128,254,14,120,97,192,14,15,240,255,142,28,112,227,207,15,248,61,192,56,1,192,14,0,112,3,188,31,240,243,199,14,56,113,195,142,28,112,227,135,30,120,255,135,120,7,192,255,15,60,112,227,128,28,0,224,7,0,56,113,231,135,248,15,128,0,112,3,128,28,0,224,247,15,248,243,199,14,56,113,195,142,28,112,227,135,30,120,127,193,238,7,128,255,15,56,112,227,135,31,248,255,199,0,56,1,231,135,248,15,0,3,240,63,131,192,28,0,224,7,1,255,207,254,14,0,112,3,128,28,0,224,7,0,56,1,192,15,112,255,143,60,112,227,135,28,56,225,199,14,60,240,255,131,220,0,224,7,0,120,63,129,248,56,1,192,14,0,112,3,188,31,240,243,135,14,56,113,195,142,28,112,227,135,28,56,225,199,14,3,0,60,0,192,0,0,0,31,192,254,0,112,3,128,28,0,224,7,0,56,1,192,14,7,255,63,248,0,192,15,0,48,0,0,0,15,224,127,0,56,1,192,14,0,112,3,128,28,0,224,7,0,56,1,192,14,0,240,127,3,240,0,56,1,192,14,0,112,3,135,28,56,227,135,56,57,193,252,15,224,115,131,142,28,112,225,199,14,127,3,248,1,192,14,0,112,3,128,28,0,224,7,0,56,1,192,14,0,112,3,192,15,224,63,127,227,255,157,156,236,231,103,59,57,217,206,206,118,115,179,157,156,236,224,59,193,255,15,56,112,227,135,28,56,225,199,14,56,113,195,142,28,112,224,7,128,255,15,60,112,227,135,28,56,225,199,14,56,113,231,135,248,15,0,59,193,255,15,60,112,227,135,28,56,225,199,14,56,113,231,143,248,119,131,128,28,0,224,7,0,15,112,255,143,60,112,227,135,28,56,225,199,14,56,113,231,135,252,30,224,7,0,56,1,192,14,59,193,255,15,60,112,227,135,28,0,224,7,0,56,1,192,14,0,112,0,15,192,255,15,60,112,3,128,15,224,31,128,14,48,113,199,143,248,31,128,14,0,112,3,128,255,231,255,7,0,56,1,192,14,0,112,3,128,28,0,240,3,248,15,192,56,113,195,142,28,112,227,135,28,56,225,199,14,56,113,231,135,248,15,0,112,115,131,140,28,113,195,142,12,112,115,3,184,13,192,124,3,224,15,0,103,59,57,217,204,206,102,83,50,217,214,199,190,60,225,199,14,56,113,192,112,113,199,7,112,59,128,248,3,128,30,1,240,29,193,231,14,56,224,224,112,115,131,142,24,113,193,142,14,96,119,1,248,15,128,60,1,224,14,0,112,3,0,56,1,192,63,241,255,128,60,1,192,28,1,192,28,1,192,28,1,224,15,252,127,224,0,240,15,128,240,7,0,56,1,192,14,0,112,7,3,240,31,192,6,0,56,1,192,14,0,112,3,128,30,0,124,1,224,7,0,56,1,192,14,0,112,3,128,28,0,224,7,0,56,1,192,14,0,112,3,128,28,0,224,7,0,56,1,192,14,0,120,3,224,7,128,28,0,224,7,0,56,1,192,15,0,63,1,248,28,0,224,7,0,56,1,192,14,0,240,31,0,240,0,60,51,243,157,220,231,230,30,0,0,0,0,0,0,0,0,0}; +static const Glyph f2_22_glyphs[]={{0,13,0,0,16,13,32},{0,13,16,0,0,13,33},{26,13,16,0,0,13,34},{52,13,16,0,0,13,35},{78,13,23,0,-3,13,36},{116,15,16,0,0,13,37},{146,13,16,0,0,13,38},{172,13,16,0,0,13,39},{198,13,22,0,-3,13,40},{234,13,22,0,-3,13,41},{270,13,13,0,3,13,42},{292,13,12,0,4,13,43},{312,13,7,0,13,13,44},{324,13,9,0,7,13,45},{339,13,4,0,12,13,46},{346,13,20,0,-2,13,47},{379,13,16,0,0,13,48},{405,13,16,0,0,13,49},{431,13,16,0,0,13,50},{457,13,16,0,0,13,51},{483,13,16,0,0,13,52},{509,13,16,0,0,13,53},{535,13,16,0,0,13,54},{561,13,16,0,0,13,55},{587,13,16,0,0,13,56},{613,13,16,0,0,13,57},{639,13,12,0,4,13,58},{659,13,16,0,4,13,59},{685,13,13,0,3,13,60},{707,13,11,0,5,13,61},{725,13,13,0,3,13,62},{747,13,16,0,0,13,63},{773,13,20,0,0,13,64},{806,13,16,0,0,13,65},{832,13,16,0,0,13,66},{858,13,16,0,0,13,67},{884,13,16,0,0,13,68},{910,13,16,0,0,13,69},{936,13,16,0,0,13,70},{962,13,16,0,0,13,71},{988,13,16,0,0,13,72},{1014,13,16,0,0,13,73},{1040,13,16,0,0,13,74},{1066,13,16,0,0,13,75},{1092,13,16,0,0,13,76},{1118,13,16,0,0,13,77},{1144,13,16,0,0,13,78},{1170,13,16,0,0,13,79},{1196,13,16,0,0,13,80},{1222,13,20,0,0,13,81},{1255,13,16,0,0,13,82},{1281,13,16,0,0,13,83},{1307,13,16,0,0,13,84},{1333,13,16,0,0,13,85},{1359,13,16,0,0,13,86},{1385,13,16,0,0,13,87},{1411,13,16,0,0,13,88},{1437,13,16,0,0,13,89},{1463,13,16,0,0,13,90},{1489,13,20,0,-2,13,91},{1522,13,20,0,-2,13,92},{1555,13,20,0,-2,13,93},{1588,13,16,0,0,13,94},{1614,13,3,0,16,13,95},{1619,13,17,0,-1,13,96},{1647,13,12,0,4,13,97},{1667,13,16,0,0,13,98},{1693,13,12,0,4,13,99},{1713,13,16,0,0,13,100},{1739,13,12,0,4,13,101},{1759,13,16,0,0,13,102},{1785,13,16,0,4,13,103},{1811,13,16,0,0,13,104},{1837,13,17,0,-1,13,105},{1865,13,21,0,-1,13,106},{1900,13,16,0,0,13,107},{1926,13,16,0,0,13,108},{1952,13,12,0,4,13,109},{1972,13,12,0,4,13,110},{1992,13,12,0,4,13,111},{2012,13,16,0,4,13,112},{2038,13,16,0,4,13,113},{2064,13,12,0,4,13,114},{2084,13,12,0,4,13,115},{2104,13,15,0,1,13,116},{2129,13,12,0,4,13,117},{2149,13,12,0,4,13,118},{2169,13,12,0,4,13,119},{2189,13,12,0,4,13,120},{2209,13,16,0,4,13,121},{2235,13,12,0,4,13,122},{2255,13,20,0,-2,13,123},{2288,13,20,0,-2,13,124},{2321,13,20,0,-2,13,125},{2354,13,10,0,6,13,126}}; +static const uint8_t f2_24_bits[]={7,0,28,0,112,1,192,7,0,28,0,112,1,192,7,0,28,0,112,1,192,0,0,0,0,0,0,192,7,128,30,0,28,224,115,129,206,7,56,28,224,115,129,206,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,56,24,192,99,1,140,63,252,255,243,255,195,24,12,96,49,128,198,31,254,127,249,255,225,140,6,48,24,192,227,0,0,0,8,0,32,0,128,15,128,255,131,254,28,188,114,113,200,7,160,15,192,63,192,127,192,63,0,158,2,56,8,231,35,158,156,63,240,127,128,112,0,128,2,0,8,0,28,6,62,12,99,28,99,24,99,48,99,112,62,96,28,192,1,128,1,128,3,56,6,252,14,198,12,198,24,198,56,198,48,252,96,56,15,128,63,192,255,193,195,131,134,7,0,7,0,15,0,62,0,254,57,206,227,159,199,31,14,30,28,60,63,252,63,184,62,56,3,128,14,0,56,0,224,3,128,14,0,56,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,3,192,31,0,240,7,128,28,0,112,3,128,14,0,56,0,224,3,128,14,0,56,0,224,3,128,14,0,60,0,112,1,224,3,224,7,192,15,0,8,16,0,240,3,224,3,192,7,128,14,0,56,0,112,1,192,7,0,28,0,112,1,192,7,0,28,0,112,1,192,15,0,56,1,224,31,0,248,3,192,4,0,3,128,7,0,14,1,28,71,179,207,175,135,252,1,192,6,192,29,192,113,192,227,128,130,0,0,0,0,0,7,0,28,0,112,1,192,7,1,255,231,255,159,254,7,0,28,0,112,1,192,0,0,0,0,7,128,30,0,112,1,192,15,0,56,0,224,3,0,31,224,127,129,254,0,0,0,0,0,0,0,0,0,0,0,0,0,3,128,31,0,124,1,240,3,128,0,48,1,224,7,0,28,0,240,3,128,14,0,112,1,192,7,0,56,0,224,3,128,28,0,112,3,192,14,0,56,1,224,7,0,28,0,224,3,128,14,0,7,192,127,193,255,15,30,56,56,224,227,131,142,238,59,184,238,227,131,142,14,56,56,224,227,199,135,252,31,240,31,0,3,192,63,1,252,15,112,49,192,135,0,28,0,112,1,192,7,0,28,0,112,1,192,7,0,28,15,254,63,248,255,224,15,128,127,131,254,30,60,112,113,193,192,7,0,60,0,224,7,0,60,1,224,15,0,120,3,192,31,252,127,241,255,192,63,240,255,195,255,0,120,3,192,30,0,120,1,248,7,224,3,192,7,0,28,0,113,193,199,143,15,248,63,192,62,0,0,112,3,128,30,0,240,3,128,30,0,240,3,142,30,56,240,227,131,142,14,63,248,255,227,255,128,14,0,56,0,224,63,240,255,195,255,14,0,56,0,224,3,190,15,252,63,248,225,224,3,128,14,0,56,192,227,135,143,252,31,240,63,0,1,192,14,0,120,1,192,14,0,120,1,192,15,248,63,241,225,199,3,156,14,112,57,192,231,135,143,252,31,224,63,0,127,249,255,231,255,28,28,112,112,3,128,14,0,56,1,192,7,0,56,0,224,3,128,28,0,112,3,128,14,0,120,0,7,192,127,193,255,15,30,56,56,224,225,199,7,188,7,192,63,129,199,14,14,56,56,224,227,199,135,252,31,240,31,0,15,192,127,131,255,30,30,112,57,192,231,3,158,30,63,248,255,192,255,0,56,1,224,7,0,56,1,224,7,0,56,0,3,0,30,0,120,0,192,0,0,0,0,0,0,0,0,0,0,0,48,1,224,7,128,12,0,3,128,31,0,124,0,224,0,0,0,0,0,0,0,0,0,0,0,60,1,224,7,128,30,0,112,3,192,14,0,56,0,0,0,0,32,3,128,62,3,240,126,3,224,14,0,60,0,252,0,254,0,126,0,120,0,96,0,0,0,63,240,255,195,255,0,0,0,0,0,3,255,15,252,63,240,0,0,0,0,0,0,0,0,0,128,3,128,15,128,31,128,31,192,15,128,14,0,120,7,224,126,15,192,60,0,192,0,0,0,0,31,128,127,1,254,0,60,0,112,1,192,15,3,248,15,224,62,0,224,3,128,14,0,0,0,0,1,128,15,0,60,0,7,192,127,195,255,142,15,120,29,199,119,63,221,231,119,29,220,119,113,221,199,119,29,220,119,63,220,115,112,1,224,3,128,15,224,31,192,31,0,7,128,30,0,124,3,240,14,192,59,0,206,7,56,28,224,113,129,199,15,252,63,240,255,195,131,156,14,112,57,192,224,63,192,255,195,255,14,30,56,56,224,227,135,143,252,63,224,255,195,135,14,14,56,56,224,227,135,143,252,63,240,255,0,7,192,127,193,255,15,30,56,56,224,3,128,14,0,56,0,224,3,128,14,0,56,0,224,227,199,135,252,31,240,31,0,63,192,255,131,255,14,30,56,56,224,227,131,142,14,56,56,224,227,131,142,14,56,56,224,227,135,143,252,63,224,255,0,63,248,255,227,255,14,0,56,0,224,3,128,15,248,63,240,255,195,128,14,0,56,0,224,3,128,15,252,63,248,255,224,63,248,255,227,255,142,0,56,0,224,3,128,15,252,63,248,255,227,128,14,0,56,0,224,3,128,14,0,56,0,224,0,7,192,127,193,255,15,30,56,56,224,3,128,14,0,57,248,231,227,159,142,14,56,56,224,227,199,135,252,31,224,31,0,56,56,224,227,131,142,14,56,56,224,227,131,143,254,63,248,255,227,131,142,14,56,56,224,227,131,142,14,56,56,224,224,63,240,255,195,255,0,224,3,128,14,0,56,0,224,3,128,14,0,56,0,224,3,128,14,0,56,15,252,63,240,255,192,3,248,15,224,63,128,14,0,56,0,224,3,128,14,0,56,0,224,3,128,14,112,57,192,231,135,143,252,31,224,63,0,56,56,224,227,135,14,28,56,240,227,131,158,15,240,63,192,255,3,158,14,56,56,224,225,195,135,14,30,56,56,224,240,56,0,224,3,128,14,0,56,0,224,3,128,14,0,56,0,224,3,128,14,0,56,0,224,3,128,15,252,63,248,255,224,120,121,225,231,143,159,62,124,249,243,231,123,157,238,119,185,220,231,3,156,14,112,57,192,231,3,156,14,112,57,192,224,60,56,240,227,227,143,142,62,56,236,227,179,142,206,59,184,230,227,155,142,110,56,248,227,227,143,142,62,56,120,225,224,7,192,127,193,255,15,30,56,56,224,227,131,142,14,56,56,224,227,131,142,14,56,56,224,227,199,135,252,31,240,31,0,63,224,255,195,255,142,15,56,28,224,115,129,206,15,63,248,255,195,254,14,0,56,0,224,3,128,14,0,56,0,224,0,7,192,63,129,255,15,30,56,56,224,227,131,142,14,56,56,224,227,131,142,14,56,56,224,227,199,135,252,15,224,31,128,14,0,28,0,112,0,224,63,192,255,131,255,14,30,56,56,224,227,131,142,30,63,240,255,131,252,14,112,56,224,227,131,135,14,28,56,120,224,224,15,128,255,131,254,30,60,112,113,192,7,128,15,128,31,192,63,192,31,0,30,0,57,192,231,135,143,252,31,224,63,0,127,249,255,231,255,128,224,3,128,14,0,56,0,224,3,128,14,0,56,0,224,3,128,14,0,56,0,224,3,128,14,0,56,56,224,227,131,142,14,56,56,224,227,131,142,14,56,56,224,227,131,142,14,56,56,224,227,199,135,252,31,240,31,0,112,57,192,231,131,142,14,56,48,225,195,135,7,28,28,96,115,129,206,3,184,14,192,59,0,252,1,240,7,192,30,0,227,31,140,126,121,153,230,103,153,158,102,121,153,46,116,185,210,231,75,159,62,124,240,243,195,207,15,60,60,240,227,192,120,56,225,227,199,7,60,30,224,63,128,252,1,240,7,128,30,0,124,3,240,14,224,115,129,199,14,28,120,57,192,224,112,28,112,112,224,225,225,193,199,3,142,3,184,7,112,7,192,15,128,14,0,28,0,56,0,112,0,224,1,192,3,128,7,0,63,240,255,195,255,0,60,0,224,7,128,28,0,224,3,128,28,0,112,3,128,30,0,112,3,192,15,254,63,248,255,224,15,192,63,128,254,3,128,14,0,56,0,224,3,128,14,0,56,0,224,3,128,14,0,56,0,224,3,128,14,0,56,0,224,3,128,14,0,63,0,254,3,248,48,0,224,3,128,7,0,28,0,112,0,224,3,128,14,0,28,0,112,1,192,3,128,14,0,60,0,112,1,192,7,128,14,0,56,0,112,1,192,7,0,14,15,192,127,1,252,0,112,1,192,7,0,28,0,112,1,192,7,0,28,0,112,1,192,7,0,28,0,112,1,192,7,0,28,0,112,1,192,63,1,252,7,240,3,128,30,0,124,3,176,14,224,115,129,198,6,28,56,112,224,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,249,255,231,255,128,15,0,28,0,56,0,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,128,127,131,255,14,28,0,112,1,193,255,15,252,120,113,193,199,15,31,252,63,112,121,192,56,0,224,3,128,14,0,59,224,255,195,255,15,30,56,56,224,227,131,142,14,56,56,224,227,199,143,252,59,240,231,128,7,192,63,193,255,15,30,56,56,224,3,128,14,0,56,0,224,227,199,135,252,15,240,31,0,0,56,0,224,3,128,14,15,184,127,225,255,143,30,56,56,224,227,131,142,14,56,56,224,227,199,135,254,31,184,60,224,7,192,63,129,255,15,30,56,56,224,227,255,143,254,56,0,224,3,195,135,252,15,240,31,0,1,248,15,224,127,129,192,7,0,28,7,255,159,254,127,248,28,0,112,1,192,7,0,28,0,112,1,192,7,0,28,0,15,56,126,225,255,143,30,56,56,224,227,131,142,14,56,56,241,225,255,135,238,7,56,0,224,3,131,254,15,240,63,128,56,0,224,3,128,14,0,57,224,239,195,255,143,30,56,56,224,227,131,142,14,56,56,224,227,131,142,14,56,56,224,224,1,128,15,0,60,0,96,0,0,0,1,252,7,240,31,192,7,0,28,0,112,1,192,7,0,28,0,112,1,192,255,243,255,207,255,0,192,7,128,30,0,48,0,0,0,3,254,15,248,63,224,3,128,14,0,56,0,224,3,128,14,0,56,0,224,3,128,14,0,56,1,224,255,7,252,31,192,56,0,224,3,128,14,0,56,56,225,227,135,14,60,56,224,255,3,252,15,240,57,224,227,131,143,14,28,56,120,224,224,127,1,252,7,240,1,192,7,0,28,0,112,1,192,7,0,28,0,112,1,192,7,0,28,0,112,1,254,3,252,7,240,118,225,255,199,255,156,206,115,57,204,231,51,156,206,115,57,204,231,51,156,206,115,57,204,224,57,224,239,195,255,143,30,56,56,224,227,131,142,14,56,56,224,227,131,142,14,56,56,224,224,7,192,63,129,255,15,30,56,56,224,227,131,142,14,56,56,224,227,199,135,252,31,224,31,0,59,224,255,195,255,15,30,56,56,224,227,131,142,14,56,56,224,227,199,143,252,59,240,231,131,128,14,0,56,0,224,0,15,184,127,225,255,143,30,56,56,224,227,131,142,14,56,56,224,227,199,135,254,31,184,60,224,3,128,14,0,56,0,224,57,224,239,195,255,15,30,56,56,224,227,128,14,0,56,0,224,3,128,14,0,56,0,224,0,15,192,127,195,255,14,14,56,0,240,1,254,1,252,0,120,0,227,131,143,254,31,240,63,128,7,0,28,0,112,1,192,127,249,255,231,255,129,192,7,0,28,0,112,1,192,7,0,28,0,112,1,254,3,248,7,224,56,56,224,227,131,142,14,56,56,224,227,131,142,14,56,56,224,227,199,135,252,31,224,31,0,112,57,192,227,131,142,28,56,112,113,193,198,7,56,14,224,59,128,252,1,240,7,192,30,0,99,29,142,118,57,216,230,119,153,218,103,105,141,46,52,248,243,227,207,15,60,60,112,241,192,120,56,225,193,207,7,184,15,192,31,0,120,1,224,15,192,63,129,206,15,28,56,121,224,224,112,57,224,227,135,142,28,28,112,115,193,206,3,184,15,192,31,0,124,1,224,3,128,14,0,112,1,192,7,0,56,0,63,240,255,195,255,0,56,1,224,15,0,56,1,192,14,0,120,1,192,15,254,63,248,255,224,0,112,7,192,63,0,240,3,128,14,0,56,0,224,3,128,14,0,120,31,192,126,0,252,0,56,0,224,3,128,14,0,56,0,224,3,128,15,192,31,0,60,3,128,14,0,56,0,224,3,128,14,0,56,0,224,3,128,14,0,56,0,224,3,128,14,0,56,0,224,3,128,14,0,56,0,224,3,128,14,0,56,0,224,56,0,248,3,240,3,192,7,0,28,0,112,1,192,7,0,28,0,120,0,254,1,248,15,192,112,1,192,7,0,28,0,112,1,192,7,0,252,3,224,15,0,28,0,248,231,243,156,254,113,240,3,128,0,0,0,0,0,0,0,0,0,0}; +static const Glyph f2_24_glyphs[]={{0,14,0,0,18,14,32},{0,14,18,0,0,14,33},{32,14,18,0,0,14,34},{64,14,18,0,0,14,35},{96,14,26,0,-4,14,36},{142,16,18,0,0,14,37},{178,15,18,0,0,14,38},{212,14,18,0,0,14,39},{244,14,24,0,-3,14,40},{286,14,24,0,-3,14,41},{328,15,15,0,3,14,42},{357,14,14,0,4,14,43},{382,14,8,0,14,14,44},{396,14,10,0,8,14,45},{414,14,5,0,13,14,46},{423,14,24,0,-3,14,47},{465,14,18,0,0,14,48},{497,14,18,0,0,14,49},{529,14,18,0,0,14,50},{561,14,18,0,0,14,51},{593,14,18,0,0,14,52},{625,14,18,0,0,14,53},{657,14,18,0,0,14,54},{689,14,18,0,0,14,55},{721,14,18,0,0,14,56},{753,14,18,0,0,14,57},{785,14,14,0,4,14,58},{810,14,18,0,4,14,59},{842,14,16,0,2,14,60},{870,14,13,0,5,14,61},{893,14,16,0,2,14,62},{921,14,18,0,0,14,63},{953,14,22,0,0,14,64},{992,14,18,0,0,14,65},{1024,14,18,0,0,14,66},{1056,14,18,0,0,14,67},{1088,14,18,0,0,14,68},{1120,14,18,0,0,14,69},{1152,14,18,0,0,14,70},{1184,14,18,0,0,14,71},{1216,14,18,0,0,14,72},{1248,14,18,0,0,14,73},{1280,14,18,0,0,14,74},{1312,14,18,0,0,14,75},{1344,14,18,0,0,14,76},{1376,14,18,0,0,14,77},{1408,14,18,0,0,14,78},{1440,14,18,0,0,14,79},{1472,14,18,0,0,14,80},{1504,14,22,0,0,14,81},{1543,14,18,0,0,14,82},{1575,14,18,0,0,14,83},{1607,14,18,0,0,14,84},{1639,14,18,0,0,14,85},{1671,14,18,0,0,14,86},{1703,14,18,0,0,14,87},{1735,14,18,0,0,14,88},{1767,15,18,0,0,14,89},{1801,14,18,0,0,14,90},{1833,14,24,0,-3,14,91},{1875,14,24,0,-3,14,92},{1917,14,24,0,-3,14,93},{1959,14,19,0,-1,14,94},{1993,14,3,0,18,14,95},{1999,14,20,0,-2,14,96},{2034,14,14,0,4,14,97},{2059,14,18,0,0,14,98},{2091,14,14,0,4,14,99},{2116,14,18,0,0,14,100},{2148,14,14,0,4,14,101},{2173,14,18,0,0,14,102},{2205,14,18,0,4,14,103},{2237,14,18,0,0,14,104},{2269,14,20,0,-2,14,105},{2304,14,24,0,-2,14,106},{2346,14,18,0,0,14,107},{2378,14,18,0,0,14,108},{2410,14,14,0,4,14,109},{2435,14,14,0,4,14,110},{2460,14,14,0,4,14,111},{2485,14,18,0,4,14,112},{2517,14,18,0,4,14,113},{2549,14,14,0,4,14,114},{2574,14,14,0,4,14,115},{2599,14,18,0,0,14,116},{2631,14,14,0,4,14,117},{2656,14,14,0,4,14,118},{2681,14,14,0,4,14,119},{2706,14,14,0,4,14,120},{2731,14,18,0,4,14,121},{2763,14,14,0,4,14,122},{2788,14,24,0,-3,14,123},{2830,14,24,0,-3,14,124},{2872,14,24,0,-3,14,125},{2914,14,12,0,6,14,126}}; +static const uint8_t f2_26_bits[]={1,192,1,192,1,192,1,192,1,192,1,192,1,192,1,192,1,192,1,192,1,192,1,192,1,192,0,0,0,0,0,0,1,128,3,192,3,192,1,128,28,112,28,112,28,112,28,112,28,112,28,112,28,112,28,112,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,24,7,24,6,24,6,24,6,24,63,254,63,254,14,48,12,48,12,48,12,48,12,48,12,112,127,252,127,252,24,96,24,96,24,96,24,224,24,192,1,0,3,0,3,0,3,0,7,192,31,240,63,240,63,120,59,56,59,56,59,0,63,0,63,128,31,224,15,240,3,248,3,124,3,60,3,28,115,28,123,60,63,248,31,240,15,192,3,0,3,0,3,0,1,0,28,3,31,131,24,195,140,97,134,49,131,25,193,204,192,126,192,30,224,0,96,0,96,0,115,128,51,224,51,56,57,140,24,198,24,99,28,49,140,15,140,3,128,7,128,31,224,63,240,60,240,56,112,56,0,28,0,28,0,30,0,63,0,127,28,115,188,115,248,113,248,112,240,112,240,121,240,127,248,63,188,31,28,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,56,0,248,1,240,3,224,3,128,7,128,7,0,15,0,14,0,14,0,14,0,14,0,14,0,14,0,14,0,14,0,14,0,15,0,7,0,7,128,3,192,3,240,1,248,0,120,0,56,0,0,0,0,28,0,30,0,31,0,7,128,3,192,1,192,1,224,0,224,0,224,0,224,0,224,0,224,0,224,0,224,0,224,0,224,0,224,0,224,1,224,1,192,3,192,15,128,31,0,30,0,24,0,0,0,3,128,3,128,3,128,3,128,99,140,121,188,127,254,63,248,3,128,6,192,14,224,30,112,60,120,24,48,0,0,0,0,0,0,1,192,1,192,1,192,1,192,1,192,63,254,63,254,63,254,1,192,1,192,1,192,1,192,1,192,0,0,0,0,3,192,3,192,7,128,7,128,7,0,7,0,14,0,14,0,15,240,15,240,15,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,128,7,192,7,192,7,192,3,128,0,60,0,56,0,56,0,120,0,112,0,112,0,240,0,224,0,224,1,224,1,192,3,192,3,128,3,128,7,128,7,0,7,0,15,0,14,0,14,0,30,0,28,0,60,0,56,0,56,0,7,224,15,240,31,248,60,60,60,28,56,28,56,28,56,28,57,156,57,220,57,220,57,156,56,28,56,28,56,28,60,28,60,60,31,248,15,240,7,224,3,192,15,192,31,192,61,192,57,192,49,192,33,192,1,192,1,192,1,192,1,192,1,192,1,192,1,192,1,192,1,192,1,192,63,252,63,252,63,252,3,224,15,240,31,248,28,60,60,60,56,28,0,28,0,60,0,56,0,120,0,240,1,240,3,224,7,192,15,128,31,0,62,0,63,252,63,252,63,252,31,252,31,252,31,252,0,60,0,112,1,224,3,192,3,224,3,240,3,248,0,60,0,28,0,28,0,28,56,28,60,28,60,60,31,248,15,240,7,224,0,120,0,112,0,240,1,224,1,192,3,192,7,128,7,0,15,0,30,28,28,28,60,28,56,28,63,252,63,252,63,252,0,28,0,28,0,28,0,28,63,248,63,248,63,248,56,0,56,0,56,0,56,0,57,224,63,240,63,248,60,120,0,56,0,56,0,56,0,56,56,56,60,120,63,240,31,240,7,192,1,224,3,192,3,128,7,128,7,0,15,0,30,0,28,128,63,240,63,248,124,120,120,60,112,28,112,28,112,28,120,60,60,120,63,248,31,240,7,192,63,254,63,254,63,252,56,28,56,28,56,60,0,56,0,120,0,112,0,112,0,240,0,224,1,224,1,192,1,192,3,192,3,128,7,128,7,0,7,0,7,224,15,240,31,248,60,60,56,28,56,28,60,60,28,56,15,240,7,224,15,240,30,120,28,60,56,28,56,28,60,28,60,60,31,248,15,248,7,224,7,192,31,240,63,248,60,120,120,60,112,28,112,28,112,28,120,60,124,124,63,248,31,248,15,112,0,240,0,224,1,192,3,192,3,128,7,128,15,0,3,128,7,192,7,192,7,192,3,128,0,0,0,0,0,0,0,0,0,0,0,0,3,128,7,192,7,192,3,128,3,128,7,192,7,192,7,192,3,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,192,7,128,7,128,7,128,7,0,15,0,14,0,14,0,0,0,0,8,0,60,0,248,3,240,15,192,63,0,60,0,56,0,63,0,31,192,7,240,1,248,0,124,0,28,0,0,0,0,63,248,63,248,63,248,0,0,0,0,0,0,0,0,63,248,63,248,63,248,0,0,0,0,0,0,0,0,0,0,32,0,56,0,62,0,31,128,7,224,1,248,0,124,0,60,0,248,3,240,15,192,63,0,60,0,48,0,0,0,0,0,31,128,31,224,31,240,0,112,0,120,0,56,0,56,0,120,0,248,7,240,7,224,7,192,7,0,7,0,0,0,0,0,6,0,15,0,15,0,6,0,3,224,15,248,31,252,60,30,56,14,120,14,112,14,113,238,113,246,115,206,115,142,115,142,115,142,115,142,115,142,115,142,115,206,113,246,113,230,120,0,56,0,60,0,31,192,15,192,3,192,3,192,7,192,7,192,7,224,7,224,14,224,14,224,14,112,14,112,28,112,28,112,28,56,31,248,63,248,63,248,56,60,56,28,56,28,120,28,112,30,63,224,63,240,63,248,56,60,56,28,56,28,56,28,56,56,63,240,63,224,63,248,56,60,56,28,56,28,56,28,56,28,56,60,63,248,63,240,63,224,7,224,15,240,31,248,60,60,56,28,56,28,56,0,56,0,56,0,56,0,56,0,56,0,56,0,56,0,56,28,60,60,60,60,31,248,15,240,7,224,63,192,63,240,63,248,56,120,56,60,56,28,56,28,56,28,56,28,56,28,56,28,56,28,56,28,56,28,56,28,56,60,56,120,63,248,63,240,63,192,63,252,63,252,63,248,56,0,56,0,56,0,56,0,56,0,63,240,63,248,63,248,56,0,56,0,56,0,56,0,56,0,56,0,63,248,63,252,63,252,63,252,63,252,63,252,56,0,56,0,56,0,56,0,56,0,56,0,63,248,63,248,63,248,56,0,56,0,56,0,56,0,56,0,56,0,56,0,56,0,7,224,15,240,31,248,60,60,56,28,56,28,56,0,56,0,56,0,56,252,56,252,56,252,56,28,56,28,56,28,60,60,60,60,31,248,15,240,7,224,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,63,248,63,248,63,248,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,63,248,63,248,63,248,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,63,248,63,248,63,248,3,248,3,248,3,248,0,56,0,56,0,56,0,56,0,56,0,56,0,56,0,56,0,56,0,56,0,56,112,56,120,56,120,120,63,240,31,224,15,192,56,28,56,60,56,60,56,56,56,120,56,112,56,240,56,224,63,224,63,192,63,224,56,224,56,240,56,112,56,112,56,120,56,56,56,60,56,28,56,30,28,0,28,0,28,0,28,0,28,0,28,0,28,0,28,0,28,0,28,0,28,0,28,0,28,0,28,0,28,0,28,0,28,0,31,252,31,254,31,254,120,60,124,124,124,124,124,124,124,124,126,252,118,220,118,220,119,220,115,156,115,156,112,28,112,28,112,28,112,28,112,28,112,28,112,28,112,28,112,28,62,28,62,28,62,28,62,28,63,28,63,28,59,28,59,156,57,156,57,156,57,156,57,220,56,220,56,220,56,252,56,124,56,124,56,124,56,124,56,60,7,224,15,240,31,248,60,60,56,28,56,28,56,28,56,28,56,28,56,28,56,28,56,28,56,28,56,28,56,28,60,60,60,60,31,248,15,240,7,224,63,224,63,240,63,248,56,60,56,28,56,28,56,28,56,28,56,60,63,248,63,240,63,224,56,0,56,0,56,0,56,0,56,0,56,0,56,0,56,0,7,224,15,240,31,248,60,60,60,60,56,28,56,28,56,28,56,28,56,28,56,28,56,28,56,28,56,28,56,28,60,60,60,60,31,248,15,240,7,224,0,240,0,112,0,120,0,56,0,28,63,224,63,240,63,248,56,60,56,28,56,28,56,28,56,28,56,60,63,248,63,240,63,224,56,224,56,240,56,112,56,120,56,120,56,56,56,60,56,28,7,192,31,240,31,240,60,120,56,56,56,0,56,0,60,0,31,128,31,224,7,248,1,248,0,60,0,28,0,28,120,28,120,60,63,248,31,240,7,192,127,252,127,252,127,252,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,56,28,56,28,56,28,56,28,56,28,56,28,56,28,56,28,56,28,56,28,56,28,56,28,56,28,56,28,56,28,60,60,60,60,31,248,15,240,7,224,112,30,120,28,56,28,56,28,56,60,60,56,60,56,28,56,28,56,28,112,30,112,14,112,14,112,14,224,14,224,7,224,7,224,7,224,7,192,3,192,113,198,56,227,28,113,142,57,199,28,227,158,112,205,56,102,156,51,110,29,183,14,219,7,109,131,166,193,243,96,248,240,124,120,30,60,15,30,7,143,3,199,128,120,28,56,60,60,56,28,120,30,112,14,240,15,224,7,224,7,192,3,192,3,192,7,224,7,224,14,240,14,112,28,120,28,56,56,60,56,28,120,30,112,30,112,28,56,60,56,56,60,56,28,112,30,112,14,240,14,224,7,224,7,192,7,192,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,63,248,63,248,63,248,0,120,0,112,0,240,0,224,1,224,1,192,3,192,7,128,7,0,15,0,14,0,30,0,28,0,60,0,63,252,63,252,63,252,7,248,7,248,7,240,7,0,7,0,7,0,7,0,7,0,7,0,7,0,7,0,7,0,7,0,7,0,7,0,7,0,7,0,7,0,7,0,7,0,7,0,7,0,7,240,7,248,7,248,56,0,56,0,60,0,28,0,30,0,14,0,14,0,15,0,7,0,7,0,7,128,3,128,3,128,3,192,1,192,1,224,0,224,0,224,0,240,0,112,0,112,0,120,0,56,0,56,0,60,15,224,15,224,15,224,0,224,0,224,0,224,0,224,0,224,0,224,0,224,0,224,0,224,0,224,0,224,0,224,0,224,0,224,0,224,0,224,0,224,0,224,0,224,15,224,15,224,15,224,1,128,3,192,7,192,7,224,6,224,14,112,14,112,28,112,28,56,56,56,56,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,63,252,63,252,63,252,7,0,7,128,3,192,1,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,192,31,240,63,248,60,120,0,56,0,56,15,248,63,248,120,56,112,56,112,56,120,120,127,248,63,184,31,56,56,0,56,0,56,0,56,0,56,0,57,224,59,248,63,248,60,60,60,60,56,28,56,28,56,28,56,28,56,28,60,60,60,60,63,248,59,248,57,224,7,224,15,240,31,248,60,60,56,28,56,0,56,0,56,0,56,0,56,0,60,28,60,60,31,248,15,240,7,224,0,28,0,28,0,28,0,28,0,28,7,156,31,220,31,252,60,60,60,60,56,28,56,28,56,28,56,28,56,28,60,60,60,60,31,252,31,220,7,156,7,224,15,240,31,248,60,60,56,28,56,28,63,252,63,252,56,0,56,0,60,0,60,60,31,248,15,240,7,224,1,252,3,252,7,252,7,128,7,0,7,0,7,0,127,252,127,252,127,252,7,0,7,0,7,0,7,0,7,0,7,0,7,0,7,0,7,0,7,0,7,156,31,220,31,252,60,60,56,60,56,28,56,28,56,28,56,28,56,60,60,60,31,252,31,220,7,156,0,28,0,28,0,60,15,248,15,248,15,224,56,0,56,0,56,0,56,0,56,0,57,224,59,248,63,248,60,60,60,60,56,28,56,28,56,28,56,28,56,28,56,28,56,28,56,28,56,28,56,28,1,128,3,192,3,192,1,128,0,0,0,0,31,192,31,192,31,192,1,192,1,192,1,192,1,192,1,192,1,192,1,192,1,192,1,192,63,254,63,254,63,254,0,96,0,240,0,240,0,96,0,0,0,0,31,240,31,240,31,240,0,112,0,112,0,112,0,112,0,112,0,112,0,112,0,112,0,112,0,112,0,112,0,112,0,240,0,240,63,224,63,192,63,128,56,0,56,0,56,0,56,0,56,0,56,28,56,60,56,56,56,120,56,240,56,224,63,224,63,192,63,224,56,240,56,240,56,120,56,56,56,60,56,28,255,0,255,0,127,0,7,0,7,0,7,0,7,0,7,0,7,0,7,0,7,0,7,0,7,0,7,0,7,0,7,0,7,128,3,252,3,254,0,254,118,112,127,248,127,252,115,188,115,28,115,28,115,28,115,28,115,28,115,28,115,28,115,28,115,28,115,28,115,28,57,224,59,248,63,248,60,60,60,60,56,28,56,28,56,28,56,28,56,28,56,28,56,28,56,28,56,28,56,28,7,224,15,240,31,248,60,60,56,28,56,28,56,28,56,28,56,28,56,28,60,60,60,60,31,248,15,240,7,224,57,224,59,248,63,248,60,60,60,60,56,28,56,28,56,28,56,28,56,28,60,60,60,60,63,248,59,248,57,224,56,0,56,0,56,0,56,0,56,0,7,156,31,220,31,252,60,60,56,60,56,28,56,28,56,28,56,28,56,28,56,60,60,60,31,252,31,220,7,156,0,28,0,28,0,28,0,28,0,28,57,224,59,248,63,248,60,60,60,60,56,28,56,28,56,0,56,0,56,0,56,0,56,0,56,0,56,0,56,0,15,224,31,248,63,248,60,60,56,0,60,0,31,192,15,240,0,248,0,60,0,28,56,60,63,252,31,248,15,224,7,0,7,0,7,0,7,0,127,252,127,252,127,252,7,0,7,0,7,0,7,0,7,0,7,0,7,0,7,0,7,0,7,252,3,252,1,252,56,28,56,28,56,28,56,28,56,28,56,28,56,28,56,28,56,28,56,28,60,60,60,60,31,248,15,240,7,224,112,28,120,28,56,28,56,56,60,56,28,56,28,120,30,112,14,112,14,112,14,224,7,224,7,224,7,192,3,192,99,142,99,142,115,142,115,206,115,204,114,204,114,204,54,220,54,92,54,92,62,124,62,120,60,120,60,120,28,120,56,60,60,56,28,120,14,112,15,224,7,224,7,192,3,192,7,224,15,224,14,240,30,112,28,120,60,60,120,28,120,28,56,28,56,60,60,56,28,56,28,120,30,112,14,112,14,240,7,224,7,224,7,224,3,192,3,192,3,192,3,128,3,128,7,128,7,0,15,0,63,248,63,248,63,248,0,120,0,240,1,224,3,192,3,128,7,128,15,0,30,0,60,0,63,252,63,252,63,252,0,124,0,252,1,248,1,224,1,192,1,192,1,192,1,192,1,192,1,192,3,192,63,128,63,0,63,128,3,192,1,192,1,192,1,192,1,192,1,192,1,192,1,224,1,248,0,252,0,124,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,3,128,60,0,62,0,63,0,7,0,7,0,7,0,7,0,7,0,7,0,7,0,7,128,3,252,1,252,3,248,7,128,7,0,7,0,7,0,7,0,7,0,7,0,15,0,63,0,62,0,60,0,30,0,63,28,127,156,115,252,113,252,0,120,0,0,0,0,0,0,0,0,0,0,0,0}; +static const Glyph f2_26_glyphs[]={{0,16,0,0,20,16,32},{0,16,20,0,0,16,33},{40,16,20,0,0,16,34},{80,16,20,0,0,16,35},{120,16,28,0,-4,16,36},{176,17,20,0,0,16,37},{219,16,20,0,0,16,38},{259,16,20,0,0,16,39},{299,16,27,0,-3,16,40},{353,16,27,0,-3,16,41},{407,16,17,0,3,16,42},{441,16,15,0,5,16,43},{471,16,8,0,16,16,44},{487,16,10,0,10,16,45},{507,16,5,0,15,16,46},{517,16,25,0,-2,16,47},{567,16,20,0,0,16,48},{607,16,20,0,0,16,49},{647,16,20,0,0,16,50},{687,16,20,0,0,16,51},{727,16,20,0,0,16,52},{767,16,20,0,0,16,53},{807,16,20,0,0,16,54},{847,16,20,0,0,16,55},{887,16,20,0,0,16,56},{927,16,20,0,0,16,57},{967,16,15,0,5,16,58},{997,16,20,0,5,16,59},{1037,16,17,0,3,16,60},{1071,16,14,0,6,16,61},{1099,16,17,0,3,16,62},{1133,16,20,0,0,16,63},{1173,16,25,0,0,16,64},{1223,16,20,0,0,16,65},{1263,16,20,0,0,16,66},{1303,16,20,0,0,16,67},{1343,16,20,0,0,16,68},{1383,16,20,0,0,16,69},{1423,16,20,0,0,16,70},{1463,16,20,0,0,16,71},{1503,16,20,0,0,16,72},{1543,16,20,0,0,16,73},{1583,16,20,0,0,16,74},{1623,16,20,0,0,16,75},{1663,16,20,0,0,16,76},{1703,16,20,0,0,16,77},{1743,16,20,0,0,16,78},{1783,16,20,0,0,16,79},{1823,16,20,0,0,16,80},{1863,16,25,0,0,16,81},{1913,16,20,0,0,16,82},{1953,16,20,0,0,16,83},{1993,16,20,0,0,16,84},{2033,16,20,0,0,16,85},{2073,16,20,0,0,16,86},{2113,17,20,-1,0,16,87},{2156,16,20,0,0,16,88},{2196,16,20,0,0,16,89},{2236,16,20,0,0,16,90},{2276,16,25,0,-2,16,91},{2326,16,25,0,-2,16,92},{2376,16,25,0,-2,16,93},{2426,16,20,0,0,16,94},{2466,16,4,0,20,16,95},{2474,16,21,0,-1,16,96},{2516,16,15,0,5,16,97},{2546,16,20,0,0,16,98},{2586,16,15,0,5,16,99},{2616,16,20,0,0,16,100},{2656,16,15,0,5,16,101},{2686,16,20,0,0,16,102},{2726,16,20,0,5,16,103},{2766,16,20,0,0,16,104},{2806,16,21,0,-1,16,105},{2848,16,26,0,-1,16,106},{2900,16,20,0,0,16,107},{2940,16,20,0,0,16,108},{2980,16,15,0,5,16,109},{3010,16,15,0,5,16,110},{3040,16,15,0,5,16,111},{3070,16,20,0,5,16,112},{3110,16,20,0,5,16,113},{3150,16,15,0,5,16,114},{3180,16,15,0,5,16,115},{3210,16,19,0,1,16,116},{3248,16,15,0,5,16,117},{3278,16,15,0,5,16,118},{3308,16,15,0,5,16,119},{3338,16,15,0,5,16,120},{3368,16,20,0,5,16,121},{3408,16,15,0,5,16,122},{3438,16,25,0,-2,16,123},{3488,16,25,0,-2,16,124},{3538,16,25,0,-2,16,125},{3588,16,12,0,8,16,126}}; +static const uint8_t f2_28_bits[]={3,192,1,224,0,240,0,120,0,60,0,30,0,15,0,7,128,3,192,1,224,0,240,0,120,0,60,0,30,0,0,0,0,0,0,0,0,224,0,248,0,124,0,28,0,15,60,7,158,3,207,1,231,128,243,192,121,224,60,240,30,120,15,60,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,12,1,134,1,195,0,227,128,113,193,255,248,255,252,12,48,14,24,7,12,3,142,1,135,0,195,128,97,129,255,248,255,252,28,112,14,56,6,28,3,12,1,134,0,0,128,0,96,0,48,0,24,0,30,0,63,192,63,240,63,252,28,206,14,103,7,48,3,152,1,236,0,254,0,63,224,15,248,0,254,0,111,0,51,128,25,195,204,225,230,112,127,248,63,248,15,248,0,240,0,48,0,24,0,12,0,4,0,30,3,143,192,199,56,113,134,56,97,140,24,103,7,59,128,252,192,30,112,0,56,0,12,0,6,0,3,158,0,207,192,103,56,57,142,12,99,134,24,227,135,56,192,252,96,30,0,7,192,7,248,7,254,3,207,1,195,128,224,0,112,0,60,0,14,0,15,128,15,224,15,241,231,157,227,207,225,227,240,240,240,120,120,60,126,15,255,7,251,192,248,240,1,224,0,240,0,120,0,60,0,28,0,14,0,7,0,3,128,1,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,14,0,31,0,31,128,31,0,15,0,15,0,7,0,7,128,3,192,1,224,0,240,0,120,0,60,0,30,0,15,0,7,128,3,192,1,224,0,240,0,60,0,30,0,7,128,3,240,0,252,0,30,0,7,0,0,0,8,0,15,0,7,192,1,240,0,60,0,15,0,3,192,1,224,0,112,0,56,0,28,0,14,0,7,128,3,192,1,224,0,240,0,112,0,56,0,28,0,30,0,15,0,15,0,15,128,31,128,31,128,15,128,7,0,0,0,0,1,224,0,240,0,112,0,56,3,28,115,230,249,255,252,63,248,1,192,1,184,1,222,1,231,1,227,192,240,224,16,32,0,0,0,0,0,1,224,0,240,0,120,0,60,0,30,0,15,0,255,248,127,252,63,254,0,240,0,120,0,60,0,30,0,15,0,0,0,0,0,0,0,0,3,224,1,224,0,240,0,120,0,120,0,60,0,28,0,14,0,14,0,0,0,0,7,252,3,254,1,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,192,1,240,0,248,0,124,0,28,0,0,30,0,15,0,7,0,7,128,3,128,3,192,1,224,0,224,0,240,0,120,0,56,0,60,0,30,0,14,0,15,0,7,0,7,128,3,192,1,192,1,224,0,240,0,112,0,120,0,60,0,28,0,30,0,14,0,0,3,224,7,252,7,255,3,199,131,193,225,224,240,240,120,120,60,61,158,30,239,15,119,135,179,195,193,225,224,240,240,120,120,60,60,30,15,30,7,255,1,255,0,62,0,3,224,3,240,7,248,7,252,3,222,1,143,0,135,128,3,192,1,224,0,240,0,120,0,60,0,30,0,15,0,7,128,3,192,1,224,0,240,15,255,135,255,195,255,224,3,224,7,252,7,255,3,199,131,193,225,224,240,0,120,0,60,0,30,0,30,0,31,0,31,0,31,0,31,0,31,0,31,0,31,0,31,0,15,255,135,255,195,255,224,31,252,15,254,7,255,0,7,128,7,128,7,0,7,0,7,192,3,248,1,254,0,15,0,3,192,1,224,0,240,0,120,120,60,60,30,15,30,7,255,1,255,0,62,0,0,120,0,60,0,60,0,60,0,30,0,30,0,31,0,15,0,15,0,7,143,7,135,135,131,195,193,225,224,240,255,248,127,252,63,254,0,15,0,7,128,3,192,1,224,31,254,15,255,7,255,131,192,1,224,0,240,0,120,0,61,240,31,252,15,255,7,199,128,1,192,0,224,0,120,0,56,0,28,30,14,15,143,7,255,1,255,128,63,0,0,240,0,120,0,120,0,60,0,60,0,60,0,30,0,30,0,14,248,15,254,7,255,135,195,195,192,241,224,120,240,60,120,30,60,15,15,15,7,255,129,255,128,63,0,63,255,31,255,143,255,199,129,195,193,225,224,240,0,112,0,120,0,56,0,60,0,30,0,14,0,15,0,7,0,7,128,3,192,1,192,1,224,0,224,0,240,0,120,0,3,240,7,252,7,255,3,199,193,193,224,224,240,112,120,60,56,15,60,3,252,1,252,1,255,1,227,193,224,240,240,120,120,60,60,30,31,31,7,255,1,255,0,62,0,3,240,7,254,7,255,131,195,195,192,241,224,120,240,60,120,30,60,15,31,15,135,255,129,255,192,127,192,1,224,1,224,0,240,0,240,0,240,0,120,0,120,0,60,0,1,192,1,240,0,248,0,124,0,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,56,0,62,0,31,0,15,128,3,128,1,192,1,240,0,248,0,124,0,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,30,0,30,0,15,0,7,128,3,128,3,192,1,224,0,224,0,112,0,0,0,0,2,0,7,128,15,192,31,192,63,0,126,0,124,0,56,0,31,0,15,224,1,252,0,63,128,7,224,0,120,0,12,0,0,0,0,0,0,0,31,254,15,255,7,255,128,0,0,0,0,0,0,0,0,63,252,31,254,15,255,0,0,0,0,0,0,0,0,0,0,0,16,0,15,0,7,224,1,252,0,63,128,7,240,0,252,0,30,0,31,0,63,0,254,1,252,1,248,0,240,0,96,0,0,0,0,0,0,31,192,15,248,7,254,0,15,128,1,192,0,224,0,112,0,56,0,60,3,254,1,254,0,252,0,120,0,60,0,14,0,0,0,0,0,1,192,1,240,0,248,0,56,0,3,240,7,254,7,255,135,195,227,192,243,192,57,224,28,241,239,121,255,189,231,222,241,239,120,247,188,123,222,61,239,30,247,143,123,207,188,255,222,60,207,0,7,128,1,224,0,248,0,63,192,15,224,1,240,0,3,224,1,240,0,248,0,254,0,127,0,59,128,29,192,30,224,14,120,7,28,3,142,3,199,1,195,192,255,224,127,240,127,248,60,30,28,15,14,3,143,1,199,128,240,63,224,31,252,15,255,7,135,131,193,193,224,224,240,112,120,56,60,60,31,252,15,252,7,255,3,195,193,224,240,240,120,120,60,60,30,30,31,15,255,7,255,3,254,0,3,224,7,252,7,255,3,199,131,193,225,224,240,240,0,120,0,60,0,30,0,15,0,7,128,3,192,1,224,0,240,0,120,60,60,30,15,30,7,255,1,255,0,62,0,63,224,31,252,15,255,7,135,131,193,225,224,240,240,120,120,60,60,30,30,15,15,7,135,131,195,193,225,224,240,240,120,120,60,60,30,30,30,15,255,7,255,3,254,0,63,254,31,255,15,255,135,128,3,192,1,224,0,240,0,120,0,60,0,31,254,15,255,7,255,131,192,1,224,0,240,0,120,0,60,0,30,0,15,255,135,255,195,255,224,63,254,31,255,15,255,135,128,3,192,1,224,0,240,0,120,0,60,0,31,254,15,255,7,255,131,192,1,224,0,240,0,120,0,60,0,30,0,15,0,7,128,3,192,0,3,224,7,252,7,255,3,199,131,193,225,224,240,240,0,120,0,60,0,30,0,15,63,135,159,195,207,225,224,240,240,120,120,60,60,30,15,30,7,255,1,255,0,62,0,60,30,30,15,15,7,135,131,195,193,225,224,240,240,120,120,60,60,30,31,255,15,255,135,255,195,193,225,224,240,240,120,120,60,60,30,30,15,15,7,135,131,195,193,224,31,254,15,255,7,255,128,60,0,30,0,15,0,7,128,3,192,1,224,0,240,0,120,0,60,0,30,0,15,0,7,128,3,192,1,224,0,240,7,255,131,255,193,255,224,3,254,1,255,0,255,128,3,192,1,224,0,240,0,120,0,60,0,30,0,15,0,7,128,3,192,1,224,0,240,0,112,240,56,120,60,30,62,15,254,3,254,0,126,0,60,14,30,15,15,7,7,135,131,195,193,227,192,241,224,120,224,60,240,31,240,15,248,7,254,3,207,1,227,128,241,224,120,240,60,60,30,30,15,7,135,131,195,192,240,30,0,15,0,7,128,3,192,1,224,0,240,0,120,0,60,0,30,0,15,0,7,128,3,192,1,224,0,240,0,120,0,60,0,30,0,15,0,7,255,131,255,193,255,224,62,31,31,15,143,135,199,195,227,243,241,217,248,236,220,118,238,59,239,28,247,142,123,199,61,227,192,241,224,120,240,60,120,30,60,15,30,7,143,3,199,129,227,192,240,62,30,31,15,15,135,135,227,195,241,225,248,240,238,120,119,60,57,158,30,239,15,119,135,155,195,204,225,231,112,243,184,120,252,60,126,30,63,15,15,135,135,195,195,224,3,224,7,252,7,255,3,199,131,193,225,224,240,240,120,120,60,60,30,30,15,15,7,135,131,195,193,225,224,240,240,120,120,60,60,30,15,30,7,255,1,255,0,62,0,63,224,31,252,15,255,7,135,195,193,225,224,112,240,56,120,28,60,30,30,31,15,255,7,255,3,254,1,224,0,240,0,120,0,60,0,30,0,15,0,7,128,3,192,0,3,224,7,252,7,255,3,199,131,193,225,224,240,240,120,120,60,60,30,30,15,15,7,135,131,195,193,225,224,240,240,120,120,60,60,30,15,30,7,255,1,255,0,63,0,3,192,0,224,0,120,0,28,0,15,0,63,224,31,252,15,255,7,135,131,193,225,224,240,240,120,120,60,60,30,30,30,15,255,7,255,3,255,1,231,128,241,192,120,240,60,120,30,30,15,15,7,131,195,193,224,3,240,7,254,7,255,131,195,193,192,224,224,112,112,0,60,0,31,128,7,248,1,254,0,63,128,3,224,0,240,0,56,112,28,60,14,31,15,7,255,1,255,0,63,0,127,254,63,255,31,255,128,120,0,60,0,30,0,15,0,7,128,3,192,1,224,0,240,0,120,0,60,0,30,0,15,0,7,128,3,192,1,224,0,240,0,120,0,60,0,60,30,30,15,15,7,135,131,195,193,225,224,240,240,120,120,60,60,30,30,15,15,7,135,131,195,193,225,224,240,240,120,120,60,60,30,15,30,7,255,1,255,0,62,0,120,15,60,7,14,3,135,1,195,193,225,224,224,112,112,56,56,30,60,15,28,3,142,1,199,0,231,128,123,192,29,192,14,224,7,240,3,248,0,248,0,124,0,62,0,113,199,56,227,156,113,206,56,231,28,115,159,57,205,156,230,206,115,102,25,179,14,219,135,109,195,182,225,209,112,248,248,124,124,62,62,31,30,15,143,3,199,129,227,192,120,14,30,15,15,7,3,199,129,227,128,123,192,61,192,15,224,3,224,1,240,0,248,0,124,0,127,0,63,128,61,224,28,240,30,60,14,30,15,7,135,3,199,128,240,120,7,28,7,143,3,131,131,193,225,192,240,224,60,240,30,112,7,120,3,248,0,252,0,124,0,30,0,15,0,7,128,3,192,1,224,0,240,0,120,0,60,0,30,0,63,252,31,254,15,255,0,7,128,7,128,3,192,3,192,1,224,1,224,0,224,0,240,0,240,0,120,0,120,0,60,0,60,0,30,0,30,0,15,255,7,255,131,255,192,7,248,3,252,1,254,0,240,0,120,0,60,0,30,0,15,0,7,128,3,192,1,224,0,240,0,120,0,60,0,30,0,15,0,7,128,3,192,1,224,0,240,0,120,0,60,0,30,0,15,0,7,248,3,252,1,254,0,56,0,30,0,7,0,3,192,1,224,0,112,0,60,0,30,0,7,0,3,192,1,224,0,112,0,60,0,14,0,7,128,3,192,0,224,0,120,0,60,0,14,0,7,128,3,192,0,224,0,120,0,28,0,15,0,7,128,31,240,15,248,7,252,0,30,0,15,0,7,128,3,192,1,224,0,240,0,120,0,60,0,30,0,15,0,7,128,3,192,1,224,0,240,0,120,0,60,0,30,0,15,0,7,128,3,192,1,224,31,240,15,248,7,252,0,1,192,1,240,0,248,0,238,0,119,0,115,128,56,224,60,112,28,28,14,14,14,7,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,15,255,135,255,192,7,128,1,224,0,112,0,60,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,240,3,252,3,255,3,199,193,193,224,0,240,0,120,31,252,31,254,30,15,15,7,135,131,195,227,225,255,240,127,248,15,60,60,0,30,0,15,0,7,128,3,192,1,239,128,255,224,127,248,62,60,30,14,15,7,7,131,195,193,225,224,240,240,120,120,56,60,28,31,30,15,255,7,255,3,223,0,3,224,7,252,7,255,3,199,195,193,225,224,112,240,0,120,0,60,0,30,0,15,3,135,131,193,227,224,255,224,63,224,7,192,0,30,0,15,0,7,128,3,192,1,224,60,240,63,120,63,252,62,62,30,15,15,7,135,131,195,193,225,224,240,240,120,120,60,60,30,31,31,7,255,129,251,192,121,224,3,224,7,252,7,255,3,199,131,193,225,224,240,240,120,127,252,63,254,30,0,15,0,7,129,129,227,224,255,224,63,224,7,192,0,127,0,255,128,255,192,120,0,60,0,30,0,15,0,127,254,63,255,31,255,128,240,0,120,0,60,0,30,0,15,0,7,128,3,192,1,224,0,240,0,120,0,60,0,7,158,15,239,7,255,135,199,195,193,225,224,240,240,120,120,60,60,30,30,15,15,7,135,199,193,255,224,254,240,30,120,0,56,0,28,0,30,3,255,1,255,0,255,0,60,0,30,0,15,0,7,128,3,192,1,239,128,255,224,127,248,62,60,30,14,15,7,7,131,195,193,225,224,240,240,120,120,60,60,30,30,15,15,7,135,131,195,193,224,1,192,1,240,0,248,0,56,0,0,0,0,0,127,128,63,192,31,224,0,240,0,120,0,60,0,30,0,15,0,7,128,3,192,1,224,0,240,0,120,7,255,227,255,241,255,248,0,56,0,62,0,31,0,7,0,0,0,0,0,0,0,63,240,31,248,15,252,0,30,0,15,0,7,128,3,192,1,224,0,240,0,120,0,60,0,30,0,15,0,7,128,3,192,1,224,0,240,0,240,31,248,15,248,7,240,0,60,0,30,0,15,0,7,128,3,192,1,224,240,240,120,120,120,60,120,30,60,15,60,7,254,3,254,1,255,128,243,192,120,240,60,120,30,30,15,15,7,131,195,193,224,127,192,63,224,31,240,0,120,0,60,0,30,0,15,0,7,128,3,192,1,224,0,240,0,120,0,60,0,30,0,15,0,7,128,3,192,1,224,0,127,192,63,224,7,240,119,56,63,254,31,255,142,115,199,25,227,140,241,198,120,227,60,113,158,56,207,28,103,142,51,199,25,227,140,241,198,120,227,60,61,240,31,252,15,255,7,199,131,193,193,224,224,240,120,120,60,60,30,30,15,15,7,135,131,195,193,225,224,240,240,120,120,60,3,224,7,252,7,255,3,199,131,193,225,224,240,240,120,120,60,60,30,30,15,15,7,135,131,193,227,192,255,224,63,224,7,192,61,240,31,252,15,255,7,199,131,193,193,224,224,240,120,120,60,60,30,30,15,15,7,7,131,131,227,193,255,224,255,224,123,224,60,0,30,0,15,0,7,128,3,192,0,7,158,7,239,7,255,135,199,195,193,225,224,240,240,120,120,60,60,30,30,15,15,7,135,131,195,227,224,255,240,63,120,15,60,0,30,0,15,0,7,128,3,192,1,224,30,248,15,254,7,255,131,227,225,224,240,240,120,120,28,60,0,30,0,15,0,7,128,3,192,1,224,0,240,0,120,0,60,0,7,240,15,252,15,255,7,135,195,192,1,224,0,252,0,63,224,7,252,0,31,0,7,135,3,195,193,225,255,224,127,240,15,224,7,128,3,192,1,224,0,240,7,255,227,255,241,255,248,15,0,7,128,3,192,1,224,0,240,0,120,0,60,0,30,0,15,0,7,128,3,255,0,255,128,31,192,60,30,30,15,15,7,135,131,195,193,225,224,240,240,120,120,60,60,30,30,14,15,7,7,131,131,227,192,255,192,63,224,15,192,120,15,28,7,14,7,135,131,193,193,192,224,224,120,240,60,112,14,56,7,188,3,220,0,238,0,127,0,63,0,15,128,7,192,113,199,56,227,156,113,206,56,231,60,115,155,48,205,152,102,220,59,110,29,55,15,155,135,199,195,227,192,241,224,120,240,60,120,60,30,30,14,7,143,1,207,0,247,128,63,128,15,128,7,192,3,224,3,248,1,252,1,239,1,227,192,241,224,240,120,240,28,120,15,28,7,15,7,135,131,129,193,192,241,224,56,224,30,112,15,120,3,184,1,252,0,254,0,62,0,31,0,7,128,3,128,3,192,1,224,0,224,0,240,0,112,0,63,252,31,254,15,255,0,15,128,7,128,7,128,7,128,7,128,7,192,3,192,3,192,3,192,3,192,1,255,224,255,240,127,248,0,30,0,63,0,31,128,30,0,15,0,7,128,3,192,1,224,0,240,0,112,0,56,0,60,3,252,1,252,0,255,0,3,192,0,224,0,112,0,60,0,30,0,15,0,7,128,3,192,1,224,0,126,0,63,0,7,128,1,224,0,240,0,120,0,60,0,30,0,15,0,7,128,3,192,1,224,0,240,0,120,0,60,0,30,0,15,0,7,128,3,192,1,224,0,240,0,120,0,60,0,30,0,15,0,7,128,3,192,1,224,0,240,0,120,0,62,0,31,128,15,224,0,240,0,56,0,28,0,15,0,7,128,3,192,1,224,0,240,0,120,0,31,224,7,240,7,248,7,128,3,192,1,224,0,240,0,120,0,60,0,28,0,14,0,15,0,63,128,31,128,15,128,0,30,0,31,135,159,227,143,121,199,159,227,199,240,1,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +static const Glyph f2_28_glyphs[]={{0,17,0,0,21,17,32},{0,17,21,0,0,17,33},{45,17,21,0,0,17,34},{90,17,21,0,0,17,35},{135,17,30,0,-4,17,36},{199,18,21,0,0,17,37},{247,17,21,0,0,17,38},{292,17,21,0,0,17,39},{337,17,28,0,-3,17,40},{397,17,28,0,-3,17,41},{457,17,17,0,4,17,42},{494,17,17,0,4,17,43},{531,17,9,0,17,17,44},{551,17,12,0,9,17,45},{577,17,5,0,16,17,46},{588,17,27,0,-3,17,47},{646,17,21,0,0,17,48},{691,17,21,0,0,17,49},{736,17,21,0,0,17,50},{781,17,21,0,0,17,51},{826,17,21,0,0,17,52},{871,17,21,0,0,17,53},{916,17,21,0,0,17,54},{961,17,21,0,0,17,55},{1006,17,21,0,0,17,56},{1051,17,21,0,0,17,57},{1096,17,16,0,5,17,58},{1130,17,21,0,5,17,59},{1175,17,18,0,3,17,60},{1214,17,15,0,6,17,61},{1246,17,18,0,3,17,62},{1285,17,21,0,0,17,63},{1330,17,26,0,0,17,64},{1386,17,21,0,0,17,65},{1431,17,21,0,0,17,66},{1476,17,21,0,0,17,67},{1521,17,21,0,0,17,68},{1566,17,21,0,0,17,69},{1611,17,21,0,0,17,70},{1656,17,21,0,0,17,71},{1701,17,21,0,0,17,72},{1746,17,21,0,0,17,73},{1791,17,21,0,0,17,74},{1836,17,21,0,0,17,75},{1881,17,21,0,0,17,76},{1926,17,21,0,0,17,77},{1971,17,21,0,0,17,78},{2016,17,21,0,0,17,79},{2061,17,21,0,0,17,80},{2106,17,26,0,0,17,81},{2162,17,21,0,0,17,82},{2207,17,21,0,0,17,83},{2252,17,21,0,0,17,84},{2297,17,21,0,0,17,85},{2342,17,21,0,0,17,86},{2387,17,21,0,0,17,87},{2432,17,21,0,0,17,88},{2477,17,21,0,0,17,89},{2522,17,21,0,0,17,90},{2567,17,27,0,-3,17,91},{2625,17,27,0,-3,17,92},{2683,17,27,0,-3,17,93},{2741,17,21,0,0,17,94},{2786,17,4,0,21,17,95},{2795,17,22,0,-1,17,96},{2842,17,16,0,5,17,97},{2876,17,21,0,0,17,98},{2921,17,16,0,5,17,99},{2955,17,21,0,0,17,100},{3000,17,16,0,5,17,101},{3034,17,21,0,0,17,102},{3079,17,21,0,5,17,103},{3124,17,21,0,0,17,104},{3169,17,22,0,-1,17,105},{3216,17,28,0,-2,17,106},{3276,17,21,0,0,17,107},{3321,17,21,0,0,17,108},{3366,17,16,0,5,17,109},{3400,17,16,0,5,17,110},{3434,17,16,0,5,17,111},{3468,17,21,0,5,17,112},{3513,17,21,0,5,17,113},{3558,17,16,0,5,17,114},{3592,17,16,0,5,17,115},{3626,17,20,0,1,17,116},{3669,17,16,0,5,17,117},{3703,17,16,0,5,17,118},{3737,17,16,0,5,17,119},{3771,17,16,0,5,17,120},{3805,17,21,0,5,17,121},{3850,17,16,0,5,17,122},{3884,17,27,0,-3,17,123},{3942,17,27,0,-3,17,124},{4000,17,27,0,-3,17,125},{4058,17,14,0,7,17,126}}; +static const uint8_t f2_30_bits[]={1,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,0,0,0,0,0,0,0,28,0,15,128,3,224,0,112,0,15,60,3,207,0,243,192,60,240,15,60,3,207,0,243,192,60,112,15,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,134,0,225,128,48,224,12,56,7,14,15,255,227,255,248,255,254,6,28,1,135,0,225,192,56,112,14,24,3,134,7,255,241,255,252,127,255,7,14,1,195,0,112,192,24,112,6,28,0,0,64,0,48,0,12,0,3,0,0,192,1,254,0,255,224,127,248,30,223,15,51,195,204,96,243,0,62,192,7,240,1,254,0,63,224,3,254,0,63,192,13,240,3,62,0,199,143,49,227,204,120,251,60,31,255,3,255,128,127,128,3,0,0,192,0,48,0,12,0,1,0,31,1,199,240,49,207,14,56,227,135,28,96,227,156,28,119,3,159,192,63,184,3,238,0,3,128,0,112,0,29,240,7,127,0,254,112,59,142,14,113,193,142,56,113,199,28,60,227,3,248,224,62,0,7,224,3,252,1,255,128,249,240,60,60,15,6,1,192,0,120,0,14,0,7,192,3,248,1,254,28,121,207,30,127,135,143,225,225,240,120,120,30,31,7,207,192,255,248,63,207,3,225,192,1,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,7,128,1,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,128,3,224,1,248,0,252,0,124,0,62,0,15,0,3,192,1,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,7,128,0,224,0,60,0,15,128,1,240,0,63,0,7,224,0,248,0,14,0,0,0,0,0,7,0,1,240,0,126,0,15,192,0,248,0,30,0,3,192,0,240,0,28,0,7,128,1,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,7,128,1,224,0,112,0,60,0,15,0,7,128,3,224,3,240,1,248,0,124,0,28,0,0,0,0,1,224,0,120,0,30,0,7,128,33,193,30,49,199,205,249,255,254,31,252,0,120,0,63,0,28,224,15,60,7,143,1,225,224,48,48,0,0,0,0,0,0,0,1,224,0,120,0,30,0,7,128,1,224,15,255,195,255,240,255,252,1,224,0,120,0,30,0,7,128,1,224,0,120,0,0,0,0,0,0,0,0,1,224,0,120,0,62,0,15,0,3,192,1,224,0,120,0,28,0,7,0,0,0,0,3,255,0,255,192,63,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,224,0,252,0,63,0,15,192,3,240,0,120,0,0,7,0,3,192,0,224,0,120,0,30,0,15,0,3,192,0,240,0,120,0,30,0,7,128,3,192,0,240,0,56,0,30,0,7,128,3,192,0,240,0,60,0,30,0,7,128,1,192,0,240,0,60,0,30,0,7,128,1,224,0,240,0,60,0,0,3,240,3,255,1,255,224,124,248,60,15,15,3,195,192,240,240,60,60,15,15,51,195,222,240,247,188,60,207,15,3,195,192,240,240,60,60,15,15,3,193,243,224,127,248,15,252,0,252,0,0,248,0,254,0,127,128,127,224,30,120,7,30,1,135,128,1,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,7,128,127,254,31,255,135,255,224,3,240,3,255,1,255,224,124,248,60,15,15,3,195,192,240,0,60,0,15,0,7,128,3,224,1,240,0,248,0,124,0,62,0,31,0,15,128,7,192,3,240,0,255,252,63,255,15,255,192,31,254,7,255,129,255,224,0,120,0,60,0,30,0,15,0,7,128,1,248,0,127,0,31,224,0,124,0,15,0,3,192,0,240,0,60,60,15,15,3,193,243,224,127,248,15,252,0,252,0,0,62,0,15,0,7,128,3,192,1,240,0,120,0,60,0,31,0,15,128,3,195,193,224,240,248,60,60,15,15,3,195,255,240,255,252,63,255,0,3,192,0,240,0,60,0,15,0,3,192,31,254,7,255,129,255,224,120,0,30,0,7,128,1,224,0,123,224,31,252,7,255,129,243,240,120,60,0,15,0,3,192,0,240,0,60,28,15,7,131,193,243,224,127,248,15,252,0,252,0,0,120,0,60,0,15,0,7,128,3,192,0,240,0,120,0,60,0,15,120,7,255,129,255,240,120,124,60,15,15,1,227,192,120,240,30,60,7,135,131,193,241,240,63,248,7,252,0,124,0,63,255,143,255,227,255,248,240,60,60,15,15,3,195,129,224,0,120,0,28,0,15,0,3,192,1,224,0,120,0,60,0,15,0,3,192,1,224,0,120,0,60,0,15,0,7,128,1,224,0,3,240,3,255,1,255,224,124,248,60,15,15,3,195,192,240,112,56,30,30,3,255,0,63,0,31,224,15,60,7,135,131,192,240,240,60,60,15,15,3,193,243,240,127,248,15,252,0,252,0,1,240,1,255,0,255,224,124,124,30,15,15,1,227,192,120,240,30,60,7,143,3,193,225,240,127,252,15,254,1,247,128,3,192,0,240,0,120,0,60,0,15,0,7,128,3,224,0,240,0,1,224,0,252,0,63,0,15,192,1,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,224,0,252,0,63,0,15,192,1,224,0,1,224,0,252,0,63,0,15,192,1,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,124,0,30,0,15,128,3,192,0,240,0,124,0,30,0,7,128,1,192,0,0,0,0,0,192,0,240,0,252,1,254,1,254,1,254,0,252,0,60,0,15,0,3,248,0,127,128,7,248,0,127,128,3,240,0,60,0,3,0,0,0,0,0,63,254,15,255,131,255,224,0,0,0,0,0,0,0,0,0,0,0,63,254,15,255,131,255,224,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,3,192,0,252,0,31,192,1,252,0,31,192,1,248,0,31,0,7,192,7,224,7,240,7,240,7,240,3,240,0,240,0,48,0,0,0,0,0,0,31,224,7,254,1,255,192,1,240,0,30,0,7,128,1,224,0,120,0,62,1,255,0,127,128,31,192,7,128,1,224,0,120,0,0,0,0,0,0,0,0,56,0,31,0,7,192,0,224,0,3,240,3,255,1,255,224,252,124,60,7,158,0,231,0,57,193,238,112,255,156,121,231,28,57,199,14,113,195,156,112,231,28,57,199,14,113,195,156,121,231,15,249,193,230,112,0,30,0,3,192,0,252,0,31,240,3,252,0,63,0,3,240,0,252,0,63,0,15,192,7,248,1,254,0,115,128,28,224,15,60,3,207,0,225,192,56,112,30,30,7,135,129,255,224,127,248,63,255,15,3,195,192,240,224,28,120,7,158,1,224,63,240,15,255,3,255,224,240,248,60,31,15,3,195,192,240,240,56,60,30,15,255,3,255,128,255,240,60,62,15,3,195,192,240,240,60,60,15,15,3,195,195,224,255,248,63,252,15,252,0,3,240,3,255,1,255,224,124,248,60,15,15,3,195,192,0,240,0,60,0,15,0,3,192,0,240,0,60,0,15,0,3,192,0,240,0,60,15,15,3,193,243,224,127,248,15,252,0,252,0,63,224,15,254,3,255,192,240,248,60,30,15,3,195,192,240,240,60,60,15,15,3,195,192,240,240,60,60,15,15,3,195,192,240,240,60,60,15,15,7,131,195,224,255,240,63,248,15,248,0,31,255,7,255,193,255,240,120,0,30,0,7,128,1,224,0,120,0,30,0,7,255,129,255,224,127,248,30,0,7,128,1,224,0,120,0,30,0,7,128,1,224,0,127,252,31,255,7,255,192,63,255,15,255,195,255,240,240,0,60,0,15,0,3,192,0,240,0,60,0,15,0,3,255,224,255,248,63,254,15,0,3,192,0,240,0,60,0,15,0,3,192,0,240,0,60,0,15,0,0,3,240,3,255,1,255,224,124,248,60,15,15,3,195,192,0,240,0,60,0,15,0,3,207,240,243,252,60,255,15,3,195,192,240,240,60,60,15,15,3,193,243,224,127,248,15,252,0,252,0,60,30,15,7,131,193,224,240,120,60,30,15,7,131,193,224,240,120,60,30,15,255,131,255,224,255,248,60,30,15,7,131,193,224,240,120,60,30,15,7,131,193,224,240,120,60,30,15,7,128,31,255,7,255,193,255,240,7,128,1,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,127,252,31,255,7,255,192,1,255,0,127,192,31,240,0,60,0,15,0,3,192,0,240,0,60,0,15,0,3,192,0,240,0,60,0,15,0,3,192,0,241,224,60,120,15,31,7,131,227,224,255,240,31,248,1,252,0,60,15,15,3,195,193,224,240,120,60,60,15,15,3,199,192,241,224,60,120,15,252,3,255,0,255,192,60,248,15,30,3,199,128,240,240,60,60,15,7,131,193,224,240,60,60,15,15,3,224,30,0,7,128,1,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,7,128,1,224,0,127,252,31,255,7,255,192,62,15,143,131,227,225,248,252,126,63,31,142,207,227,179,248,254,222,61,183,143,125,227,222,120,247,158,60,231,143,1,227,192,120,240,30,60,7,143,1,227,192,120,240,30,60,7,143,1,224,62,15,15,131,195,240,240,252,60,63,15,15,227,195,248,240,246,60,61,207,15,115,195,220,240,243,188,60,239,15,59,195,199,240,241,252,60,127,15,15,195,195,240,240,252,60,31,15,7,192,3,240,3,255,1,255,224,124,248,60,31,15,3,195,192,240,240,60,60,15,15,3,195,192,240,240,60,60,15,15,3,195,192,240,240,60,60,15,15,7,193,243,224,127,248,15,252,0,252,0,63,240,15,255,3,255,224,240,124,60,15,15,1,227,192,120,240,30,60,15,15,7,195,255,240,255,248,63,248,15,0,3,192,0,240,0,60,0,15,0,3,192,0,240,0,60,0,15,0,0,3,240,3,255,1,255,224,124,248,60,15,15,3,195,192,240,240,60,60,15,15,3,195,192,240,240,60,60,15,15,3,195,192,240,240,60,60,15,15,3,193,243,224,127,248,15,252,0,254,0,7,192,0,240,0,30,0,7,128,0,240,63,240,15,255,3,255,224,240,248,60,15,15,3,195,192,240,240,60,60,15,15,7,131,255,224,255,240,63,248,15,60,3,199,128,241,224,60,60,15,15,3,193,224,240,120,60,15,15,3,192,3,240,3,255,1,255,224,124,248,60,15,15,3,195,192,0,248,0,31,0,7,248,0,255,192,15,248,0,127,0,3,192,0,120,0,30,60,7,143,3,225,241,240,127,248,15,252,0,252,0,127,255,31,255,199,255,240,7,128,1,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,7,128,1,224,0,120,0,60,15,15,3,195,192,240,240,60,60,15,15,3,195,192,240,240,60,60,15,15,3,195,192,240,240,60,60,15,15,3,195,192,240,240,60,60,15,15,7,129,243,224,127,248,15,252,0,252,0,120,7,158,1,227,192,112,240,60,60,15,15,3,193,192,224,120,120,30,30,7,135,128,225,192,60,112,15,60,3,207,0,115,128,30,224,7,248,1,254,0,63,0,15,192,3,240,0,252,0,241,227,188,120,231,30,57,199,142,113,227,156,121,231,30,113,207,156,115,39,28,201,199,179,113,236,220,59,55,14,205,195,179,112,252,248,62,62,15,143,131,227,224,248,120,62,30,15,135,128,124,15,143,3,193,225,224,120,120,15,60,3,207,0,127,128,31,224,3,240,0,252,0,30,0,15,192,3,240,1,254,0,127,128,60,240,15,60,7,135,129,225,224,240,60,60,15,30,1,224,120,7,158,1,195,192,240,240,60,30,30,7,135,128,227,192,60,240,15,56,1,254,0,127,0,15,192,3,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,7,128,1,224,0,120,0,63,254,15,255,131,255,224,0,248,0,60,0,15,0,7,128,3,224,0,240,0,120,0,30,0,15,0,3,192,1,224,0,248,0,60,0,30,0,7,128,3,224,0,255,248,63,254,15,255,128,0,0,0,255,128,63,224,15,248,3,192,0,240,0,60,0,15,0,3,192,0,240,0,60,0,15,0,3,192,0,240,0,60,0,15,0,3,192,0,240,0,60,0,15,0,3,192,0,240,0,60,0,15,0,3,192,0,240,0,63,224,15,248,3,254,0,56,0,15,0,1,192,0,120,0,30,0,3,192,0,240,0,60,0,7,128,1,224,0,120,0,15,0,3,192,0,112,0,30,0,7,128,0,240,0,60,0,15,0,1,224,0,120,0,14,0,3,192,0,240,0,30,0,7,128,1,224,0,60,0,15,0,0,0,3,254,0,255,128,63,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,255,128,63,224,15,248,0,1,224,0,120,0,63,0,15,192,7,184,1,206,0,243,192,56,112,14,28,7,135,129,192,224,240,60,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,255,195,255,240,255,252,7,128,0,240,0,30,0,3,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,240,1,255,0,255,224,124,124,30,15,0,3,192,0,240,31,252,31,255,7,131,195,192,240,240,60,60,15,15,207,193,255,240,127,188,7,207,0,60,0,15,0,3,192,0,240,0,60,0,15,62,3,223,192,255,248,63,62,15,7,195,192,240,240,60,60,15,15,3,195,192,240,240,60,60,15,15,135,195,243,224,255,248,61,252,15,62,0,3,240,3,255,1,255,224,124,248,60,15,15,3,195,192,0,240,0,60,0,15,0,3,192,0,240,60,60,15,7,207,129,255,224,63,240,3,240,0,0,15,0,3,192,0,240,0,60,0,15,1,243,192,254,240,127,252,63,63,15,7,195,192,240,240,60,60,15,15,3,195,192,240,240,60,60,15,15,7,195,243,240,127,252,15,239,1,243,192,3,240,3,255,1,255,192,124,248,60,30,15,3,195,192,240,255,252,63,255,15,0,3,192,0,240,0,60,14,7,207,129,255,224,63,240,3,240,0,0,127,128,63,224,31,248,15,128,3,192,0,240,0,60,0,255,254,63,255,143,255,224,60,0,15,0,3,192,0,240,0,60,0,15,0,3,192,0,240,0,60,0,15,0,3,192,0,240,0,7,207,3,251,193,255,240,252,252,60,31,15,3,195,192,240,240,60,60,15,15,3,195,192,240,248,124,31,255,7,251,192,124,240,0,60,0,15,0,3,192,3,224,63,248,15,252,3,252,0,60,0,15,0,3,192,0,240,0,60,0,15,62,3,223,192,255,248,63,62,15,135,195,192,240,240,60,60,15,15,3,195,192,240,240,60,60,15,15,3,195,192,240,240,60,60,15,15,3,192,0,224,0,124,0,31,0,3,128,0,0,0,0,0,0,0,127,192,31,240,7,252,0,15,0,3,192,0,240,0,60,0,15,0,3,192,0,240,0,60,0,15,0,3,192,0,240,15,255,195,255,240,255,252,0,56,0,31,0,7,192,0,224,0,0,0,0,0,0,0,127,240,31,252,7,255,0,3,192,0,240,0,60,0,15,0,3,192,0,240,0,60,0,15,0,3,192,0,240,0,60,0,15,0,3,192,0,240,0,120,0,62,3,255,128,255,192,63,192,0,60,0,15,0,3,192,0,240,0,60,0,15,3,195,193,240,240,120,60,60,15,15,3,199,128,243,224,63,240,15,252,3,255,0,241,224,60,120,15,15,3,195,224,240,120,60,15,15,3,192,255,192,63,240,15,252,0,15,0,3,192,0,240,0,60,0,15,0,3,192,0,240,0,60,0,15,0,3,192,0,240,0,60,0,15,0,3,192,0,240,0,62,0,7,254,0,255,128,31,224,57,222,14,255,195,191,248,243,158,60,231,143,57,227,206,120,243,158,60,231,143,57,227,206,120,243,158,60,231,143,57,227,206,120,243,158,60,231,128,60,248,15,127,3,255,224,252,248,60,31,15,3,195,192,240,240,60,60,15,15,3,195,192,240,240,60,60,15,15,3,195,192,240,240,60,60,15,0,3,240,3,254,1,255,192,124,248,60,30,15,3,195,192,240,240,60,60,15,15,3,195,192,240,240,60,60,30,7,207,129,255,192,63,224,3,240,0,60,248,15,127,3,255,224,252,248,60,31,15,3,195,192,240,240,60,60,15,15,3,195,192,240,240,60,62,31,15,207,131,255,224,247,240,60,248,15,0,3,192,0,240,0,60,0,15,0,0,7,207,3,251,193,255,240,252,252,60,31,15,3,195,192,240,240,60,60,15,15,3,195,192,240,240,60,60,31,15,207,193,255,240,63,188,7,207,0,3,192,0,240,0,60,0,15,0,3,192,30,124,7,191,129,255,240,126,124,30,15,135,129,225,224,120,120,0,30,0,7,128,1,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,0,3,248,3,255,128,255,240,124,124,30,7,7,128,1,240,0,63,224,7,254,0,31,192,0,248,0,30,30,7,135,199,225,255,240,63,248,3,252,0,3,128,1,224,0,120,0,30,0,7,128,31,255,199,255,241,255,252,7,128,1,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,7,128,1,224,0,124,0,15,252,1,255,0,63,192,60,15,15,3,195,192,240,240,60,60,15,15,3,195,192,240,240,60,60,15,15,3,195,192,240,240,60,60,30,7,207,129,255,192,63,240,3,240,0,120,7,142,1,195,192,240,240,60,28,14,7,135,129,225,224,56,112,15,60,3,207,0,115,128,28,224,7,248,1,254,0,63,0,15,192,3,240,0,113,227,156,120,231,30,57,199,142,113,227,156,120,227,146,112,236,220,59,55,14,205,195,179,112,236,220,63,62,7,143,129,225,224,120,120,30,30,0,60,15,15,135,193,225,224,60,240,15,60,1,254,0,63,0,15,192,1,224,0,252,0,127,128,31,224,15,60,7,135,129,225,224,240,60,124,15,128,120,7,143,3,195,192,240,120,60,30,30,7,135,128,241,224,60,240,15,60,1,238,0,127,128,15,224,3,240,0,252,0,31,0,7,128,1,224,0,120,0,60,0,15,0,3,128,1,224,0,63,254,15,255,131,255,224,0,248,0,60,0,30,0,15,0,7,128,3,224,1,240,0,120,0,60,0,30,0,15,128,3,255,224,255,248,63,254,0,0,0,0,15,192,7,240,3,252,0,240,0,60,0,15,0,3,192,0,240,0,60,0,15,0,3,192,1,224,15,248,3,248,0,255,0,1,224,0,60,0,15,0,3,192,0,240,0,60,0,15,0,3,192,0,240,0,62,0,7,240,1,252,0,31,0,1,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,7,128,1,224,0,120,0,30,0,7,128,1,224,0,0,0,15,192,3,248,0,254,0,7,192,0,240,0,60,0,15,0,3,192,0,240,0,60,0,15,0,3,192,0,127,192,15,240,7,252,1,224,0,240,0,60,0,15,0,3,192,0,240,0,60,0,15,0,3,192,1,224,3,248,0,252,0,62,0,0,0,0,7,193,195,248,241,255,60,121,239,30,63,199,135,224,0,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +static const Glyph f2_30_glyphs[]={{0,18,0,0,22,18,32},{0,18,22,0,0,18,33},{50,18,22,0,0,18,34},{100,18,22,0,0,18,35},{150,18,32,0,-5,18,36},{222,19,22,0,0,18,37},{275,18,22,0,0,18,38},{325,18,22,0,0,18,39},{375,18,30,0,-4,18,40},{443,18,30,0,-4,18,41},{511,18,19,0,3,18,42},{554,18,17,0,5,18,43},{593,18,9,0,18,18,44},{614,18,12,0,10,18,45},{641,18,6,0,16,18,46},{655,18,29,0,-4,18,47},{721,18,22,0,0,18,48},{771,18,22,0,0,18,49},{821,18,22,0,0,18,50},{871,18,22,0,0,18,51},{921,18,22,0,0,18,52},{971,18,22,0,0,18,53},{1021,18,22,0,0,18,54},{1071,18,22,0,0,18,55},{1121,18,22,0,0,18,56},{1171,18,22,0,0,18,57},{1221,18,17,0,5,18,58},{1260,18,22,0,5,18,59},{1310,18,19,0,3,18,60},{1353,18,16,0,6,18,61},{1389,18,19,0,3,18,62},{1432,18,22,0,0,18,63},{1482,18,27,0,0,18,64},{1543,18,22,0,0,18,65},{1593,18,22,0,0,18,66},{1643,18,22,0,0,18,67},{1693,18,22,0,0,18,68},{1743,18,22,0,0,18,69},{1793,18,22,0,0,18,70},{1843,18,22,0,0,18,71},{1893,18,22,0,0,18,72},{1943,18,22,0,0,18,73},{1993,18,22,0,0,18,74},{2043,18,22,0,0,18,75},{2093,18,22,0,0,18,76},{2143,18,22,0,0,18,77},{2193,18,22,0,0,18,78},{2243,18,22,0,0,18,79},{2293,18,22,0,0,18,80},{2343,18,27,0,0,18,81},{2404,18,22,0,0,18,82},{2454,18,22,0,0,18,83},{2504,18,22,0,0,18,84},{2554,18,22,0,0,18,85},{2604,18,22,0,0,18,86},{2654,18,22,0,0,18,87},{2704,18,22,0,0,18,88},{2754,18,22,0,0,18,89},{2804,18,22,0,0,18,90},{2854,18,29,0,-4,18,91},{2920,18,29,0,-4,18,92},{2986,18,29,0,-4,18,93},{3052,18,22,0,0,18,94},{3102,18,4,0,22,18,95},{3111,18,24,0,-2,18,96},{3165,18,17,0,5,18,97},{3204,18,22,0,0,18,98},{3254,18,17,0,5,18,99},{3293,18,22,0,0,18,100},{3343,18,17,0,5,18,101},{3382,18,22,0,0,18,102},{3432,18,22,0,5,18,103},{3482,18,22,0,0,18,104},{3532,18,24,0,-2,18,105},{3586,18,29,0,-2,18,106},{3652,18,22,0,0,18,107},{3702,18,22,0,0,18,108},{3752,18,17,0,5,18,109},{3791,18,17,0,5,18,110},{3830,18,17,0,5,18,111},{3869,18,22,0,5,18,112},{3919,18,22,0,5,18,113},{3969,18,17,0,5,18,114},{4008,18,17,0,5,18,115},{4047,18,22,0,0,18,116},{4097,18,17,0,5,18,117},{4136,18,17,0,5,18,118},{4175,18,17,0,5,18,119},{4214,18,17,0,5,18,120},{4253,18,22,0,5,18,121},{4303,18,17,0,5,18,122},{4342,18,29,0,-4,18,123},{4408,18,29,0,-4,18,124},{4474,18,29,0,-4,18,125},{4540,18,15,0,7,18,126}}; +const Font fonts[]={{f0_16_bits,f0_16_glyphs,95,16,0},{f0_18_bits,f0_18_glyphs,95,18,0},{f0_24_bits,f0_24_glyphs,95,24,0},{f0_26_bits,f0_26_glyphs,95,26,0},{f0_28_bits,f0_28_glyphs,95,28,0},{f0_30_bits,f0_30_glyphs,95,30,0},{f0_34_bits,f0_34_glyphs,95,34,0},{f0_36_bits,f0_36_glyphs,95,36,0},{f0_40_bits,f0_40_glyphs,95,40,0},{f0_46_bits,f0_46_glyphs,95,46,0},{f0_50_bits,f0_50_glyphs,95,50,0},{f0_52_bits,f0_52_glyphs,95,52,0},{f0_56_bits,f0_56_glyphs,95,56,0},{f0_58_bits,f0_58_glyphs,15,58,0},{f0_60_bits,f0_60_glyphs,15,60,0},{f0_64_bits,f0_64_glyphs,15,64,0},{f0_72_bits,f0_72_glyphs,15,72,0},{f0_96_bits,f0_96_glyphs,15,96,0},{f0_120_bits,f0_120_glyphs,15,120,0},{f0_124_bits,f0_124_glyphs,15,124,0},{f0_132_bits,f0_132_glyphs,15,132,0},{f0_144_bits,f0_144_glyphs,15,144,0},{f0_212_bits,f0_212_glyphs,15,212,0},{f0_236_bits,f0_236_glyphs,15,236,0},{f1_18_bits,f1_18_glyphs,95,18,1},{f2_16_bits,f2_16_glyphs,95,16,2},{f2_18_bits,f2_18_glyphs,95,18,2},{f2_19_bits,f2_19_glyphs,95,19,2},{f2_20_bits,f2_20_glyphs,95,20,2},{f2_21_bits,f2_21_glyphs,95,21,2},{f2_22_bits,f2_22_glyphs,95,22,2},{f2_24_bits,f2_24_glyphs,95,24,2},{f2_26_bits,f2_26_glyphs,95,26,2},{f2_28_bits,f2_28_glyphs,95,28,2},{f2_30_bits,f2_30_glyphs,95,30,2}}; +const int font_count=sizeof(fonts)/sizeof(fonts[0]); \ No newline at end of file diff --git a/firmware/targets/mindreset_read_pico/main/fonts.hpp b/firmware/targets/mindreset_read_pico/main/fonts.hpp new file mode 100644 index 0000000..83aabce --- /dev/null +++ b/firmware/targets/mindreset_read_pico/main/fonts.hpp @@ -0,0 +1,6 @@ +#pragma once +#include +struct Glyph { uint32_t offset; int16_t width, height, left, top, advance; uint8_t character; }; +struct Font { const uint8_t* bitmap; const Glyph* glyphs; int count, size, family; }; +extern const Font fonts[]; +extern const int font_count; diff --git a/firmware/targets/mindreset_read_pico/main/fonts/Archivo-OFL.txt b/firmware/targets/mindreset_read_pico/main/fonts/Archivo-OFL.txt new file mode 100644 index 0000000..de1cee8 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/main/fonts/Archivo-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2020 The Archivo Project Authors (https://github.com/Omnibus-Type/Archivo) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/firmware/targets/mindreset_read_pico/main/fonts/JetBrainsMono-OFL.txt b/firmware/targets/mindreset_read_pico/main/fonts/JetBrainsMono-OFL.txt new file mode 100644 index 0000000..821a3da --- /dev/null +++ b/firmware/targets/mindreset_read_pico/main/fonts/JetBrainsMono-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. + +This license is copied below, and is also available with a FAQ at: https://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/firmware/targets/mindreset_read_pico/partitions.csv b/firmware/targets/mindreset_read_pico/partitions.csv new file mode 100644 index 0000000..a3eb613 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/partitions.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size +nvs, data, nvs, 0x9000, 0x6000 +phy_init, data, phy, 0xf000, 0x1000 +ota_0, app, ota_0, 0x10000, 0x400000 +ota_1, app, ota_1, 0x410000, 0x400000 +otadata, data, ota, 0x810000, 0x2000 diff --git a/firmware/targets/mindreset_read_pico/sdkconfig.defaults b/firmware/targets/mindreset_read_pico/sdkconfig.defaults new file mode 100644 index 0000000..46a3666 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/sdkconfig.defaults @@ -0,0 +1,50 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_COMPILER_OPTIMIZATION_PERF=y + +# Flash and PSRAM share the MSPI clock domain; the epdiy render task streams the framebuffer +# from PSRAM and starves the line queue below 120 MHz. The board's ZB25VQ128 enters +# high-performance mode through its status register (components/read_pico), and is outside +# the flash models ESP-IDF validates for temperature-based timing tuning. +CONFIG_IDF_EXPERIMENTAL_FEATURES=y +CONFIG_ESPTOOLPY_FLASHFREQ_120M=y +CONFIG_SPI_FLASH_HPM_ENA=y +CONFIG_SPI_FLASH_HPM_DC_DISABLE=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_120M=y +CONFIG_SPIRAM_TIMING_TUNING_POINT_VIA_TEMPERATURE_SENSOR=n +CONFIG_SPIRAM_USE_MALLOC=y +CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=32768 +CONFIG_ESP32S3_DATA_CACHE_64KB=y +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y + +# The panel power-up sequence polls PGOOD in millisecond steps. +CONFIG_FREERTOS_HZ=1000 + +CONFIG_BT_ENABLED=y +CONFIG_BT_NIMBLE_ENABLED=y +CONFIG_BT_NIMBLE_ROLE_CENTRAL=n +CONFIG_BT_NIMBLE_ROLE_OBSERVER=n +CONFIG_BT_NIMBLE_ROLE_BROADCASTER=y +CONFIG_BT_NIMBLE_ROLE_PERIPHERAL=y +CONFIG_BT_NIMBLE_MAX_CONNECTIONS=1 +CONFIG_BT_NIMBLE_MAX_BONDS=3 +CONFIG_BT_NIMBLE_NVS_PERSIST=y +CONFIG_BT_NIMBLE_SM_SC_ONLY=1 +CONFIG_BT_NIMBLE_SM_LVL=3 +CONFIG_BT_NIMBLE_LOG_LEVEL_WARNING=y +CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=517 +CONFIG_ESP_DESKTOP_BUDDY_RX_INBOX_DEPTH=32 +CONFIG_ESP_DESKTOP_BUDDY_TX_QUEUE_DEPTH=4 +CONFIG_ESP_DESKTOP_BUDDY_FOLDER_PUSH_MAX_RAW_CHUNK=4096 +CONFIG_ESP_DESKTOP_BUDDY_FOLDER_PUSH_MAX_DECODED_CHUNK=2880 +CONFIG_ESP_DESKTOP_BUDDY_FOLDER_PUSH_MAX_TRANSFER_BYTES=4194816 +CONFIG_ESP_DESKTOP_BUDDY_FOLDER_PUSH_FLAT_PATHS_ONLY=y +CONFIG_LOG_DEFAULT_LEVEL_INFO=y +CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y +CONFIG_ESP_MAIN_TASK_STACK_SIZE=12288 diff --git a/firmware/targets/waveshare_epaper_397/README.md b/firmware/targets/waveshare_epaper_397/README.md index 32a9e5a..17697c3 100644 --- a/firmware/targets/waveshare_epaper_397/README.md +++ b/firmware/targets/waveshare_epaper_397/README.md @@ -16,8 +16,7 @@ and retain the shared on-device confirmation and power gate. A BLE OTA from 0.9.0 to 0.9.1 through `tools/run_ota_validation.py` passed on 2026-09-22 (153 s, physical confirmation, reboot and version check). The OTA page used monochrome refreshes: a full refresh on entry and after 20 partial -updates, with partial refreshes for progress updates; power-loss recovery and rollback -remain unverified. +updates, with partial refreshes for progress updates. ## Display and controls diff --git a/protocol/README.md b/protocol/README.md index 81d530b..3f0bbec 100644 --- a/protocol/README.md +++ b/protocol/README.md @@ -53,6 +53,7 @@ Bridge 必须确认 `sec=true`、`protocol=1` 和 `caps` 包含 `usage.v1`。 | `waveshare_amoled_216` | `Waveshare AMOLED 2.16` | | `waveshare_epaper_397` | `Waveshare ePaper 3.97` | | `waveshare_rlcd_42` | `Waveshare RLCD 4.2` | +| `mindreset_read_pico` | `MindReset Read Pico` | 同名设备由 Bridge 添加地址后缀区分。BLE 广播名称继续使用 `QF-` 前缀、型号缩写和设备后缀;显示名称不参与 target 匹配或固件文件命名。 diff --git a/tools/read_pico/preview/preview.cpp b/tools/read_pico/preview/preview.cpp new file mode 100644 index 0000000..46b731d --- /dev/null +++ b/tools/read_pico/preview/preview.cpp @@ -0,0 +1,260 @@ +// Renders the nine diagnostic views and the gray-level test card with the firmware drawing code, one PGM per view and rotation. +#include "demo_view.hpp" +#include "epdiy.h" +#include +#include +#include +namespace { +constexpr int kWidth = 1216, kHeight = 684; +uint8_t front[kWidth / 2 * kHeight], back[kWidth / 2 * kHeight]; +EpdRotation rotation = EPD_ROT_LANDSCAPE; +int refreshes = 0, failures = 0; +EpdDrawMode last_mode = MODE_GC16; +EpdDrawError refresh(EpdiyHighlevelState *state, EpdDrawMode mode) { + ++refreshes; + last_mode = mode; + // epdiy advances its back buffer even when the panel update fails. + memcpy(state->back_fb, state->front_fb, sizeof(front)); + if (failures) { + --failures; + return EPD_DRAW_EMPTY_LINE_QUEUE; + } + return EPD_DRAW_SUCCESS; +} +// Same mapping as epdiy's _rotate(). +void to_native(int &x, int &y) { + const int lx = x, ly = y; + switch (rotation) { + case EPD_ROT_LANDSCAPE: + break; + case EPD_ROT_PORTRAIT: + x = kWidth - ly - 1; + y = lx; + break; + case EPD_ROT_INVERTED_LANDSCAPE: + x = kWidth - lx - 1; + y = kHeight - ly - 1; + break; + case EPD_ROT_INVERTED_PORTRAIT: + x = ly; + y = kHeight - lx - 1; + break; + } +} +} // namespace +extern "C" { +int epd_width(void) { return kWidth; } +int epd_height(void) { return kHeight; } +void epd_set_rotation(EpdRotation r) { rotation = r; } +void epd_draw_pixel(int x, int y, uint8_t color, uint8_t *fb) { + to_native(x, y); + if (x < 0 || x >= kWidth || y < 0 || y >= kHeight) + return; + uint8_t &b = fb[y * kWidth / 2 + x / 2]; + b = x % 2 ? (b & 0x0F) | (color & 0xF0) : (b & 0xF0) | (color >> 4); +} +void epd_poweron(void) {} +void epd_poweroff(void) {} +void epd_clear(void) {} +void epd_lcd_set_prefill_lines(int) {} +uint8_t *epd_hl_get_framebuffer(EpdiyHighlevelState *s) { return s->front_fb; } +void epd_hl_set_all_white(EpdiyHighlevelState *s) { memset(s->front_fb, 0xFF, sizeof(front)); } +EpdDrawError epd_hl_update_screen(EpdiyHighlevelState *s, EpdDrawMode m, int) { return refresh(s, m); } +EpdDrawError epd_hl_update_screen_full(EpdiyHighlevelState *s, EpdDrawMode m, int) { return refresh(s, m); } +EpdDrawError epd_hl_update_screen_from_white(EpdiyHighlevelState *s, EpdDrawMode m, int) { return refresh(s, m); } +} +int main(int argc, char **argv) { + using namespace usage_panel::read_pico; + const std::string out = argc > 1 ? argv[1] : "."; + EpdiyHighlevelState state{front, back}; + DisplayUi ui; + ui.begin(&state); + View live{}; + snprintf(live.device_name, sizeof(live.device_name), "QF-MR-PICO-3C7A"); + // Each view is rendered upright and, with the suffix "l", turned to landscape. + // The test card is portrait only. + for (int n = 0; n < 19; ++n) { + const char board = "123456789t"[n / 2]; + View v = demo_view(board, live); + v.orientation = n % 2; + ui.render(v, true); + const bool portrait = !(v.orientation & 1); + const int w = portrait ? kHeight : kWidth, h = portrait ? kWidth : kHeight; + FILE *f = fopen((out + "/board" + board + (n % 2 ? "l" : "") + ".pgm").c_str(), "wb"); + if (!f) + return 1; + fprintf(f, "P5\n%d %d\n255\n", w, h); + for (int y = 0; y < h; ++y) + for (int x = 0; x < w; ++x) { + int nx = x, ny = y; + to_native(nx, ny); + const uint8_t b = front[ny * kWidth / 2 + nx / 2]; + fputc((nx % 2 ? b >> 4 : b & 0x0F) * 17, f); + } + fclose(f); + } + // Every control drawn on the settings and OTA confirmation views must be reachable by touch. + const View settings = demo_view('7', live), confirm = demo_view('9', live); + const bool ok = ui.hit(settings, 342, 1128).kind == TapTarget::Done && + ui.hit(settings, 600, 750).kind == TapTarget::Option && + ui.hit(settings, 600, 750).row == 2 && ui.hit(settings, 600, 750).index == 2 && + ui.hit(confirm, 100, 1080).kind == TapTarget::OtaDeny && + ui.hit(confirm, 600, 1080).kind == TapTarget::OtaConfirm && + ui.hit(settings, 342, 60).kind == TapTarget::None; + puts(ok ? "hit tests passed" : "HIT TESTS FAILED"); + if (!ok) + return 1; + + DisplayUi recovering; + recovering.begin(&state); + refreshes = 0; + failures = 2; + recovering.render(settings); + if (recovering.ready() || refreshes != 2) + return 1; + recovering.render(settings); + if (!recovering.ready() || refreshes != 3) + return 1; + recovering.render(settings); + if (refreshes != 3) + return 1; + failures = 1; + recovering.render(settings, true); + if (!recovering.ready() || refreshes != 5) + return 1; + failures = 2; + recovering.render(settings, true); + if (recovering.ready() || refreshes != 7) + return 1; + puts("refresh failure and recovery checks passed"); + + View clock = live; + clock.page = Page::Clock; + clock.link = {true, true, false, 0}; + ui.render(clock); + const std::string no_short(reinterpret_cast(front), sizeof(front)); + usage_panel::UsageUpdate week{}; + week.state = usage_panel::SourceState::Partial; + week.week_window = {true, 87, false, 0}; + if (!clock.model.apply(week, clock.now_ms)) + return 1; + ui.render(clock); + // Without a short window the clock summary shows the week window. + if (!memcmp(no_short.data(), front, sizeof(front))) + return 1; + puts("week-only clock check passed"); + + // Text-only updates use DU; a changed historical curve can require a gray refresh. + View home = demo_view('1', live); + home.trend[46].claude = 90; + ui.render(home, true); + ++home.calendar.minute; + ui.render(home); + if (last_mode != MODE_DU) + return 1; + home.trend[46].claude = 10; + ui.render(home); + if (last_mode != MODE_GL16) + return 1; + for (int orientation : {0, 1}) { + View scrolling = demo_view('1', live); + scrolling.orientation = orientation; + // Start after the day-to-hour countdown reflow, which resizes the landscape gray bar. + scrolling.now_ms += 120000; + ui.render(scrolling, true); + for (int step = 1; step <= 10; ++step) { + scrolling.now_ms += 30000; + scrolling.calendar.minute = 32 + step / 2; + const int before = refreshes; + ui.render(scrolling); + if (refreshes != before && last_mode != MODE_DU) + return 1; + } + } + View alarm = demo_view('2', live); + ui.render(alarm, true); + alarm.now_ms += 60000; + ui.render(alarm); + if (last_mode != MODE_GL16) + return 1; + puts("refresh mode checks passed"); + + // Expired, future and undated history must render like an empty chart, including its labels. + for (int scenario = 0; scenario < 4; ++scenario) { + View history = demo_view('1', live); + history.link = {}; + if (scenario == 0) + history.now_ms += 48 * 3600000ULL; + else { + history.model = {}; + const uint32_t last = history.trend[history.trend_count - 1].epoch; + history.utc_epoch = scenario == 1 ? last + 48 * 3600 + : scenario == 2 ? history.trend[0].epoch - 1 : 0; + } + ui.render(history); + const std::string actual(reinterpret_cast(front), sizeof(front)); + history.trend_count = 0; + ui.render(history); + if (memcmp(actual.data(), front, sizeof(front))) + return 1; + } + // A restored RTC can date SD history before the first Bridge publication. + View restored = demo_view('1', live); + restored.link = {}; + restored.model = {}; + restored.utc_epoch = restored.trend[restored.trend_count - 1].epoch; + ui.render(restored); + const std::string recent(reinterpret_cast(front), sizeof(front)); + restored.trend_count = 0; + ui.render(restored); + if (!memcmp(recent.data(), front, sizeof(front))) + return 1; + puts("trend clock and expiry checks passed"); + + // The gray snapshot stays fixed between stored samples while its age labels advance. + auto trend_pixels = [&](const View &v, bool labels = true) { + ui.render(v); + std::string pixels; + for (int y = labels ? 760 : 830; y < (labels ? kWidth : 1141); ++y) + for (int x = labels ? 0 : 40; x < (labels ? kHeight : 505); ++x) { + int nx = x, ny = y; + to_native(nx, ny); + const uint8_t b = front[ny * kWidth / 2 + nx / 2]; + pixels += static_cast(nx % 2 ? b >> 4 : b & 0x0F); + } + return pixels; + }; + View latest = demo_view('1', live); + const std::string before = trend_pixels(latest); + usage_panel::UsageUpdate update{}; + update.provider = usage_panel::Provider::Claude; + update.state = usage_panel::SourceState::Partial; + update.sent_at = update.sampled_at = latest.model.snapshot(update.provider).sampled_at; + update.short_window = {true, 75, false, 0}; + if (!latest.model.apply(update, latest.now_ms) || trend_pixels(latest) != before) + return 1; + const std::string online = trend_pixels(latest); + latest.link = {}; + if (trend_pixels(latest) != online) + return 1; + const std::string curve = trend_pixels(latest, false); + latest.now_ms += 20 * 60000; + if (trend_pixels(latest) == online || trend_pixels(latest, false) != curve) + return 1; + // Week-only publications leave the short-window chart on dated history. + update.short_window = {}; + update.week_window = {true, 30, false, 0}; + latest.model.apply(update, latest.now_ms); + const std::string week_only = trend_pixels(latest); + update.week_window.used_percent = 90; + latest.model.apply(update, latest.now_ms); + if (trend_pixels(latest) != week_only) + return 1; + latest.now_ms += 10 * 60000; + latest.trend[47].epoch += 1800; + latest.trend[47].claude = 75; + if (trend_pixels(latest, false) == curve) + return 1; + puts("stored trend snapshot, offline age and half-hour update checks passed"); + return 0; +} diff --git a/tools/read_pico/preview/run.sh b/tools/read_pico/preview/run.sh new file mode 100755 index 0000000..22152c8 --- /dev/null +++ b/tools/read_pico/preview/run.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Host-renders the Read Pico views to PNG files (clang or gcc, Python 3). +# Usage: tools/read_pico/preview/run.sh [output directory] +set -euo pipefail +cd "$(dirname "$0")/../../.." +P=tools/read_pico/preview +M=firmware/targets/mindreset_read_pico/main +C=firmware/components +OUT=${1:-firmware/targets/mindreset_read_pico/build/preview} +mkdir -p "$OUT" +c++ -std=c++17 -O1 -w -I$P/stubs -I$M -I$C/usage_core/include -I$C/usage_ble/include \ + -I$C/usage_ota/include -I$C/usage_protocol/include \ + $P/preview.cpp $M/display_ui.cpp $M/demo_view.cpp $M/fonts.cpp \ + $C/usage_core/src/usage_state.cpp $C/usage_ota/src/presentation.cpp -o "$OUT/preview" +"$OUT/preview" "$OUT" +python3 - "$OUT" <<'PY' +import struct, sys, zlib +from pathlib import Path +for pgm in sorted(Path(sys.argv[1]).glob('board*.pgm')): + _, wh, _, px = pgm.read_bytes().split(b'\n', 3) + w, h = map(int, wh.split()) + raw = b''.join(b'\0' + px[y * w:(y + 1) * w] for y in range(h)) + chunk = lambda t, b: struct.pack('>I', len(b)) + t + b + struct.pack('>I', zlib.crc32(t + b)) + pgm.with_suffix('.png').write_bytes(b'\x89PNG\r\n\x1a\n' + + chunk(b'IHDR', struct.pack('>IIBBBBB', w, h, 8, 0, 0, 0, 0)) + + chunk(b'IDAT', zlib.compress(raw, 9)) + chunk(b'IEND', b'')) + pgm.unlink() + print(pgm.with_suffix('.png')) +PY diff --git a/tools/read_pico/preview/stubs/epd_highlevel.h b/tools/read_pico/preview/stubs/epd_highlevel.h new file mode 100644 index 0000000..6fa77cc --- /dev/null +++ b/tools/read_pico/preview/stubs/epd_highlevel.h @@ -0,0 +1 @@ +#include "epdiy.h" diff --git a/tools/read_pico/preview/stubs/epdiy.h b/tools/read_pico/preview/stubs/epdiy.h new file mode 100644 index 0000000..5d3f063 --- /dev/null +++ b/tools/read_pico/preview/stubs/epdiy.h @@ -0,0 +1,27 @@ +// Host stand-ins for the epdiy calls made by display_ui.cpp. +#pragma once +#include +#include +#ifdef __cplusplus +extern "C" { +#endif +enum EpdRotation { EPD_ROT_LANDSCAPE, EPD_ROT_PORTRAIT, EPD_ROT_INVERTED_LANDSCAPE, EPD_ROT_INVERTED_PORTRAIT }; +enum EpdDrawMode { MODE_DU = 1, MODE_GC16 = 2, MODE_GL16 = 5 }; +enum EpdDrawError { EPD_DRAW_SUCCESS = 0, EPD_DRAW_EMPTY_LINE_QUEUE = 0x400 }; +typedef struct { uint8_t *front_fb, *back_fb; } EpdiyHighlevelState; +int epd_width(void); +int epd_height(void); +void epd_set_rotation(enum EpdRotation rotation); +void epd_draw_pixel(int x, int y, uint8_t color, uint8_t *framebuffer); +void epd_poweron(void); +void epd_poweroff(void); +void epd_clear(void); +void epd_lcd_set_prefill_lines(int lines); +uint8_t *epd_hl_get_framebuffer(EpdiyHighlevelState *state); +void epd_hl_set_all_white(EpdiyHighlevelState *state); +enum EpdDrawError epd_hl_update_screen(EpdiyHighlevelState *state, enum EpdDrawMode mode, int temperature); +enum EpdDrawError epd_hl_update_screen_full(EpdiyHighlevelState *state, enum EpdDrawMode mode, int temperature); +enum EpdDrawError epd_hl_update_screen_from_white(EpdiyHighlevelState *state, enum EpdDrawMode mode, int temperature); +#ifdef __cplusplus +} +#endif diff --git a/tools/read_pico/preview/stubs/esp_desktop_buddy/esp_desktop_buddy.h b/tools/read_pico/preview/stubs/esp_desktop_buddy/esp_desktop_buddy.h new file mode 100644 index 0000000..772436d --- /dev/null +++ b/tools/read_pico/preview/stubs/esp_desktop_buddy/esp_desktop_buddy.h @@ -0,0 +1,2 @@ +#pragma once +typedef struct esp_desktop_buddy_command_view esp_desktop_buddy_command_view_t; diff --git a/tools/read_pico/preview/stubs/esp_log.h b/tools/read_pico/preview/stubs/esp_log.h new file mode 100644 index 0000000..ea1245a --- /dev/null +++ b/tools/read_pico/preview/stubs/esp_log.h @@ -0,0 +1,4 @@ +#pragma once +#define ESP_LOGI(tag, ...) ((void)0) +#define ESP_LOGW(tag, ...) ((void)0) +#define ESP_LOGE(tag, ...) ((void)0) diff --git a/tools/read_pico/preview/stubs/esp_ota_ops.h b/tools/read_pico/preview/stubs/esp_ota_ops.h new file mode 100644 index 0000000..b6e1701 --- /dev/null +++ b/tools/read_pico/preview/stubs/esp_ota_ops.h @@ -0,0 +1,4 @@ +#pragma once +#include +typedef struct esp_partition esp_partition_t; +typedef uint32_t esp_ota_handle_t; diff --git a/tools/read_pico/preview/stubs/esp_timer.h b/tools/read_pico/preview/stubs/esp_timer.h new file mode 100644 index 0000000..b7aa873 --- /dev/null +++ b/tools/read_pico/preview/stubs/esp_timer.h @@ -0,0 +1,3 @@ +#pragma once +#include +static inline int64_t esp_timer_get_time(void) { return 0; } diff --git a/tools/read_pico/preview/stubs/freertos/FreeRTOS.h b/tools/read_pico/preview/stubs/freertos/FreeRTOS.h new file mode 100644 index 0000000..c4a6f29 --- /dev/null +++ b/tools/read_pico/preview/stubs/freertos/FreeRTOS.h @@ -0,0 +1,4 @@ +#pragma once +#include +typedef uint32_t TickType_t; +typedef void *QueueHandle_t; diff --git a/tools/read_pico/preview/stubs/freertos/queue.h b/tools/read_pico/preview/stubs/freertos/queue.h new file mode 100644 index 0000000..6f70f09 --- /dev/null +++ b/tools/read_pico/preview/stubs/freertos/queue.h @@ -0,0 +1 @@ +#pragma once diff --git a/tools/read_pico/preview/stubs/psa/crypto.h b/tools/read_pico/preview/stubs/psa/crypto.h new file mode 100644 index 0000000..fdb9a56 --- /dev/null +++ b/tools/read_pico/preview/stubs/psa/crypto.h @@ -0,0 +1,2 @@ +#pragma once +typedef struct { int unused; } psa_hash_operation_t; diff --git a/tools/read_pico/preview/stubs/read_pico_board.h b/tools/read_pico/preview/stubs/read_pico_board.h new file mode 100644 index 0000000..dfe9e5e --- /dev/null +++ b/tools/read_pico/preview/stubs/read_pico_board.h @@ -0,0 +1,2 @@ +#pragma once +static inline bool read_pico_rails_on() { return true; } diff --git a/tools/read_pico/preview/stubs/read_pico_epd_timing.h b/tools/read_pico/preview/stubs/read_pico_epd_timing.h new file mode 100644 index 0000000..9de38ab --- /dev/null +++ b/tools/read_pico/preview/stubs/read_pico_epd_timing.h @@ -0,0 +1,5 @@ +#pragma once +#define READ_PICO_EPD_PCLK_MIN_MHZ 12 +#define READ_PICO_EPD_SCAN_FULL 0 +static inline void read_pico_epd_use_scan(int) {} +static inline void read_pico_epd_set_pclk(int) {} diff --git a/tools/ws397/generate_fonts.py b/tools/ws397/generate_fonts.py index fce74f7..2293520 100644 --- a/tools/ws397/generate_fonts.py +++ b/tools/ws397/generate_fonts.py @@ -2,9 +2,13 @@ from pathlib import Path import argparse from PIL import Image, ImageDraw, ImageFont -p=argparse.ArgumentParser(); p.add_argument('font_dir',type=Path); p.add_argument('output',type=Path); a=p.parse_args() +p=argparse.ArgumentParser(); p.add_argument('font_dir',type=Path); p.add_argument('output',type=Path); p.add_argument('--target',choices=['ws397','read_pico'],default='ws397'); a=p.parse_args() chars=''.join(chr(i) for i in range(32,127)) -sets=[(0,n,chars if n<=56 else '0123456789%:-. ') for n in [24,28,30,34,38,42,50,56,104,120,136,184]]+[(1,n,chars) for n in [15,16,17,20]] +# Pixel sizes used by each target's layout, per family: Archivo 700, JetBrains Mono 500, JetBrains Mono 700. +weights=[700,500,700] +sizes={'ws397':([24,28,30,34,38,42,50,56,104,120,136,184],[15,16,17,20],[]), + 'read_pico':([16,18,24,26,28,30,34,36,40,46,50,52,56,58,60,64,72,96,120,124,132,144,212,236],[18],[16,18,19,20,21,22,24,26,28,30])}[a.target] +sets=[(family,n,chars if family or n<=56 else '0123456789%:-. ') for family in range(3) for n in sizes[family]] header='''#pragma once #include struct Glyph { uint32_t offset; int16_t width, height, left, top, advance; uint8_t character; }; @@ -16,7 +20,7 @@ out=['#include "fonts.hpp"']; names=[] for family,size,subset in sets: f=ImageFont.truetype(str(a.font_dir/('Archivo.ttf' if family==0 else 'JetBrainsMono.ttf')),size) - axes=f.get_variation_axes(); f.set_variation_by_axes([700 if x['name']==b'Weight' and family==0 else 500 if x['name']==b'Weight' else x['default'] for x in axes]) + axes=f.get_variation_axes(); f.set_variation_by_axes([weights[family] if x['name']==b'Weight' else x['default'] for x in axes]) # Inlining this in the glyph f-string below would nest single quotes, which requires Python 3.12. baseline=f.getbbox('0',anchor='la')[1] data=bytearray(); glyphs=[] diff --git a/web/src/components/HomePage.astro b/web/src/components/HomePage.astro index 6d04464..1c39d00 100644 --- a/web/src/components/HomePage.astro +++ b/web/src/components/HomePage.astro @@ -558,7 +558,7 @@ const assetRoot = locale === "en" ? "../" : "./"; >{t("反馈问题")} - +