From 4f3a4d795cc59d41b4b63802a75755e05564c39c Mon Sep 17 00:00:00 2001 From: wulu Date: Tue, 6 Oct 2026 10:15:19 +0800 Subject: [PATCH 1/7] feat(read-pico): add MindReset Read Pico support --- THIRD_PARTY_LICENSES.md | 10 + .../mindreset_read_pico/CMakeLists.txt | 12 + .../targets/mindreset_read_pico/README.md | 114 ++ .../components/UPSTREAM.md | 29 + .../components/cst836u/CMakeLists.txt | 6 + .../components/cst836u/LICENSE | 17 + .../components/cst836u/README.md | 108 ++ .../components/cst836u/cst836u.c | 303 +++++ .../examples/touch_read/CMakeLists.txt | 3 + .../examples/touch_read/main/CMakeLists.txt | 1 + .../touch_read/main/Kconfig.projbuild | 25 + .../touch_read/main/idf_component.yml | 4 + .../cst836u/examples/touch_read/main/main.c | 73 ++ .../examples/touch_read/sdkconfig.defaults | 1 + .../components/cst836u/idf_component.yml | 13 + .../components/cst836u/include/cst836u.h | 113 ++ .../e0470_epaper_waveform/CMakeLists.txt | 6 + .../components/e0470_epaper_waveform/LICENSE | 17 + .../e0470_epaper_waveform/README.md | 58 + .../e0470_epaper_waveform.c | 253 +++++ .../e0470_waveform_trim.c | 110 ++ .../e0470_epaper_waveform/idf_component.yml | 13 + .../include/e0470_epaper_waveform.h | 101 ++ .../include/e0470_waveform_trim.h | 58 + .../e0470_epaper_waveform/waveforms/du.h | 41 + .../e0470_epaper_waveform/waveforms/gc16.h | 68 ++ .../e0470_epaper_waveform/waveforms/gl16.h | 69 ++ .../waveforms/gray8_gc16.h | 51 + .../waveforms/gray8_gl16.h | 51 + .../components/epdiy/CMakeLists.txt | 45 + .../components/epdiy/LICENSE | 24 + .../components/epdiy/README.md | 39 + .../components/epdiy/idf_component.yml | 4 + .../components/epdiy/include/epd_lcd.h | 42 + .../components/epdiy/include/epd_waveform.h | 27 + .../components/epdiy/src/board/epd_board.c | 120 ++ .../components/epdiy/src/diff.S | 159 +++ .../components/epdiy/src/displays.c | 13 + .../components/epdiy/src/epd_board.h | 122 ++ .../components/epdiy/src/epd_display.h | 22 + .../components/epdiy/src/epd_highlevel.h | 177 +++ .../components/epdiy/src/epd_internals.h | 53 + .../components/epdiy/src/epdiy.c | 543 +++++++++ .../components/epdiy/src/epdiy.h | 638 +++++++++++ .../components/epdiy/src/font.c | 434 +++++++ .../components/epdiy/src/hacks.cmake | 67 ++ .../components/epdiy/src/highlevel.c | 326 ++++++ .../epdiy/src/output_common/line_queue.c | 86 ++ .../epdiy/src/output_common/line_queue.h | 40 + .../components/epdiy/src/output_common/lut.S | 145 +++ .../components/epdiy/src/output_common/lut.c | 474 ++++++++ .../components/epdiy/src/output_common/lut.h | 46 + .../epdiy/src/output_common/render_context.c | 110 ++ .../epdiy/src/output_common/render_context.h | 107 ++ .../epdiy/src/output_common/render_method.h | 15 + .../epdiy/src/output_common/rmt_compat.c | 227 ++++ .../epdiy/src/output_common/rmt_compat.h | 75 ++ .../epdiy/src/output_lcd/idf-4-backports.h | 22 + .../epdiy/src/output_lcd/lcd_driver.c | 853 ++++++++++++++ .../epdiy/src/output_lcd/lcd_driver.h | 13 + .../epdiy/src/output_lcd/render_lcd.c | 282 +++++ .../epdiy/src/output_lcd/render_lcd.h | 19 + .../components/epdiy/src/render.c | 636 +++++++++++ .../components/epdiy/src/render.h | 12 + .../components/fca9555/CMakeLists.txt | 5 + .../components/fca9555/Kconfig | 12 + .../components/fca9555/LICENSE | 17 + .../components/fca9555/README.md | 94 ++ .../examples/read_ports/CMakeLists.txt | 3 + .../examples/read_ports/main/CMakeLists.txt | 1 + .../read_ports/main/Kconfig.projbuild | 25 + .../read_ports/main/idf_component.yml | 4 + .../fca9555/examples/read_ports/main/main.c | 66 ++ .../examples/read_ports/sdkconfig.defaults | 1 + .../components/fca9555/fca9555.c | 206 ++++ .../components/fca9555/idf_component.yml | 14 + .../components/fca9555/include/fca9555.h | 133 +++ .../components/pwm_audio/CMakeLists.txt | 6 + .../components/pwm_audio/LICENSE | 20 + .../components/pwm_audio/README.md | 30 + .../components/pwm_audio/include/pwm_audio.h | 75 ++ .../components/pwm_audio/pwm_audio.c | 827 ++++++++++++++ .../components/read_pico/CMakeLists.txt | 12 + .../components/read_pico/Kconfig | 33 + .../components/read_pico/LICENSE | 17 + .../components/read_pico/README.md | 41 + .../components/read_pico/buzzer_1bit.c | 130 +++ .../components/read_pico/buzzer_duty_map.c | 185 +++ .../components/read_pico/buzzer_score.c | 830 ++++++++++++++ .../components/read_pico/idf_component.yml | 12 + .../read_pico/include/buzzer_1bit.h | 51 + .../read_pico/include/buzzer_duty_map.h | 60 + .../read_pico/include/buzzer_score.h | 106 ++ .../components/read_pico/include/read_pico.h | 16 + .../read_pico/include/read_pico_board.h | 110 ++ .../read_pico/include/read_pico_buzzer.h | 83 ++ .../read_pico/include/read_pico_epd_timing.h | 131 +++ .../read_pico/include/read_pico_init.h | 58 + .../read_pico/include/read_pico_sd.h | 47 + .../read_pico/read_pico_blockchain.ots | Bin 0 -> 840 bytes .../components/read_pico/read_pico_board.c | 696 ++++++++++++ .../components/read_pico/read_pico_buzzer.c | 862 ++++++++++++++ .../read_pico/read_pico_epd_timing.c | 177 +++ .../read_pico/read_pico_flash_hpm.c | 101 ++ .../components/read_pico/read_pico_init.c | 161 +++ .../components/read_pico/read_pico_sd.c | 286 +++++ .../components/read_pico_pmu/CMakeLists.txt | 6 + .../components/read_pico_pmu/LICENSE | 17 + .../components/read_pico_pmu/README.md | 54 + .../read_pico_pmu/idf_component.yml | 14 + .../read_pico_pmu/include/read_pico_pmu.h | 196 ++++ .../include/read_pico_pmu_protocol.h | 280 +++++ .../components/read_pico_pmu/read_pico_pmu.c | 830 ++++++++++++++ .../components/sc7a20h/CMakeLists.txt | 6 + .../components/sc7a20h/LICENSE | 17 + .../components/sc7a20h/README.md | 112 ++ .../examples/pickup_wake/CMakeLists.txt | 3 + .../examples/pickup_wake/main/CMakeLists.txt | 1 + .../pickup_wake/main/Kconfig.projbuild | 20 + .../pickup_wake/main/idf_component.yml | 4 + .../sc7a20h/examples/pickup_wake/main/main.c | 62 + .../examples/pickup_wake/sdkconfig.defaults | 1 + .../components/sc7a20h/idf_component.yml | 14 + .../components/sc7a20h/include/sc7a20h.h | 197 ++++ .../components/sc7a20h/include/sc7a20h_lab.h | 194 ++++ .../components/sc7a20h/sc7a20h.c | 1012 +++++++++++++++++ .../components/sy7636a/CMakeLists.txt | 6 + .../components/sy7636a/LICENSE | 17 + .../components/sy7636a/README.md | 50 + .../examples/read_status/CMakeLists.txt | 3 + .../examples/read_status/main/CMakeLists.txt | 1 + .../read_status/main/idf_component.yml | 4 + .../sy7636a/examples/read_status/main/main.c | 62 + .../components/sy7636a/idf_component.yml | 14 + .../components/sy7636a/include/sy7636a.h | 176 +++ .../components/sy7636a/sy7636a.c | 491 ++++++++ .../mindreset_read_pico/dependencies.lock | 44 + .../mindreset_read_pico/main/CMakeLists.txt | 7 + .../mindreset_read_pico/main/app_main.cpp | 294 +++++ .../mindreset_read_pico/main/board.cpp | 279 +++++ .../mindreset_read_pico/main/board.hpp | 38 + .../mindreset_read_pico/main/demo_view.cpp | 61 + .../mindreset_read_pico/main/demo_view.hpp | 10 + .../mindreset_read_pico/main/display_ui.cpp | 892 +++++++++++++++ .../mindreset_read_pico/main/display_ui.hpp | 110 ++ .../mindreset_read_pico/main/fonts.cpp | 73 ++ .../mindreset_read_pico/main/fonts.hpp | 6 + .../main/fonts/Archivo-OFL.txt | 93 ++ .../main/fonts/JetBrainsMono-OFL.txt | 93 ++ .../mindreset_read_pico/partitions.csv | 6 + .../mindreset_read_pico/sdkconfig.defaults | 50 + tools/read_pico/preview/preview.cpp | 260 +++++ tools/read_pico/preview/run.sh | 29 + tools/read_pico/preview/stubs/epd_highlevel.h | 1 + tools/read_pico/preview/stubs/epdiy.h | 27 + .../esp_desktop_buddy/esp_desktop_buddy.h | 2 + tools/read_pico/preview/stubs/esp_log.h | 4 + tools/read_pico/preview/stubs/esp_ota_ops.h | 4 + tools/read_pico/preview/stubs/esp_timer.h | 3 + .../preview/stubs/freertos/FreeRTOS.h | 4 + .../read_pico/preview/stubs/freertos/queue.h | 1 + tools/read_pico/preview/stubs/psa/crypto.h | 2 + .../preview/stubs/read_pico_epd_timing.h | 5 + tools/ws397/generate_fonts.py | 10 +- 164 files changed, 19758 insertions(+), 3 deletions(-) create mode 100644 firmware/targets/mindreset_read_pico/CMakeLists.txt create mode 100644 firmware/targets/mindreset_read_pico/README.md create mode 100644 firmware/targets/mindreset_read_pico/components/UPSTREAM.md create mode 100644 firmware/targets/mindreset_read_pico/components/cst836u/CMakeLists.txt create mode 100644 firmware/targets/mindreset_read_pico/components/cst836u/LICENSE create mode 100644 firmware/targets/mindreset_read_pico/components/cst836u/README.md create mode 100644 firmware/targets/mindreset_read_pico/components/cst836u/cst836u.c create mode 100644 firmware/targets/mindreset_read_pico/components/cst836u/examples/touch_read/CMakeLists.txt create mode 100644 firmware/targets/mindreset_read_pico/components/cst836u/examples/touch_read/main/CMakeLists.txt create mode 100644 firmware/targets/mindreset_read_pico/components/cst836u/examples/touch_read/main/Kconfig.projbuild create mode 100644 firmware/targets/mindreset_read_pico/components/cst836u/examples/touch_read/main/idf_component.yml create mode 100644 firmware/targets/mindreset_read_pico/components/cst836u/examples/touch_read/main/main.c create mode 100644 firmware/targets/mindreset_read_pico/components/cst836u/examples/touch_read/sdkconfig.defaults create mode 100644 firmware/targets/mindreset_read_pico/components/cst836u/idf_component.yml create mode 100644 firmware/targets/mindreset_read_pico/components/cst836u/include/cst836u.h create mode 100644 firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/CMakeLists.txt create mode 100644 firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/LICENSE create mode 100644 firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/README.md create mode 100644 firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/e0470_epaper_waveform.c create mode 100644 firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/e0470_waveform_trim.c create mode 100644 firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/idf_component.yml create mode 100644 firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/include/e0470_epaper_waveform.h create mode 100644 firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/include/e0470_waveform_trim.h create mode 100644 firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/waveforms/du.h create mode 100644 firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/waveforms/gc16.h create mode 100644 firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/waveforms/gl16.h create mode 100644 firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/waveforms/gray8_gc16.h create mode 100644 firmware/targets/mindreset_read_pico/components/e0470_epaper_waveform/waveforms/gray8_gl16.h create mode 100755 firmware/targets/mindreset_read_pico/components/epdiy/CMakeLists.txt create mode 100644 firmware/targets/mindreset_read_pico/components/epdiy/LICENSE create mode 100644 firmware/targets/mindreset_read_pico/components/epdiy/README.md create mode 100755 firmware/targets/mindreset_read_pico/components/epdiy/idf_component.yml create mode 100644 firmware/targets/mindreset_read_pico/components/epdiy/include/epd_lcd.h create mode 100644 firmware/targets/mindreset_read_pico/components/epdiy/include/epd_waveform.h create mode 100755 firmware/targets/mindreset_read_pico/components/epdiy/src/board/epd_board.c create mode 100755 firmware/targets/mindreset_read_pico/components/epdiy/src/diff.S create mode 100755 firmware/targets/mindreset_read_pico/components/epdiy/src/displays.c create mode 100755 firmware/targets/mindreset_read_pico/components/epdiy/src/epd_board.h create mode 100755 firmware/targets/mindreset_read_pico/components/epdiy/src/epd_display.h create mode 100755 firmware/targets/mindreset_read_pico/components/epdiy/src/epd_highlevel.h create mode 100755 firmware/targets/mindreset_read_pico/components/epdiy/src/epd_internals.h create mode 100755 firmware/targets/mindreset_read_pico/components/epdiy/src/epdiy.c create mode 100755 firmware/targets/mindreset_read_pico/components/epdiy/src/epdiy.h create mode 100755 firmware/targets/mindreset_read_pico/components/epdiy/src/font.c create mode 100755 firmware/targets/mindreset_read_pico/components/epdiy/src/hacks.cmake create mode 100755 firmware/targets/mindreset_read_pico/components/epdiy/src/highlevel.c create mode 100755 firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/line_queue.c create mode 100755 firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/line_queue.h create mode 100755 firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/lut.S create mode 100755 firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/lut.c create mode 100755 firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/lut.h create mode 100755 firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/render_context.c create mode 100755 firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/render_context.h create mode 100755 firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/render_method.h create mode 100644 firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/rmt_compat.c create mode 100644 firmware/targets/mindreset_read_pico/components/epdiy/src/output_common/rmt_compat.h create mode 100755 firmware/targets/mindreset_read_pico/components/epdiy/src/output_lcd/idf-4-backports.h create mode 100755 firmware/targets/mindreset_read_pico/components/epdiy/src/output_lcd/lcd_driver.c create mode 100755 firmware/targets/mindreset_read_pico/components/epdiy/src/output_lcd/lcd_driver.h create mode 100755 firmware/targets/mindreset_read_pico/components/epdiy/src/output_lcd/render_lcd.c create mode 100755 firmware/targets/mindreset_read_pico/components/epdiy/src/output_lcd/render_lcd.h create mode 100755 firmware/targets/mindreset_read_pico/components/epdiy/src/render.c create mode 100755 firmware/targets/mindreset_read_pico/components/epdiy/src/render.h create mode 100644 firmware/targets/mindreset_read_pico/components/fca9555/CMakeLists.txt create mode 100644 firmware/targets/mindreset_read_pico/components/fca9555/Kconfig create mode 100644 firmware/targets/mindreset_read_pico/components/fca9555/LICENSE create mode 100644 firmware/targets/mindreset_read_pico/components/fca9555/README.md create mode 100644 firmware/targets/mindreset_read_pico/components/fca9555/examples/read_ports/CMakeLists.txt create mode 100644 firmware/targets/mindreset_read_pico/components/fca9555/examples/read_ports/main/CMakeLists.txt create mode 100644 firmware/targets/mindreset_read_pico/components/fca9555/examples/read_ports/main/Kconfig.projbuild create mode 100644 firmware/targets/mindreset_read_pico/components/fca9555/examples/read_ports/main/idf_component.yml create mode 100644 firmware/targets/mindreset_read_pico/components/fca9555/examples/read_ports/main/main.c create mode 100644 firmware/targets/mindreset_read_pico/components/fca9555/examples/read_ports/sdkconfig.defaults create mode 100644 firmware/targets/mindreset_read_pico/components/fca9555/fca9555.c create mode 100644 firmware/targets/mindreset_read_pico/components/fca9555/idf_component.yml create mode 100644 firmware/targets/mindreset_read_pico/components/fca9555/include/fca9555.h create mode 100644 firmware/targets/mindreset_read_pico/components/pwm_audio/CMakeLists.txt create mode 100644 firmware/targets/mindreset_read_pico/components/pwm_audio/LICENSE create mode 100644 firmware/targets/mindreset_read_pico/components/pwm_audio/README.md create mode 100644 firmware/targets/mindreset_read_pico/components/pwm_audio/include/pwm_audio.h create mode 100644 firmware/targets/mindreset_read_pico/components/pwm_audio/pwm_audio.c create mode 100644 firmware/targets/mindreset_read_pico/components/read_pico/CMakeLists.txt create mode 100644 firmware/targets/mindreset_read_pico/components/read_pico/Kconfig create mode 100644 firmware/targets/mindreset_read_pico/components/read_pico/LICENSE create mode 100644 firmware/targets/mindreset_read_pico/components/read_pico/README.md create mode 100644 firmware/targets/mindreset_read_pico/components/read_pico/buzzer_1bit.c create mode 100644 firmware/targets/mindreset_read_pico/components/read_pico/buzzer_duty_map.c create mode 100644 firmware/targets/mindreset_read_pico/components/read_pico/buzzer_score.c create mode 100644 firmware/targets/mindreset_read_pico/components/read_pico/idf_component.yml create mode 100644 firmware/targets/mindreset_read_pico/components/read_pico/include/buzzer_1bit.h create mode 100644 firmware/targets/mindreset_read_pico/components/read_pico/include/buzzer_duty_map.h create mode 100644 firmware/targets/mindreset_read_pico/components/read_pico/include/buzzer_score.h create mode 100644 firmware/targets/mindreset_read_pico/components/read_pico/include/read_pico.h create mode 100644 firmware/targets/mindreset_read_pico/components/read_pico/include/read_pico_board.h create mode 100644 firmware/targets/mindreset_read_pico/components/read_pico/include/read_pico_buzzer.h create mode 100644 firmware/targets/mindreset_read_pico/components/read_pico/include/read_pico_epd_timing.h create mode 100644 firmware/targets/mindreset_read_pico/components/read_pico/include/read_pico_init.h create mode 100644 firmware/targets/mindreset_read_pico/components/read_pico/include/read_pico_sd.h create mode 100644 firmware/targets/mindreset_read_pico/components/read_pico/read_pico_blockchain.ots create mode 100644 firmware/targets/mindreset_read_pico/components/read_pico/read_pico_board.c create mode 100644 firmware/targets/mindreset_read_pico/components/read_pico/read_pico_buzzer.c create mode 100644 firmware/targets/mindreset_read_pico/components/read_pico/read_pico_epd_timing.c create mode 100644 firmware/targets/mindreset_read_pico/components/read_pico/read_pico_flash_hpm.c create mode 100644 firmware/targets/mindreset_read_pico/components/read_pico/read_pico_init.c create mode 100644 firmware/targets/mindreset_read_pico/components/read_pico/read_pico_sd.c create mode 100644 firmware/targets/mindreset_read_pico/components/read_pico_pmu/CMakeLists.txt create mode 100644 firmware/targets/mindreset_read_pico/components/read_pico_pmu/LICENSE create mode 100644 firmware/targets/mindreset_read_pico/components/read_pico_pmu/README.md create mode 100644 firmware/targets/mindreset_read_pico/components/read_pico_pmu/idf_component.yml create mode 100644 firmware/targets/mindreset_read_pico/components/read_pico_pmu/include/read_pico_pmu.h create mode 100644 firmware/targets/mindreset_read_pico/components/read_pico_pmu/include/read_pico_pmu_protocol.h create mode 100644 firmware/targets/mindreset_read_pico/components/read_pico_pmu/read_pico_pmu.c create mode 100644 firmware/targets/mindreset_read_pico/components/sc7a20h/CMakeLists.txt create mode 100644 firmware/targets/mindreset_read_pico/components/sc7a20h/LICENSE create mode 100644 firmware/targets/mindreset_read_pico/components/sc7a20h/README.md create mode 100644 firmware/targets/mindreset_read_pico/components/sc7a20h/examples/pickup_wake/CMakeLists.txt create mode 100644 firmware/targets/mindreset_read_pico/components/sc7a20h/examples/pickup_wake/main/CMakeLists.txt create mode 100644 firmware/targets/mindreset_read_pico/components/sc7a20h/examples/pickup_wake/main/Kconfig.projbuild create mode 100644 firmware/targets/mindreset_read_pico/components/sc7a20h/examples/pickup_wake/main/idf_component.yml create mode 100644 firmware/targets/mindreset_read_pico/components/sc7a20h/examples/pickup_wake/main/main.c create mode 100644 firmware/targets/mindreset_read_pico/components/sc7a20h/examples/pickup_wake/sdkconfig.defaults create mode 100644 firmware/targets/mindreset_read_pico/components/sc7a20h/idf_component.yml create mode 100644 firmware/targets/mindreset_read_pico/components/sc7a20h/include/sc7a20h.h create mode 100644 firmware/targets/mindreset_read_pico/components/sc7a20h/include/sc7a20h_lab.h create mode 100644 firmware/targets/mindreset_read_pico/components/sc7a20h/sc7a20h.c create mode 100644 firmware/targets/mindreset_read_pico/components/sy7636a/CMakeLists.txt create mode 100644 firmware/targets/mindreset_read_pico/components/sy7636a/LICENSE create mode 100644 firmware/targets/mindreset_read_pico/components/sy7636a/README.md create mode 100644 firmware/targets/mindreset_read_pico/components/sy7636a/examples/read_status/CMakeLists.txt create mode 100644 firmware/targets/mindreset_read_pico/components/sy7636a/examples/read_status/main/CMakeLists.txt create mode 100644 firmware/targets/mindreset_read_pico/components/sy7636a/examples/read_status/main/idf_component.yml create mode 100644 firmware/targets/mindreset_read_pico/components/sy7636a/examples/read_status/main/main.c create mode 100644 firmware/targets/mindreset_read_pico/components/sy7636a/idf_component.yml create mode 100644 firmware/targets/mindreset_read_pico/components/sy7636a/include/sy7636a.h create mode 100644 firmware/targets/mindreset_read_pico/components/sy7636a/sy7636a.c create mode 100644 firmware/targets/mindreset_read_pico/dependencies.lock create mode 100644 firmware/targets/mindreset_read_pico/main/CMakeLists.txt create mode 100644 firmware/targets/mindreset_read_pico/main/app_main.cpp create mode 100644 firmware/targets/mindreset_read_pico/main/board.cpp create mode 100644 firmware/targets/mindreset_read_pico/main/board.hpp create mode 100644 firmware/targets/mindreset_read_pico/main/demo_view.cpp create mode 100644 firmware/targets/mindreset_read_pico/main/demo_view.hpp create mode 100644 firmware/targets/mindreset_read_pico/main/display_ui.cpp create mode 100644 firmware/targets/mindreset_read_pico/main/display_ui.hpp create mode 100644 firmware/targets/mindreset_read_pico/main/fonts.cpp create mode 100644 firmware/targets/mindreset_read_pico/main/fonts.hpp create mode 100644 firmware/targets/mindreset_read_pico/main/fonts/Archivo-OFL.txt create mode 100644 firmware/targets/mindreset_read_pico/main/fonts/JetBrainsMono-OFL.txt create mode 100644 firmware/targets/mindreset_read_pico/partitions.csv create mode 100644 firmware/targets/mindreset_read_pico/sdkconfig.defaults create mode 100644 tools/read_pico/preview/preview.cpp create mode 100755 tools/read_pico/preview/run.sh create mode 100644 tools/read_pico/preview/stubs/epd_highlevel.h create mode 100644 tools/read_pico/preview/stubs/epdiy.h create mode 100644 tools/read_pico/preview/stubs/esp_desktop_buddy/esp_desktop_buddy.h create mode 100644 tools/read_pico/preview/stubs/esp_log.h create mode 100644 tools/read_pico/preview/stubs/esp_ota_ops.h create mode 100644 tools/read_pico/preview/stubs/esp_timer.h create mode 100644 tools/read_pico/preview/stubs/freertos/FreeRTOS.h create mode 100644 tools/read_pico/preview/stubs/freertos/queue.h create mode 100644 tools/read_pico/preview/stubs/psa/crypto.h create mode 100644 tools/read_pico/preview/stubs/read_pico_epd_timing.h diff --git a/THIRD_PARTY_LICENSES.md b/THIRD_PARTY_LICENSES.md index 0d7ff72..8092ba2 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 28–56 px, digits and `%:-. ` at 58–230 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: weight 500; printable ASCII at 16–26 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/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..480e57e --- /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. +The target is not listed in the Target Registry, so CI, releases and the web +flasher do not build it. + +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 layout follows the design boards 01-08: 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. 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 of the +design boards: 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 0000000000000000000000000000000000000000..efdd0ff92b5864e7038ae48d30abb438c0ad2460 GIT binary patch literal 840 zcmZSZFG$S`$;?eHE=kNSC}v;?D9X=IW7yyM=tawmNmCd(gceuw-8{X0lh3h3-XC{a zsBbe8(Gggjo-0;SHMi%NjME2!>dk78X3w7at?tmm)hEgypXT`gfy1yH=nBA@qWzdFU_f}oK_~Y*NWzwm$+lnI;?(X`P{+7+QApoev)vKqTui)|9u+$?C zHXJO4rgFD$lGYuaVH5*Q`= z{>{n$O!*v1HoN*lRoXoC--_P&<|v|j=F9b%H>-Y611e$7xtN%Oy$FjZESU){ladH +#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..f504797 --- /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 design boards, 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..0fdb176 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/main/board.cpp @@ -0,0 +1,279 @@ +#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{}; + v.sd = read_pico_sd_get_info(&sd) == ESP_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; + static bool loaded = false; + if (!loaded) { + loaded = true; + 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..c36c9e3 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/main/board.hpp @@ -0,0 +1,38 @@ +#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; + 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; } + void poll(View &view); + /** Sets the system clock and the PMU RTC, and stores the UTC offset. */ + bool sync(const LocalCalendarTime &time); + 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_; } + bool save(const Settings &settings); + Settings load(); + 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; + 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..9a7dd42 --- /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 a synthetic view of one design board 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..75eb311 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/main/display_ui.cpp @@ -0,0 +1,892 @@ +#include "display_ui.hpp" +#include "epdiy.h" +#include "esp_log.h" +#include "esp_timer.h" +#include "fonts.hpp" +#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 sit lower than their on-screen design values. +// 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(); + 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..cefe953 --- /dev/null +++ b/firmware/targets/mindreset_read_pico/main/display_ui.hpp @@ -0,0 +1,110 @@ +#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 { + 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; + 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/tools/read_pico/preview/preview.cpp b/tools/read_pico/preview/preview.cpp new file mode 100644 index 0000000..4e0c2bf --- /dev/null +++ b/tools/read_pico/preview/preview.cpp @@ -0,0 +1,260 @@ +// Renders the nine design boards and the gray-level test card with the firmware drawing code, one PGM per board 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 board 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 confirmation boards 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..75983f4 --- /dev/null +++ b/tools/read_pico/preview/run.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Host-renders the Read Pico design boards 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_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=[] From 0bd902e106c8ba7b4a9bf854afac7613a6d861c2 Mon Sep 17 00:00:00 2001 From: wulu Date: Tue, 6 Oct 2026 10:15:19 +0800 Subject: [PATCH 2/7] fix(bridge): retain macOS device adoption task --- bridge/src/quotaframe_bridge/ui/macos/app.py | 12 +++++- bridge/tests/test_macos_app.py | 39 ++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) 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, *, From 145aae90d06f93821bac63d18226c00e6e847aa3 Mon Sep 17 00:00:00 2001 From: wulu Date: Tue, 6 Oct 2026 10:26:13 +0800 Subject: [PATCH 3/7] docs(read-pico): align documentation and comments List Read Pico in the firmware and protocol references, correct its font subset record, describe the diagnostic views directly, and document the board and view interfaces. Drop pending-verification remarks from the Bridge, Mosaico and ePaper notes. --- THIRD_PARTY_LICENSES.md | 4 ++-- bridge/README.md | 2 +- firmware/README.md | 4 +++- firmware/targets/esp_mosaico/README.md | 2 +- firmware/targets/mindreset_read_pico/README.md | 5 ++--- firmware/targets/mindreset_read_pico/main/app_main.cpp | 2 +- firmware/targets/mindreset_read_pico/main/board.hpp | 9 +++++++++ firmware/targets/mindreset_read_pico/main/demo_view.hpp | 2 +- firmware/targets/mindreset_read_pico/main/display_ui.cpp | 2 +- firmware/targets/mindreset_read_pico/main/display_ui.hpp | 3 +++ firmware/targets/waveshare_epaper_397/README.md | 3 +-- protocol/README.md | 1 + tools/read_pico/preview/preview.cpp | 6 +++--- tools/read_pico/preview/run.sh | 2 +- 14 files changed, 30 insertions(+), 17 deletions(-) diff --git a/THIRD_PARTY_LICENSES.md b/THIRD_PARTY_LICENSES.md index 8092ba2..f7f4867 100644 --- a/THIRD_PARTY_LICENSES.md +++ b/THIRD_PARTY_LICENSES.md @@ -242,7 +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 28–56 px, digits and `%:-. ` at 58–230 px; notice: `firmware/targets/mindreset_read_pico/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 @@ -250,7 +250,7 @@ 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: weight 500; printable ASCII at 16–26 px; notice: `firmware/targets/mindreset_read_pico/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 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/firmware/README.md b/firmware/README.md index 32fee33..db40777 100644 --- a/firmware/README.md +++ b/firmware/README.md @@ -38,10 +38,11 @@ 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)。 -维护中的硬件目标统一登记在 `bridge/src/quotaframe_bridge/targets.py`,用于 CI、Release、镜像结构和分区容量校验。Bridge 基础发现使用公共 QF 名称空间和 NUS,运行时名称来自 status,常驻会话来自 `devices.json`;第三方设备基础接入无需加入 registry。`scripts/target_registry.py` 输出官方构建与发布 matrix。 +维护中的硬件目标统一登记在 `bridge/src/quotaframe_bridge/targets.py`,用于 CI、Release、镜像结构和分区容量校验。Bridge 基础发现使用公共 QF 名称空间和 NUS,运行时名称来自 status,常驻会话来自 `devices.json`;第三方设备基础接入无需加入 registry。`scripts/target_registry.py` 输出官方构建与发布 matrix。Read Pico 未登记在 registry,CI、Release 和 Web 烧录器不构建它,需从源码构建,见[目标说明](targets/mindreset_read_pico/README.md)。 新增或移植硬件目标时,先阅读 [Hardware Target Porting Guide](../docs/PORTING.md)。其中定义了 target 目录与 Registry 契约、共享/板级边界、分区与 OTA 要求、target test app 接入方式,以及 build-only 与 hardware-verified 的证据边界;可复制的最小工程骨架位于 `docs/porting/minimal-target/`。 @@ -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/README.md b/firmware/targets/mindreset_read_pico/README.md index 480e57e..f172665 100644 --- a/firmware/targets/mindreset_read_pico/README.md +++ b/firmware/targets/mindreset_read_pico/README.md @@ -21,7 +21,7 @@ on the flash fitted to this board. ## Display -The layout follows the design boards 01-08: Home with both services, the +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 @@ -96,8 +96,7 @@ in mg together with the resolved orientation. ## USB diagnostics -Commands are accepted while OTA is idle. `1`-`9` show synthetic views of the -design boards: Home, alarms, clock, landscape Home, pairing, OTA progress, +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. diff --git a/firmware/targets/mindreset_read_pico/main/app_main.cpp b/firmware/targets/mindreset_read_pico/main/app_main.cpp index f504797..75b32bf 100644 --- a/firmware/targets/mindreset_read_pico/main/app_main.cpp +++ b/firmware/targets/mindreset_read_pico/main/app_main.cpp @@ -94,7 +94,7 @@ extern "C" void app_main() { 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 design boards, i state, r live"); + 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); diff --git a/firmware/targets/mindreset_read_pico/main/board.hpp b/firmware/targets/mindreset_read_pico/main/board.hpp index c36c9e3..2b93f2d 100644 --- a/firmware/targets/mindreset_read_pico/main/board.hpp +++ b/firmware/targets/mindreset_read_pico/main/board.hpp @@ -8,6 +8,7 @@ struct Input { // 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 { @@ -15,17 +16,25 @@ class Board { /** 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 from the SD card on the first call with a mounted card, then appends + * one sample per half hour while the link is encrypted. Does nothing without a card. + */ void sample_trend(View &view); private: diff --git a/firmware/targets/mindreset_read_pico/main/demo_view.hpp b/firmware/targets/mindreset_read_pico/main/demo_view.hpp index 9a7dd42..5dadb1e 100644 --- a/firmware/targets/mindreset_read_pico/main/demo_view.hpp +++ b/firmware/targets/mindreset_read_pico/main/demo_view.hpp @@ -2,7 +2,7 @@ #include "display_ui.hpp" namespace usage_panel::read_pico { /** - * Builds a synthetic view of one design board for display checks without a Bridge. + * 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. */ diff --git a/firmware/targets/mindreset_read_pico/main/display_ui.cpp b/firmware/targets/mindreset_read_pico/main/display_ui.cpp index 75eb311..dfa550d 100644 --- a/firmware/targets/mindreset_read_pico/main/display_ui.cpp +++ b/firmware/targets/mindreset_read_pico/main/display_ui.cpp @@ -13,7 +13,7 @@ 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 sit lower than their on-screen design values. +// 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, diff --git a/firmware/targets/mindreset_read_pico/main/display_ui.hpp b/firmware/targets/mindreset_read_pico/main/display_ui.hpp index cefe953..f8702af 100644 --- a/firmware/targets/mindreset_read_pico/main/display_ui.hpp +++ b/firmware/targets/mindreset_read_pico/main/display_ui.hpp @@ -9,6 +9,8 @@ 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 { @@ -29,6 +31,7 @@ struct View { // 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; 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 index 4e0c2bf..46b731d 100644 --- a/tools/read_pico/preview/preview.cpp +++ b/tools/read_pico/preview/preview.cpp @@ -1,4 +1,4 @@ -// Renders the nine design boards and the gray-level test card with the firmware drawing code, one PGM per board and rotation. +// 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 @@ -71,7 +71,7 @@ int main(int argc, char **argv) { ui.begin(&state); View live{}; snprintf(live.device_name, sizeof(live.device_name), "QF-MR-PICO-3C7A"); - // Each board is rendered upright and, with the suffix "l", turned to landscape. + // 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]; @@ -93,7 +93,7 @@ int main(int argc, char **argv) { } fclose(f); } - // Every control drawn on the settings and confirmation boards must be reachable by touch. + // 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 && diff --git a/tools/read_pico/preview/run.sh b/tools/read_pico/preview/run.sh index 75983f4..22152c8 100755 --- a/tools/read_pico/preview/run.sh +++ b/tools/read_pico/preview/run.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Host-renders the Read Pico design boards to PNG files (clang or gcc, Python 3). +# 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")/../../.." From 1e7cb6291c22cf0b14bfb86d7a8f350a4ec0059f Mon Sep 17 00:00:00 2001 From: wulu Date: Tue, 6 Oct 2026 10:27:51 +0800 Subject: [PATCH 4/7] feat(read-pico): register the target for CI and releases Add MindReset Read Pico to the Target Registry without a web flasher entry, and list it in the device, firmware and release references. --- README.md | 1 + README.zh-CN.md | 1 + bridge/src/quotaframe_bridge/targets.py | 11 +++++++++++ bridge/tests/test_target_registry.py | 9 +++++++++ docs/devices.md | 3 +++ docs/release/FIRMWARE_RELEASE.md | 2 ++ firmware/README.md | 4 ++-- firmware/targets/mindreset_read_pico/README.md | 4 ++-- 8 files changed, 31 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c983170..0143c4f 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. diff --git a/README.zh-CN.md b/README.zh-CN.md index 93cbf2e..4e21252 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)。 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/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 db40777..8b8a452 100644 --- a/firmware/README.md +++ b/firmware/README.md @@ -38,11 +38,11 @@ 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 驱动、触屏与三枚电容键 | +| MindReset Read Pico | 已维护 | 自绘帧缓冲、epdiy 驱动、触屏与三枚电容键 | 各目标的实机验收范围见 [verification.md](../docs/verification.md)。 -维护中的硬件目标统一登记在 `bridge/src/quotaframe_bridge/targets.py`,用于 CI、Release、镜像结构和分区容量校验。Bridge 基础发现使用公共 QF 名称空间和 NUS,运行时名称来自 status,常驻会话来自 `devices.json`;第三方设备基础接入无需加入 registry。`scripts/target_registry.py` 输出官方构建与发布 matrix。Read Pico 未登记在 registry,CI、Release 和 Web 烧录器不构建它,需从源码构建,见[目标说明](targets/mindreset_read_pico/README.md)。 +维护中的硬件目标统一登记在 `bridge/src/quotaframe_bridge/targets.py`,用于 CI、Release、镜像结构和分区容量校验。Bridge 基础发现使用公共 QF 名称空间和 NUS,运行时名称来自 status,常驻会话来自 `devices.json`;第三方设备基础接入无需加入 registry。`scripts/target_registry.py` 输出官方构建与发布 matrix。 新增或移植硬件目标时,先阅读 [Hardware Target Porting Guide](../docs/PORTING.md)。其中定义了 target 目录与 Registry 契约、共享/板级边界、分区与 OTA 要求、target test app 接入方式,以及 build-only 与 hardware-verified 的证据边界;可复制的最小工程骨架位于 `docs/porting/minimal-target/`。 diff --git a/firmware/targets/mindreset_read_pico/README.md b/firmware/targets/mindreset_read_pico/README.md index f172665..bf550e4 100644 --- a/firmware/targets/mindreset_read_pico/README.md +++ b/firmware/targets/mindreset_read_pico/README.md @@ -11,8 +11,8 @@ 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. -The target is not listed in the Target Registry, so CI, releases and the web -flasher do not build it. +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 From a297f60e0a1f1323c43bf67c940294f4d76c8f6e Mon Sep 17 00:00:00 2001 From: wulu Date: Tue, 6 Oct 2026 10:43:28 +0800 Subject: [PATCH 5/7] docs: acknowledge Waveshare and MindReset device support --- README.md | 2 +- README.zh-CN.md | 2 +- web/src/components/HomePage.astro | 2 +- web/src/i18n/en.js | 5 +++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0143c4f..5a435f2 100644 --- a/README.md +++ b/README.md @@ -79,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 4e21252..6eab559 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -79,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/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("反馈问题")} - +