From f91769b3ffd727550011cd35b38726e10f74e9af Mon Sep 17 00:00:00 2001 From: Julio Merino Date: Sat, 9 May 2026 03:39:11 +0200 Subject: [PATCH 1/5] Turn Fonts into a proper type This allows encapsulating the constructor and the getter, which feels nicer for consistency across modules. --- cli/src/main.rs | 2 +- st7735s/src/lib.rs | 17 ++--------------- std/src/gfx/lcd/fonts/mod.rs | 35 +++++++++++++++++++++++++++-------- 3 files changed, 30 insertions(+), 24 deletions(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index afa69611..736f7d35 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -183,7 +183,7 @@ fn setup_console( endbasic_rpi::spi_bus_open, endbasic_terminal::TerminalConsole::from_stdio(signals_tx)?, spec, - &endbasic_std::gfx::lcd::fonts::all_fonts(), + &endbasic_std::gfx::lcd::fonts::Fonts::all(), )?; Ok(Rc::from(RefCell::from(console))) } diff --git a/st7735s/src/lib.rs b/st7735s/src/lib.rs index 06b19645..54c466ce 100644 --- a/st7735s/src/lib.rs +++ b/st7735s/src/lib.rs @@ -27,8 +27,7 @@ use async_channel::{Receiver, TryRecvError}; use async_trait::async_trait; use endbasic_std::console::graphics::InputOps; use endbasic_std::console::{ - CharsXY, ClearType, Console, ConsoleSpec, GraphicsConsole, Key, ParseError, PixelsXY, RGB, - SizeInPixels, + CharsXY, ClearType, Console, ConsoleSpec, GraphicsConsole, Key, PixelsXY, RGB, SizeInPixels, }; use endbasic_std::gfx::lcd::fonts::Fonts; use endbasic_std::gfx::lcd::{BufferedLcd, Lcd, LcdSize, LcdXY, RGB565Pixel, to_xy_size}; @@ -486,19 +485,7 @@ where let default_bg_color = spec.take_keyed_flag::("bg_color")?; let font_name = spec.take_keyed_flag_str("font").unwrap_or("5x8"); - let font = match fonts.get(font_name) { - Some(font) => font, - None => { - let mut valid = fonts.keys().copied().collect::>(); - valid.sort(); - return Err(ParseError(format!( - "Unknown font: {}; valid names are: {}", - font_name, - valid.join(", ") - )) - .into()); - } - }; + let font = fonts.get(font_name)?; let pins = Arc::from(Mutex::from(pins)); let lcd = ST7735SLcd::new(pins.clone(), new_spi)?; diff --git a/std/src/gfx/lcd/fonts/mod.rs b/std/src/gfx/lcd/fonts/mod.rs index 537532fb..11d3cfbb 100644 --- a/std/src/gfx/lcd/fonts/mod.rs +++ b/std/src/gfx/lcd/fonts/mod.rs @@ -17,6 +17,7 @@ use crate::gfx::lcd::LcdSize; use std::collections::HashMap; +use std::io; mod font_5x8; pub(crate) use font_5x8::FONT_5X8; @@ -58,14 +59,32 @@ impl Font { } /// Registry of all available fonts. -pub type Fonts = HashMap<&'static str, &'static Font>; - -/// Obtains a mapping of all available fonts. -pub fn all_fonts() -> Fonts { - let mut fonts = Fonts::default(); - fonts.insert(FONT_5X8.name, &FONT_5X8); - fonts.insert(FONT_16X16.name, &FONT_16X16); - fonts +pub struct Fonts(HashMap<&'static str, &'static Font>); + +impl Fonts { + /// Obtains a mapping of all available fonts. + pub fn all() -> Self { + let mut fonts = HashMap::default(); + fonts.insert(FONT_5X8.name, &FONT_5X8); + fonts.insert(FONT_16X16.name, &FONT_16X16); + Self(fonts) + } + + /// Gets a font by `name`, ensuring that it's present. + pub fn get(&self, name: &str) -> io::Result<&'static Font> { + match self.0.get(name) { + Some(font) => Ok(*font), + None => { + let mut valid = self.0.keys().copied().collect::>(); + valid.sort(); + Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!("Unknown font: {}; valid names are: {}", name, valid.join(", ")), + ) + .into()) + } + } + } } #[cfg(test)] From 3671c13a4e4e4ae601b215e5a89babe35231e620 Mon Sep 17 00:00:00 2001 From: Julio Merino Date: Sat, 9 May 2026 03:39:11 +0200 Subject: [PATCH 2/5] Expose FONT_* types as public This is to allow obtaining a specific font name without duplicating its name in other places. --- st7735s/src/lib.rs | 4 ++-- std/src/gfx/lcd/fonts/font_16x16.rs | 2 +- std/src/gfx/lcd/fonts/font_5x8.rs | 2 +- std/src/gfx/lcd/fonts/mod.rs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/st7735s/src/lib.rs b/st7735s/src/lib.rs index 54c466ce..58fe5fda 100644 --- a/st7735s/src/lib.rs +++ b/st7735s/src/lib.rs @@ -29,7 +29,7 @@ use endbasic_std::console::graphics::InputOps; use endbasic_std::console::{ CharsXY, ClearType, Console, ConsoleSpec, GraphicsConsole, Key, PixelsXY, RGB, SizeInPixels, }; -use endbasic_std::gfx::lcd::fonts::Fonts; +use endbasic_std::gfx::lcd::fonts::{FONT_5X8, Fonts}; use endbasic_std::gfx::lcd::{BufferedLcd, Lcd, LcdSize, LcdXY, RGB565Pixel, to_xy_size}; use endbasic_std::gpio::{Pin, PinMode, Pins}; use endbasic_std::spi::{SpiBus, SpiMode}; @@ -484,7 +484,7 @@ where let default_fg_color = spec.take_keyed_flag::("fg_color")?; let default_bg_color = spec.take_keyed_flag::("bg_color")?; - let font_name = spec.take_keyed_flag_str("font").unwrap_or("5x8"); + let font_name = spec.take_keyed_flag_str("font").unwrap_or(FONT_5X8.name); let font = fonts.get(font_name)?; let pins = Arc::from(Mutex::from(pins)); diff --git a/std/src/gfx/lcd/fonts/font_16x16.rs b/std/src/gfx/lcd/fonts/font_16x16.rs index 142c22fd..0a5c474f 100644 --- a/std/src/gfx/lcd/fonts/font_16x16.rs +++ b/std/src/gfx/lcd/fonts/font_16x16.rs @@ -321,7 +321,7 @@ const DATA: &[u8] = &[ ]; /// Square 16x16 font. -pub(crate) const FONT_16X16: Font = Font { +pub const FONT_16X16: Font = Font { name: "16x16", glyph_size: LcdSize { width: WIDTH, height: HEIGHT }, stride: 2, diff --git a/std/src/gfx/lcd/fonts/font_5x8.rs b/std/src/gfx/lcd/fonts/font_5x8.rs index 88056cd4..e3ef107a 100644 --- a/std/src/gfx/lcd/fonts/font_5x8.rs +++ b/std/src/gfx/lcd/fonts/font_5x8.rs @@ -921,7 +921,7 @@ const DATA: &[u8] = &[ ]; /// Small font for tiny displays. -pub(crate) const FONT_5X8: Font = Font { +pub const FONT_5X8: Font = Font { name: "5x8", glyph_size: LcdSize { width: WIDTH, height: HEIGHT }, stride: 1, diff --git a/std/src/gfx/lcd/fonts/mod.rs b/std/src/gfx/lcd/fonts/mod.rs index 11d3cfbb..6e18d897 100644 --- a/std/src/gfx/lcd/fonts/mod.rs +++ b/std/src/gfx/lcd/fonts/mod.rs @@ -20,10 +20,10 @@ use std::collections::HashMap; use std::io; mod font_5x8; -pub(crate) use font_5x8::FONT_5X8; +pub use font_5x8::FONT_5X8; mod font_16x16; -pub(crate) use font_16x16::FONT_16X16; +pub use font_16x16::FONT_16X16; /// Representation of a font. pub struct Font { From 7674917a87aca63b0a0f9c0ce8804a3a2528864e Mon Sep 17 00:00:00 2001 From: Julio Merino Date: Sat, 9 May 2026 03:39:11 +0200 Subject: [PATCH 3/5] Add the vt220 font This is a good 8x16 font from NetBSD to be used as a default for a "regular-sized" console. --- std/src/gfx/lcd/fonts/font_vt220.rs | 3888 +++++++++++++++++++++++++++ std/src/gfx/lcd/fonts/mod.rs | 4 + 2 files changed, 3892 insertions(+) create mode 100644 std/src/gfx/lcd/fonts/font_vt220.rs diff --git a/std/src/gfx/lcd/fonts/font_vt220.rs b/std/src/gfx/lcd/fonts/font_vt220.rs new file mode 100644 index 00000000..4d8dc1f1 --- /dev/null +++ b/std/src/gfx/lcd/fonts/font_vt220.rs @@ -0,0 +1,3888 @@ +// EndBASIC +// Copyright 2026 Julio Merino +// +// 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. + +/* + * Copyright (c) 1992, 1993, 1994 Hellmuth Michaelis and Joerg Wunsch + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by + * Hellmuth Michaelis and Joerg Wunsch + * 4. The name authors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Translated into compiler and human readable for the Atari-TT port of + * NetBSD by Leo Weppelman. + * + * Reorganized and edited some chars to fit the iso-8859-1 fontset by + * Thomas Gerner + * + * Translated into wsfont format from sys/arch/atari/dev/font_8x16.c rev 1.2 + * by Izumi Tsutsui. + */ + +//! The VT220 font from NetBSD. + +use crate::gfx::lcd::LcdSize; +use crate::gfx::lcd::fonts::Font; + +/// Width of the font glyphs in pixels. +const WIDTH: usize = 8; + +/// Height of the font glyphs in pixels. +const HEIGHT: usize = 16; + +/// Raw font data table for ASCII characters. Use `glyph` to access. +const DATA: &[u8] = &[ + /* 0x20 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x21 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x18, /* ...**... */ + 0x3c, /* ..****.. */ + 0x3c, /* ..****.. */ + 0x3c, /* ..****.. */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x00, /* ........ */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x22 */ + 0x00, /* ........ */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x24, /* ..*..*.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x23 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x6c, /* .**.**.. */ + 0x6c, /* .**.**.. */ + 0xfe, /* *******. */ + 0x6c, /* .**.**.. */ + 0x6c, /* .**.**.. */ + 0x6c, /* .**.**.. */ + 0xfe, /* *******. */ + 0x6c, /* .**.**.. */ + 0x6c, /* .**.**.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x24 */ + 0x00, /* ........ */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0xc2, /* **....*. */ + 0xc0, /* **...... */ + 0x7c, /* .*****.. */ + 0x06, /* .....**. */ + 0x86, /* *....**. */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x25 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xc2, /* **....*. */ + 0xc6, /* **...**. */ + 0x0c, /* ....**.. */ + 0x18, /* ...**... */ + 0x30, /* ..**.... */ + 0x60, /* .**..... */ + 0xc6, /* **...**. */ + 0x86, /* *....**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x26 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x38, /* ..***... */ + 0x6c, /* .**.**.. */ + 0x6c, /* .**.**.. */ + 0x38, /* ..***... */ + 0x76, /* .***.**. */ + 0xdc, /* **.***.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0x76, /* .***.**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x27 */ + 0x00, /* ........ */ + 0x30, /* ..**.... */ + 0x30, /* ..**.... */ + 0x30, /* ..**.... */ + 0x60, /* .**..... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x28 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x0c, /* ....**.. */ + 0x18, /* ...**... */ + 0x30, /* ..**.... */ + 0x30, /* ..**.... */ + 0x30, /* ..**.... */ + 0x30, /* ..**.... */ + 0x30, /* ..**.... */ + 0x30, /* ..**.... */ + 0x18, /* ...**... */ + 0x0c, /* ....**.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x29 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x30, /* ..**.... */ + 0x18, /* ...**... */ + 0x0c, /* ....**.. */ + 0x0c, /* ....**.. */ + 0x0c, /* ....**.. */ + 0x0c, /* ....**.. */ + 0x0c, /* ....**.. */ + 0x0c, /* ....**.. */ + 0x18, /* ...**... */ + 0x30, /* ..**.... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x2A */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x66, /* .**..**. */ + 0x3c, /* ..****.. */ + 0xff, /* ******** */ + 0x3c, /* ..****.. */ + 0x66, /* .**..**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x2B */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x7e, /* .******. */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x2C */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x30, /* ..**.... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x2D */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x7e, /* .******. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x2E */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x2F */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x02, /* ......*. */ + 0x06, /* .....**. */ + 0x0c, /* ....**.. */ + 0x18, /* ...**... */ + 0x30, /* ..**.... */ + 0x60, /* .**..... */ + 0xc0, /* **...... */ + 0x80, /* *....... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x30 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xce, /* **..***. */ + 0xde, /* **.****. */ + 0xf6, /* ****.**. */ + 0xe6, /* ***..**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x31 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x18, /* ...**... */ + 0x38, /* ..***... */ + 0x78, /* .****... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x7e, /* .******. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x32 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0x06, /* .....**. */ + 0x0c, /* ....**.. */ + 0x18, /* ...**... */ + 0x30, /* ..**.... */ + 0x60, /* .**..... */ + 0xc0, /* **...... */ + 0xc6, /* **...**. */ + 0xfe, /* *******. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x33 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0x06, /* .....**. */ + 0x06, /* .....**. */ + 0x3c, /* ..****.. */ + 0x06, /* .....**. */ + 0x06, /* .....**. */ + 0x06, /* .....**. */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x34 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x0c, /* ....**.. */ + 0x1c, /* ...***.. */ + 0x3c, /* ..****.. */ + 0x6c, /* .**.**.. */ + 0xcc, /* **..**.. */ + 0xfe, /* *******. */ + 0x0c, /* ....**.. */ + 0x0c, /* ....**.. */ + 0x0c, /* ....**.. */ + 0x1e, /* ...****. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x35 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xfe, /* *******. */ + 0xc0, /* **...... */ + 0xc0, /* **...... */ + 0xc0, /* **...... */ + 0xfc, /* ******.. */ + 0x06, /* .....**. */ + 0x06, /* .....**. */ + 0x06, /* .....**. */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x36 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x38, /* ..***... */ + 0x60, /* .**..... */ + 0xc0, /* **...... */ + 0xc0, /* **...... */ + 0xfc, /* ******.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x37 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xfe, /* *******. */ + 0xc6, /* **...**. */ + 0x06, /* .....**. */ + 0x06, /* .....**. */ + 0x0c, /* ....**.. */ + 0x18, /* ...**... */ + 0x30, /* ..**.... */ + 0x30, /* ..**.... */ + 0x30, /* ..**.... */ + 0x30, /* ..**.... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x38 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x39 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x7e, /* .******. */ + 0x06, /* .....**. */ + 0x06, /* .....**. */ + 0x06, /* .....**. */ + 0x0c, /* ....**.. */ + 0x78, /* .****... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x3A */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x3B */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x30, /* ..**.... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x3C */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x06, /* .....**. */ + 0x0c, /* ....**.. */ + 0x18, /* ...**... */ + 0x30, /* ..**.... */ + 0x60, /* .**..... */ + 0x30, /* ..**.... */ + 0x18, /* ...**... */ + 0x0c, /* ....**.. */ + 0x06, /* .....**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x3D */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x7e, /* .******. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x7e, /* .******. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x3E */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x60, /* .**..... */ + 0x30, /* ..**.... */ + 0x18, /* ...**... */ + 0x0c, /* ....**.. */ + 0x06, /* .....**. */ + 0x0c, /* ....**.. */ + 0x18, /* ...**... */ + 0x30, /* ..**.... */ + 0x60, /* .**..... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x3F */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x0c, /* ....**.. */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x00, /* ........ */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x40 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xde, /* **.****. */ + 0xde, /* **.****. */ + 0xde, /* **.****. */ + 0xdc, /* **.***.. */ + 0xc0, /* **...... */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x41 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x10, /* ...*.... */ + 0x38, /* ..***... */ + 0x6c, /* .**.**.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xfe, /* *******. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x42 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xfc, /* ******.. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x7c, /* .*****.. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0xfc, /* ******.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x43 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x3c, /* ..****.. */ + 0x66, /* .**..**. */ + 0xc2, /* **....*. */ + 0xc0, /* **...... */ + 0xc0, /* **...... */ + 0xc0, /* **...... */ + 0xc0, /* **...... */ + 0xc2, /* **....*. */ + 0x66, /* .**..**. */ + 0x3c, /* ..****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x44 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xf8, /* *****... */ + 0x6c, /* .**.**.. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x6c, /* .**.**.. */ + 0xf8, /* *****... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x45 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xfe, /* *******. */ + 0x66, /* .**..**. */ + 0x62, /* .**...*. */ + 0x68, /* .**.*... */ + 0x78, /* .****... */ + 0x68, /* .**.*... */ + 0x60, /* .**..... */ + 0x62, /* .**...*. */ + 0x66, /* .**..**. */ + 0xfe, /* *******. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x46 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xfe, /* *******. */ + 0x66, /* .**..**. */ + 0x62, /* .**...*. */ + 0x68, /* .**.*... */ + 0x78, /* .****... */ + 0x68, /* .**.*... */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0xf0, /* ****.... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x47 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x3c, /* ..****.. */ + 0x66, /* .**..**. */ + 0xc2, /* **....*. */ + 0xc0, /* **...... */ + 0xc0, /* **...... */ + 0xde, /* **.****. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x66, /* .**..**. */ + 0x3a, /* ..***.*. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x48 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xfe, /* *******. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x49 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x3c, /* ..****.. */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x3c, /* ..****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x4A */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x1e, /* ...****. */ + 0x0c, /* ....**.. */ + 0x0c, /* ....**.. */ + 0x0c, /* ....**.. */ + 0x0c, /* ....**.. */ + 0x0c, /* ....**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0x78, /* .****... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x4B */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xe6, /* ***..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x6c, /* .**.**.. */ + 0x78, /* .****... */ + 0x78, /* .****... */ + 0x6c, /* .**.**.. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0xe6, /* ***..**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x4C */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xf0, /* ****.... */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0x62, /* .**...*. */ + 0x66, /* .**..**. */ + 0xfe, /* *******. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x4D */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xc6, /* **...**. */ + 0xee, /* ***.***. */ + 0xfe, /* *******. */ + 0xfe, /* *******. */ + 0xd6, /* **.*.**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x4E */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xc6, /* **...**. */ + 0xe6, /* ***..**. */ + 0xf6, /* ****.**. */ + 0xfe, /* *******. */ + 0xde, /* **.****. */ + 0xce, /* **..***. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x4F */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x50 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xfc, /* ******.. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x7c, /* .*****.. */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0xf0, /* ****.... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x51 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xd6, /* **.*.**. */ + 0xde, /* **.****. */ + 0x7c, /* .*****.. */ + 0x0c, /* ....**.. */ + 0x0e, /* ....***. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x52 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xfc, /* ******.. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x7c, /* .*****.. */ + 0x6c, /* .**.**.. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0xe6, /* ***..**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x53 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x60, /* .**..... */ + 0x38, /* ..***... */ + 0x0c, /* ....**.. */ + 0x06, /* .....**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x54 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x7e, /* .******. */ + 0x7e, /* .******. */ + 0x5a, /* .*.**.*. */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x3c, /* ..****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x55 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x56 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x6c, /* .**.**.. */ + 0x38, /* ..***... */ + 0x10, /* ...*.... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x57 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xd6, /* **.*.**. */ + 0xd6, /* **.*.**. */ + 0xd6, /* **.*.**. */ + 0xfe, /* *******. */ + 0xee, /* ***.***. */ + 0x6c, /* .**.**.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x58 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x6c, /* .**.**.. */ + 0x7c, /* .*****.. */ + 0x38, /* ..***... */ + 0x38, /* ..***... */ + 0x7c, /* .*****.. */ + 0x6c, /* .**.**.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x59 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x3c, /* ..****.. */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x3c, /* ..****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x5A */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xfe, /* *******. */ + 0xc6, /* **...**. */ + 0x86, /* *....**. */ + 0x0c, /* ....**.. */ + 0x18, /* ...**... */ + 0x30, /* ..**.... */ + 0x60, /* .**..... */ + 0xc2, /* **....*. */ + 0xc6, /* **...**. */ + 0xfe, /* *******. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x5B */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x3c, /* ..****.. */ + 0x30, /* ..**.... */ + 0x30, /* ..**.... */ + 0x30, /* ..**.... */ + 0x30, /* ..**.... */ + 0x30, /* ..**.... */ + 0x30, /* ..**.... */ + 0x30, /* ..**.... */ + 0x30, /* ..**.... */ + 0x3c, /* ..****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x5C */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x80, /* *....... */ + 0xc0, /* **...... */ + 0xe0, /* ***..... */ + 0x70, /* .***.... */ + 0x38, /* ..***... */ + 0x1c, /* ...***.. */ + 0x0e, /* ....***. */ + 0x06, /* .....**. */ + 0x02, /* ......*. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x5D */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x3c, /* ..****.. */ + 0x0c, /* ....**.. */ + 0x0c, /* ....**.. */ + 0x0c, /* ....**.. */ + 0x0c, /* ....**.. */ + 0x0c, /* ....**.. */ + 0x0c, /* ....**.. */ + 0x0c, /* ....**.. */ + 0x0c, /* ....**.. */ + 0x3c, /* ..****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x5E */ + 0x10, /* ...*.... */ + 0x38, /* ..***... */ + 0x6c, /* .**.**.. */ + 0xc6, /* **...**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x5F */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xff, /* ******** */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x60 */ + 0x30, /* ..**.... */ + 0x30, /* ..**.... */ + 0x18, /* ...**... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x61 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x78, /* .****... */ + 0x0c, /* ....**.. */ + 0x7c, /* .*****.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0x76, /* .***.**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x62 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xe0, /* ***..... */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0x78, /* .****... */ + 0x6c, /* .**.**.. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x63 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0xc0, /* **...... */ + 0xc0, /* **...... */ + 0xc0, /* **...... */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x64 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x1c, /* ...***.. */ + 0x0c, /* ....**.. */ + 0x0c, /* ....**.. */ + 0x3c, /* ..****.. */ + 0x6c, /* .**.**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0x76, /* .***.**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x65 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0xfe, /* *******. */ + 0xc0, /* **...... */ + 0xc0, /* **...... */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x66 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x38, /* ..***... */ + 0x6c, /* .**.**.. */ + 0x64, /* .**..*.. */ + 0x60, /* .**..... */ + 0xf0, /* ****.... */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0xf0, /* ****.... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x67 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x76, /* .***.**. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0x7c, /* .*****.. */ + 0x0c, /* ....**.. */ + 0xcc, /* **..**.. */ + 0x78, /* .****... */ + 0x00, /* ........ */ + /* 0x68 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xe0, /* ***..... */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0x6c, /* .**.**.. */ + 0x76, /* .***.**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0xe6, /* ***..**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x69 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x00, /* ........ */ + 0x38, /* ..***... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x3c, /* ..****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x6A */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x06, /* .....**. */ + 0x06, /* .....**. */ + 0x00, /* ........ */ + 0x0e, /* ....***. */ + 0x06, /* .....**. */ + 0x06, /* .....**. */ + 0x06, /* .....**. */ + 0x06, /* .....**. */ + 0x06, /* .....**. */ + 0x06, /* .....**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x3c, /* ..****.. */ + 0x00, /* ........ */ + /* 0x6B */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xe0, /* ***..... */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0x66, /* .**..**. */ + 0x6c, /* .**.**.. */ + 0x78, /* .****... */ + 0x78, /* .****... */ + 0x6c, /* .**.**.. */ + 0x66, /* .**..**. */ + 0xe6, /* ***..**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x6C */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x38, /* ..***... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x3c, /* ..****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x6D */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xec, /* ***.**.. */ + 0xfe, /* *******. */ + 0xd6, /* **.*.**. */ + 0xd6, /* **.*.**. */ + 0xd6, /* **.*.**. */ + 0xd6, /* **.*.**. */ + 0xc6, /* **...**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x6E */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xdc, /* **.***.. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x6F */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x70 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xdc, /* **.***.. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x7c, /* .*****.. */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0xf0, /* ****.... */ + 0x00, /* ........ */ + /* 0x71 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x76, /* .***.**. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0x7c, /* .*****.. */ + 0x0c, /* ....**.. */ + 0x0c, /* ....**.. */ + 0x1e, /* ...****. */ + 0x00, /* ........ */ + /* 0x72 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xdc, /* **.***.. */ + 0x76, /* .***.**. */ + 0x66, /* .**..**. */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0xf0, /* ****.... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x73 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0x60, /* .**..... */ + 0x38, /* ..***... */ + 0x0c, /* ....**.. */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x74 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x10, /* ...*.... */ + 0x30, /* ..**.... */ + 0x30, /* ..**.... */ + 0xfc, /* ******.. */ + 0x30, /* ..**.... */ + 0x30, /* ..**.... */ + 0x30, /* ..**.... */ + 0x30, /* ..**.... */ + 0x36, /* ..**.**. */ + 0x1c, /* ...***.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x75 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0x76, /* .***.**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x76 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x6c, /* .**.**.. */ + 0x38, /* ..***... */ + 0x10, /* ...*.... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x77 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xd6, /* **.*.**. */ + 0xd6, /* **.*.**. */ + 0xd6, /* **.*.**. */ + 0xfe, /* *******. */ + 0x6c, /* .**.**.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x78 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xc6, /* **...**. */ + 0x6c, /* .**.**.. */ + 0x38, /* ..***... */ + 0x38, /* ..***... */ + 0x38, /* ..***... */ + 0x6c, /* .**.**.. */ + 0xc6, /* **...**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x79 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x7e, /* .******. */ + 0x06, /* .....**. */ + 0x0c, /* ....**.. */ + 0xf8, /* *****... */ + 0x00, /* ........ */ + /* 0x7A */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xfe, /* *******. */ + 0xcc, /* **..**.. */ + 0x18, /* ...**... */ + 0x30, /* ..**.... */ + 0x60, /* .**..... */ + 0xc6, /* **...**. */ + 0xfe, /* *******. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x7B */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x0e, /* ....***. */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x70, /* .***.... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x0e, /* ....***. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x7C */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x00, /* ........ */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x7D */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x70, /* .***.... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x0e, /* ....***. */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x70, /* .***.... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x7E */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x76, /* .***.**. */ + 0xdc, /* **.***.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x7F */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x10, /* ...*.... */ + 0x38, /* ..***... */ + 0x6c, /* .**.**.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xfe, /* *******. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x80 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x81 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x82 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x83 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x84 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x85 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x86 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x87 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x88 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x89 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x8A */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x8B */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x8C */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x8D */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x8E */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x8F */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x90 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x91 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x92 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x93 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x94 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x95 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x96 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x97 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x98 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x99 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x9A */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x9B */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x9C */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x9D */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x9E */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0x9F */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xA0 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xA1 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x00, /* ........ */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x3c, /* ..****.. */ + 0x3c, /* ..****.. */ + 0x3c, /* ..****.. */ + 0x18, /* ...**... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xA2 */ + 0x00, /* ........ */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x3c, /* ..****.. */ + 0x66, /* .**..**. */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0x66, /* .**..**. */ + 0x3c, /* ..****.. */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xA3 */ + 0x00, /* ........ */ + 0x38, /* ..***... */ + 0x6c, /* .**.**.. */ + 0x64, /* .**..*.. */ + 0x60, /* .**..... */ + 0xf0, /* ****.... */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0xe6, /* ***..**. */ + 0xfc, /* ******.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xA4 */ + 0xc3, /* **....** */ + 0x3c, /* ..****.. */ + 0x66, /* .**..**. */ + 0x42, /* .*....*. */ + 0x66, /* .**..**. */ + 0x3c, /* ..****.. */ + 0xc3, /* **....** */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xA5 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x3c, /* ..****.. */ + 0x18, /* ...**... */ + 0x7e, /* .******. */ + 0x18, /* ...**... */ + 0x7e, /* .******. */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xA6 */ + 0x00, /* ........ */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x00, /* ........ */ + /* 0xA7 */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0x60, /* .**..... */ + 0x38, /* ..***... */ + 0x6c, /* .**.**.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x6c, /* .**.**.. */ + 0x38, /* ..***... */ + 0x0c, /* ....**.. */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xA8 */ + 0x00, /* ........ */ + 0xc6, /* **...**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xA9 */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0x82, /* *.....*. */ + 0x9a, /* *..**.*. */ + 0xa6, /* *.*..**. */ + 0xa2, /* *.*...*. */ + 0xa6, /* *.*..**. */ + 0x9a, /* *..**.*. */ + 0x82, /* *.....*. */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xAA */ + 0x00, /* ........ */ + 0x3c, /* ..****.. */ + 0x6c, /* .**.**.. */ + 0x6c, /* .**.**.. */ + 0x3e, /* ..*****. */ + 0x00, /* ........ */ + 0x7e, /* .******. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xAB */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x36, /* ..**.**. */ + 0x6c, /* .**.**.. */ + 0xd8, /* **.**... */ + 0x6c, /* .**.**.. */ + 0x36, /* ..**.**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xAC */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xfe, /* *******. */ + 0x06, /* .....**. */ + 0x06, /* .....**. */ + 0x06, /* .....**. */ + 0x06, /* .....**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xAD */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xfe, /* *******. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xAE */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0x82, /* *.....*. */ + 0xba, /* *.***.*. */ + 0xa6, /* *.*..**. */ + 0xba, /* *.***.*. */ + 0xaa, /* *.*.*.*. */ + 0xa6, /* *.*..**. */ + 0x82, /* *.....*. */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xAF */ + 0x00, /* ........ */ + 0xfe, /* *******. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xB0 */ + 0x00, /* ........ */ + 0x38, /* ..***... */ + 0x6c, /* .**.**.. */ + 0x6c, /* .**.**.. */ + 0x38, /* ..***... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xB1 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x7e, /* .******. */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xff, /* ******** */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xB2 */ + 0x00, /* ........ */ + 0x70, /* .***.... */ + 0xd8, /* **.**... */ + 0x30, /* ..**.... */ + 0x60, /* .**..... */ + 0xc8, /* **..*... */ + 0xf8, /* *****... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xB3 */ + 0x00, /* ........ */ + 0x70, /* .***.... */ + 0xd8, /* **.**... */ + 0x30, /* ..**.... */ + 0x30, /* ..**.... */ + 0xd8, /* **.**... */ + 0x70, /* .***.... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xB4 */ + 0x18, /* ...**... */ + 0x30, /* ..**.... */ + 0x60, /* .**..... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xB5 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x7c, /* .*****.. */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0xc0, /* **...... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xB6 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x7f, /* .******* */ + 0xdb, /* **.**.** */ + 0xdb, /* **.**.** */ + 0xdb, /* **.**.** */ + 0x7b, /* .****.** */ + 0x1b, /* ...**.** */ + 0x1b, /* ...**.** */ + 0x1b, /* ...**.** */ + 0x1b, /* ...**.** */ + 0x1b, /* ...**.** */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xB7 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x18, /* ...**... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xB8 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x18, /* ...**... */ + 0x30, /* ..**.... */ + 0x60, /* .**..... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xB9 */ + 0x00, /* ........ */ + 0x30, /* ..**.... */ + 0x70, /* .***.... */ + 0xf0, /* ****.... */ + 0x30, /* ..**.... */ + 0x30, /* ..**.... */ + 0x78, /* .****... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xBA */ + 0x00, /* ........ */ + 0x38, /* ..***... */ + 0x6c, /* .**.**.. */ + 0x6c, /* .**.**.. */ + 0x38, /* ..***... */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xBB */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xd8, /* **.**... */ + 0x6c, /* .**.**.. */ + 0x36, /* ..**.**. */ + 0x6c, /* .**.**.. */ + 0xd8, /* **.**... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xBC */ + 0x00, /* ........ */ + 0xc0, /* **...... */ + 0xc0, /* **...... */ + 0xc2, /* **....*. */ + 0xc6, /* **...**. */ + 0xcc, /* **..**.. */ + 0x18, /* ...**... */ + 0x30, /* ..**.... */ + 0x66, /* .**..**. */ + 0xce, /* **..***. */ + 0x9e, /* *..****. */ + 0x3e, /* ..*****. */ + 0x06, /* .....**. */ + 0x06, /* .....**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xBD */ + 0x00, /* ........ */ + 0xc0, /* **...... */ + 0xc0, /* **...... */ + 0xc2, /* **....*. */ + 0xc6, /* **...**. */ + 0xcc, /* **..**.. */ + 0x18, /* ...**... */ + 0x30, /* ..**.... */ + 0x60, /* .**..... */ + 0xdc, /* **.***.. */ + 0x86, /* *....**. */ + 0x0c, /* ....**.. */ + 0x18, /* ...**... */ + 0x3e, /* ..*****. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xBE */ + 0x00, /* ........ */ + 0xc0, /* **...... */ + 0x60, /* .**..... */ + 0xc2, /* **....*. */ + 0x66, /* .**..**. */ + 0xcc, /* **..**.. */ + 0x18, /* ...**... */ + 0x30, /* ..**.... */ + 0x66, /* .**..**. */ + 0xce, /* **..***. */ + 0x9e, /* *..****. */ + 0x3e, /* ..*****. */ + 0x06, /* .....**. */ + 0x06, /* .....**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xBF */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x30, /* ..**.... */ + 0x30, /* ..**.... */ + 0x00, /* ........ */ + 0x30, /* ..**.... */ + 0x30, /* ..**.... */ + 0x60, /* .**..... */ + 0xc0, /* **...... */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xC0 */ + 0x18, /* ...**... */ + 0x0c, /* ....**.. */ + 0x06, /* .....**. */ + 0x00, /* ........ */ + 0x38, /* ..***... */ + 0x6c, /* .**.**.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xfe, /* *******. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xC1 */ + 0x18, /* ...**... */ + 0x30, /* ..**.... */ + 0x60, /* .**..... */ + 0x00, /* ........ */ + 0x38, /* ..***... */ + 0x6c, /* .**.**.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xfe, /* *******. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xC2 */ + 0x10, /* ...*.... */ + 0x38, /* ..***... */ + 0x6c, /* .**.**.. */ + 0x00, /* ........ */ + 0x38, /* ..***... */ + 0x6c, /* .**.**.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xfe, /* *******. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xC3 */ + 0x76, /* .***.**. */ + 0xdc, /* **.***.. */ + 0x00, /* ........ */ + 0x10, /* ...*.... */ + 0x38, /* ..***... */ + 0x6c, /* .**.**.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xfe, /* *******. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xC4 */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x00, /* ........ */ + 0x10, /* ...*.... */ + 0x38, /* ..***... */ + 0x6c, /* .**.**.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xfe, /* *******. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xC5 */ + 0x38, /* ..***... */ + 0x6c, /* .**.**.. */ + 0x38, /* ..***... */ + 0x00, /* ........ */ + 0x38, /* ..***... */ + 0x6c, /* .**.**.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xfe, /* *******. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xC6 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x3e, /* ..*****. */ + 0x6c, /* .**.**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xfe, /* *******. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xce, /* **..***. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xC7 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x3c, /* ..****.. */ + 0x66, /* .**..**. */ + 0xc2, /* **....*. */ + 0xc0, /* **...... */ + 0xc0, /* **...... */ + 0xc0, /* **...... */ + 0xc2, /* **....*. */ + 0x66, /* .**..**. */ + 0x3c, /* ..****.. */ + 0x0c, /* ....**.. */ + 0x06, /* .....**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xC8 */ + 0x18, /* ...**... */ + 0x0c, /* ....**.. */ + 0x06, /* .....**. */ + 0x00, /* ........ */ + 0xfe, /* *******. */ + 0x66, /* .**..**. */ + 0x60, /* .**..... */ + 0x7c, /* .*****.. */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0x66, /* .**..**. */ + 0xfe, /* *******. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xC9 */ + 0x18, /* ...**... */ + 0x30, /* ..**.... */ + 0x60, /* .**..... */ + 0x00, /* ........ */ + 0xfe, /* *******. */ + 0x66, /* .**..**. */ + 0x60, /* .**..... */ + 0x7c, /* .*****.. */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0x66, /* .**..**. */ + 0xfe, /* *******. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xCA */ + 0x10, /* ...*.... */ + 0x38, /* ..***... */ + 0x6c, /* .**.**.. */ + 0x00, /* ........ */ + 0xfe, /* *******. */ + 0x66, /* .**..**. */ + 0x60, /* .**..... */ + 0x7c, /* .*****.. */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0x66, /* .**..**. */ + 0xfe, /* *******. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xCB */ + 0x00, /* ........ */ + 0xc6, /* **...**. */ + 0x00, /* ........ */ + 0xfe, /* *******. */ + 0x66, /* .**..**. */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0x7c, /* .*****.. */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0x66, /* .**..**. */ + 0xfe, /* *******. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xCC */ + 0x18, /* ...**... */ + 0x0c, /* ....**.. */ + 0x06, /* .....**. */ + 0x00, /* ........ */ + 0x3c, /* ..****.. */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x3c, /* ..****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xCD */ + 0x18, /* ...**... */ + 0x30, /* ..**.... */ + 0x60, /* .**..... */ + 0x00, /* ........ */ + 0x3c, /* ..****.. */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x3c, /* ..****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xCE */ + 0x10, /* ...*.... */ + 0x38, /* ..***... */ + 0x6c, /* .**.**.. */ + 0x00, /* ........ */ + 0x3c, /* ..****.. */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x3c, /* ..****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xCF */ + 0x00, /* ........ */ + 0x66, /* .**..**. */ + 0x00, /* ........ */ + 0x3c, /* ..****.. */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x3c, /* ..****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xD0 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xf8, /* *****... */ + 0x6c, /* .**.**.. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0xf6, /* ****.**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x6c, /* .**.**.. */ + 0xf8, /* *****... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xD1 */ + 0x76, /* .***.**. */ + 0xdc, /* **.***.. */ + 0x00, /* ........ */ + 0xc6, /* **...**. */ + 0xe6, /* ***..**. */ + 0xf6, /* ****.**. */ + 0xfe, /* *******. */ + 0xde, /* **.****. */ + 0xce, /* **..***. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xD2 */ + 0x18, /* ...**... */ + 0x0c, /* ....**.. */ + 0x06, /* .....**. */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xD3 */ + 0x18, /* ...**... */ + 0x30, /* ..**.... */ + 0x60, /* .**..... */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xD4 */ + 0x10, /* ...*.... */ + 0x38, /* ..***... */ + 0x6c, /* .**.**.. */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xD5 */ + 0x76, /* .***.**. */ + 0xdc, /* **.***.. */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xD6 */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xD7 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xc6, /* **...**. */ + 0x6c, /* .**.**.. */ + 0x38, /* ..***... */ + 0x38, /* ..***... */ + 0x6c, /* .**.**.. */ + 0xc6, /* **...**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xD8 */ + 0x00, /* ........ */ + 0x06, /* .....**. */ + 0x7e, /* .******. */ + 0xce, /* **..***. */ + 0xce, /* **..***. */ + 0xce, /* **..***. */ + 0xd6, /* **.*.**. */ + 0xd6, /* **.*.**. */ + 0xe6, /* ***..**. */ + 0xe6, /* ***..**. */ + 0xe6, /* ***..**. */ + 0xfc, /* ******.. */ + 0xc0, /* **...... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xD9 */ + 0x18, /* ...**... */ + 0x0c, /* ....**.. */ + 0x06, /* .....**. */ + 0x00, /* ........ */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xDA */ + 0x18, /* ...**... */ + 0x30, /* ..**.... */ + 0x60, /* .**..... */ + 0x00, /* ........ */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xDB */ + 0x10, /* ...*.... */ + 0x38, /* ..***... */ + 0x6c, /* .**.**.. */ + 0x00, /* ........ */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xDC */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x00, /* ........ */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xDD */ + 0x18, /* ...**... */ + 0x30, /* ..**.... */ + 0x60, /* .**..... */ + 0x00, /* ........ */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x3c, /* ..****.. */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x3c, /* ..****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xDE */ + 0x00, /* ........ */ + 0xf0, /* ****.... */ + 0x60, /* .**..... */ + 0x7c, /* .*****.. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x7c, /* .*****.. */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0xf0, /* ****.... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xDF */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x78, /* .****... */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xd8, /* **.**... */ + 0xcc, /* **..**.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xcc, /* **..**.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xE0 */ + 0x00, /* ........ */ + 0x60, /* .**..... */ + 0x30, /* ..**.... */ + 0x18, /* ...**... */ + 0x00, /* ........ */ + 0x78, /* .****... */ + 0x0c, /* ....**.. */ + 0x7c, /* .*****.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0x76, /* .***.**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xE1 */ + 0x00, /* ........ */ + 0x18, /* ...**... */ + 0x30, /* ..**.... */ + 0x60, /* .**..... */ + 0x00, /* ........ */ + 0x78, /* .****... */ + 0x0c, /* ....**.. */ + 0x7c, /* .*****.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0x76, /* .***.**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xE2 */ + 0x00, /* ........ */ + 0x10, /* ...*.... */ + 0x38, /* ..***... */ + 0x6c, /* .**.**.. */ + 0x00, /* ........ */ + 0x78, /* .****... */ + 0x0c, /* ....**.. */ + 0x7c, /* .*****.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0x76, /* .***.**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xE3 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x76, /* .***.**. */ + 0xdc, /* **.***.. */ + 0x00, /* ........ */ + 0x78, /* .****... */ + 0x0c, /* ....**.. */ + 0x7c, /* .*****.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0x76, /* .***.**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xE4 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xcc, /* **..**.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x78, /* .****... */ + 0x0c, /* ....**.. */ + 0x7c, /* .*****.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0x76, /* .***.**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xE5 */ + 0x00, /* ........ */ + 0x38, /* ..***... */ + 0x6c, /* .**.**.. */ + 0x38, /* ..***... */ + 0x00, /* ........ */ + 0x78, /* .****... */ + 0x0c, /* ....**.. */ + 0x7c, /* .*****.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0x76, /* .***.**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xE6 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x6c, /* .**.**.. */ + 0xfe, /* *******. */ + 0xb2, /* *.**..*. */ + 0x32, /* ..**..*. */ + 0x7e, /* .******. */ + 0xd8, /* **.**... */ + 0xd8, /* **.**... */ + 0x6e, /* .**.***. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xE7 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x3c, /* ..****.. */ + 0x66, /* .**..**. */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0x66, /* .**..**. */ + 0x3c, /* ..****.. */ + 0x0c, /* ....**.. */ + 0x06, /* .....**. */ + 0x3c, /* ..****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xE8 */ + 0x00, /* ........ */ + 0x60, /* .**..... */ + 0x30, /* ..**.... */ + 0x18, /* ...**... */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0xfe, /* *******. */ + 0xc0, /* **...... */ + 0xc0, /* **...... */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xE9 */ + 0x00, /* ........ */ + 0x0c, /* ....**.. */ + 0x18, /* ...**... */ + 0x30, /* ..**.... */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0xfe, /* *******. */ + 0xc0, /* **...... */ + 0xc0, /* **...... */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xEA */ + 0x00, /* ........ */ + 0x10, /* ...*.... */ + 0x38, /* ..***... */ + 0x6c, /* .**.**.. */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0xfe, /* *******. */ + 0xc0, /* **...... */ + 0xc0, /* **...... */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xEB */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xc6, /* **...**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0xfe, /* *******. */ + 0xc0, /* **...... */ + 0xc0, /* **...... */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xEC */ + 0x00, /* ........ */ + 0x60, /* .**..... */ + 0x30, /* ..**.... */ + 0x18, /* ...**... */ + 0x00, /* ........ */ + 0x38, /* ..***... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x3c, /* ..****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xED */ + 0x00, /* ........ */ + 0x0c, /* ....**.. */ + 0x18, /* ...**... */ + 0x30, /* ..**.... */ + 0x00, /* ........ */ + 0x38, /* ..***... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x3c, /* ..****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xEE */ + 0x00, /* ........ */ + 0x18, /* ...**... */ + 0x3c, /* ..****.. */ + 0x66, /* .**..**. */ + 0x00, /* ........ */ + 0x38, /* ..***... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x3c, /* ..****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xEF */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x66, /* .**..**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x38, /* ..***... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x3c, /* ..****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xF0 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x3e, /* ..*****. */ + 0x30, /* ..**.... */ + 0x18, /* ...**... */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xF1 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x76, /* .***.**. */ + 0xdc, /* **.***.. */ + 0x00, /* ........ */ + 0xdc, /* **.***.. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xF2 */ + 0x00, /* ........ */ + 0x60, /* .**..... */ + 0x30, /* ..**.... */ + 0x18, /* ...**... */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xF3 */ + 0x00, /* ........ */ + 0x18, /* ...**... */ + 0x30, /* ..**.... */ + 0x60, /* .**..... */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xF4 */ + 0x00, /* ........ */ + 0x10, /* ...*.... */ + 0x38, /* ..***... */ + 0x6c, /* .**.**.. */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xF5 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x76, /* .***.**. */ + 0xdc, /* **.***.. */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xF6 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xc6, /* **...**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x7c, /* .*****.. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x7c, /* .*****.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xF7 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x00, /* ........ */ + 0x7e, /* .******. */ + 0x00, /* ........ */ + 0x18, /* ...**... */ + 0x18, /* ...**... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xF8 */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x06, /* .....**. */ + 0x7e, /* .******. */ + 0xce, /* **..***. */ + 0xce, /* **..***. */ + 0xd6, /* **.*.**. */ + 0xe6, /* ***..**. */ + 0xe6, /* ***..**. */ + 0xfc, /* ******.. */ + 0xc0, /* **...... */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xF9 */ + 0x00, /* ........ */ + 0x60, /* .**..... */ + 0x30, /* ..**.... */ + 0x18, /* ...**... */ + 0x00, /* ........ */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0x76, /* .***.**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xFA */ + 0x00, /* ........ */ + 0x18, /* ...**... */ + 0x30, /* ..**.... */ + 0x60, /* .**..... */ + 0x00, /* ........ */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0x76, /* .***.**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xFB */ + 0x00, /* ........ */ + 0x30, /* ..**.... */ + 0x78, /* .****... */ + 0xcc, /* **..**.. */ + 0x00, /* ........ */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0x76, /* .***.**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xFC */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xcc, /* **..**.. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0xcc, /* **..**.. */ + 0x76, /* .***.**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + /* 0xFD */ + 0x00, /* ........ */ + 0x18, /* ...**... */ + 0x30, /* ..**.... */ + 0x60, /* .**..... */ + 0x00, /* ........ */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x7e, /* .******. */ + 0x06, /* .....**. */ + 0x0c, /* ....**.. */ + 0x78, /* .****... */ + 0x00, /* ........ */ + /* 0xFE */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xf0, /* ****.... */ + 0x60, /* .**..... */ + 0x7c, /* .*****.. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x66, /* .**..**. */ + 0x7c, /* .*****.. */ + 0x60, /* .**..... */ + 0x60, /* .**..... */ + 0xf0, /* ****.... */ + 0x00, /* ........ */ + /* 0xFF */ + 0x00, /* ........ */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x00, /* ........ */ + 0x00, /* ........ */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0xc6, /* **...**. */ + 0x7e, /* .******. */ + 0x06, /* .....**. */ + 0x0c, /* ....**.. */ + 0x78, /* .****... */ + 0x00, /* ........ */ +]; + +/// The VT220 font from NetBSD. +pub const FONT_VT220: Font = Font { + name: "vt220", + glyph_size: LcdSize { width: WIDTH, height: HEIGHT }, + stride: 1, + data: DATA, +}; diff --git a/std/src/gfx/lcd/fonts/mod.rs b/std/src/gfx/lcd/fonts/mod.rs index 6e18d897..f10bd3cc 100644 --- a/std/src/gfx/lcd/fonts/mod.rs +++ b/std/src/gfx/lcd/fonts/mod.rs @@ -25,6 +25,9 @@ pub use font_5x8::FONT_5X8; mod font_16x16; pub use font_16x16::FONT_16X16; +mod font_vt220; +pub use font_vt220::FONT_VT220; + /// Representation of a font. pub struct Font { /// The name of the font. @@ -67,6 +70,7 @@ impl Fonts { let mut fonts = HashMap::default(); fonts.insert(FONT_5X8.name, &FONT_5X8); fonts.insert(FONT_16X16.name, &FONT_16X16); + fonts.insert(FONT_VT220.name, &FONT_VT220); Self(fonts) } From 02fbfea4a29db1be5b9b9d7a88c19123850988b1 Mon Sep 17 00:00:00 2001 From: Julio Merino Date: Fri, 8 May 2026 10:06:36 +0200 Subject: [PATCH 4/5] Move sdl to the native rasterization primitives --- .github/workflows/build.yml | 2 +- .github/workflows/release.sh | 6 +- .github/workflows/release.yml | 4 +- .github/workflows/setup-sdl.ps1 | 12 +- .github/workflows/test.yml | 6 +- NEWS.md | 3 + cli/src/main.rs | 2 +- sdl/Cargo.toml | 1 - sdl/src/IBMPlexMono-Regular-6.0.0.ttf | Bin 136292 -> 0 bytes sdl/src/console.rs | 194 +++---------- sdl/src/font.rs | 122 -------- sdl/src/host.rs | 394 ++++---------------------- sdl/src/lib.rs | 69 +---- std/src/gfx/lcd/mod.rs | 4 +- 14 files changed, 129 insertions(+), 690 deletions(-) delete mode 100644 sdl/src/IBMPlexMono-Regular-6.0.0.ttf delete mode 100644 sdl/src/font.rs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ec084dfb..e5676623 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -48,7 +48,7 @@ jobs: - uses: actions/checkout@v6 - uses: mozilla-actions/sccache-action@v0.0.10 - run: sudo apt update - - run: sudo apt install libsdl2-dev libsdl2-ttf-dev + - run: sudo apt install libsdl2-dev - run: ./.github/workflows/release.sh linux-x86_64-sdl - uses: actions/upload-artifact@v7 with: diff --git a/.github/workflows/release.sh b/.github/workflows/release.sh index 6d88865e..02d867c1 100755 --- a/.github/workflows/release.sh +++ b/.github/workflows/release.sh @@ -83,7 +83,7 @@ main() { ;; macos*) - brew install sdl2 sdl2_ttf + brew install sdl2 local brew="$(brew --prefix)" ( @@ -98,14 +98,12 @@ main() { # Bundle the necessary shared libraries as provided by Homebrew. cp "${brew}"/Cellar/sdl2/*/lib/libSDL2-*.dylib "${distname}" cp "${brew}"/Cellar/sdl2/*/LICENSE.txt "${distname}/LICENSE.sdl2" - cp "${brew}"/Cellar/sdl2_ttf/*/lib/libSDL2_ttf-*.dylib "${distname}" - cp "${brew}"/Cellar/sdl2_ttf/*/LICENSE.txt "${distname}/LICENSE.sdl2_ttf" cp "${brew}"/Cellar/freetype/*/lib/libfreetype.*.dylib "${distname}" cp "${brew}"/Cellar/freetype/*/LICENSE.TXT "${distname}/LICENSE.freetype" cp "${brew}"/Cellar/libpng/*/lib/libpng16.*.dylib "${distname}" cp "${brew}"/Cellar/libpng/*/LICENSE "${distname}/LICENSE.libpng" - brew uninstall --ignore-dependencies sdl2 sdl2_ttf freetype libpng + brew uninstall --ignore-dependencies sdl2 freetype libpng sanity_check "${distname}/endbasic" ;; diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d02b285b..35baaa70 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,7 +31,7 @@ jobs: - uses: actions/checkout@v6 - uses: mozilla-actions/sccache-action@v0.0.10 - run: sudo apt update - - run: sudo apt install libsdl2-dev libsdl2-ttf-dev + - run: sudo apt install libsdl2-dev - run: ./.github/workflows/package.sh cargo-install: @@ -43,7 +43,7 @@ jobs: - uses: actions/checkout@v6 - uses: mozilla-actions/sccache-action@v0.0.10 - run: sudo apt update - - run: sudo apt install libsdl2-dev libsdl2-ttf-dev + - run: sudo apt install libsdl2-dev - run: ./.github/workflows/install.sh - run: ./.github/workflows/install.sh --no-default-features - run: ./.github/workflows/install.sh --features=sdl diff --git a/.github/workflows/setup-sdl.ps1 b/.github/workflows/setup-sdl.ps1 index e792ec8e..069daa05 100644 --- a/.github/workflows/setup-sdl.ps1 +++ b/.github/workflows/setup-sdl.ps1 @@ -16,20 +16,16 @@ Invoke-WebRequest ` -Uri https://www.libsdl.org/release/SDL2-devel-2.32.10-VC.zip ` -OutFile SDL2-devel-2.32.10-VC.zip -Invoke-WebRequest ` - -Uri https://www.libsdl.org/projects/SDL_ttf/release/SDL2_ttf-devel-2.24.0-VC.zip ` - -OutFile SDL2_ttf-devel-2.24.0-VC.zip unzip SDL2-devel-2.32.10-VC.zip -unzip SDL2_ttf-devel-2.24.0-VC.zip [void](New-Item -Force -Type Directory libs) -Copy-Item .\SDL2-2.32.10\lib\x64\*.lib,.\SDL2_ttf-2.24.0\lib\x64\*.lib libs +Copy-Item .\SDL2-2.32.10\lib\x64\*.lib libs [void](New-Item -Force -Type Directory dlls) -Copy-Item .\SDL2-2.32.10\lib\x64\*.dll,.\SDL2_ttf-2.24.0\lib\x64\*.dll dlls -Copy-Item .\SDL2-2.32.10\lib\x64\*.txt,.\SDL2_ttf-2.24.0\lib\x64\*.txt dlls +Copy-Item .\SDL2-2.32.10\lib\x64\*.dll dlls +Copy-Item .\SDL2-2.32.10\lib\x64\*.txt dlls -Remove-Item -Recurse -Force .\SDL2-2.32.10,.\SDL2_ttf-2.24.0,SDL2*.zip +Remove-Item -Recurse -Force .\SDL2-2.32.10 foreach ($dir in ".", diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index de0b76f7..5733e958 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@v6 - uses: mozilla-actions/sccache-action@v0.0.10 - run: sudo apt update - - run: sudo apt install libsdl2-dev libsdl2-ttf-dev + - run: sudo apt install libsdl2-dev - run: rustup component add clippy rustfmt - run: ./.github/workflows/lint.sh @@ -45,7 +45,7 @@ jobs: - uses: actions/checkout@v6 - uses: mozilla-actions/sccache-action@v0.0.10 - run: sudo apt update - - run: sudo apt install libsdl2-dev libsdl2-ttf-dev + - run: sudo apt install libsdl2-dev - run: cargo test --package='*' --features=sdl -- --include-ignored --skip sdl_console @@ -62,7 +62,7 @@ jobs: steps: - uses: actions/checkout@v6 - uses: mozilla-actions/sccache-action@v0.0.10 - - run: brew install sdl2 sdl2_ttf + - run: brew install sdl2 - run: cargo test --package=endbasic-client -- --include-ignored - run: cargo test --package=endbasic-core -- --include-ignored - run: cargo test --package=endbasic-std -- --include-ignored diff --git a/NEWS.md b/NEWS.md index 6c8a794f..55010942 100644 --- a/NEWS.md +++ b/NEWS.md @@ -48,6 +48,9 @@ for the time being.** * Because the new core has been AGPLv3+ licensed, the rest of the project has switched to this license as well. +* Modified the SDL console to use the same rasterization primitives as + the LCD for full font rendering control. + ## Changes in version 0.12.1 **Released on 2026-05-03.** diff --git a/cli/src/main.rs b/cli/src/main.rs index 736f7d35..658c5a38 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -160,7 +160,7 @@ fn setup_console( signals_tx: Sender, spec: &mut ConsoleSpec, ) -> io::Result>> { - endbasic_sdl::setup(spec, signals_tx) + endbasic_sdl::setup(spec, &endbasic_std::gfx::lcd::fonts::Fonts::all(), signals_tx) } /// Errors out during the creation of the graphical console when SDL support is not compiled in. diff --git a/sdl/Cargo.toml b/sdl/Cargo.toml index 1e6ff3e7..9a81317b 100644 --- a/sdl/Cargo.toml +++ b/sdl/Cargo.toml @@ -31,7 +31,6 @@ path = "../std" [dependencies.sdl2] version = "0.38" default-features = false -features = ["ttf"] [dev-dependencies] flate2 = "1.0" diff --git a/sdl/src/IBMPlexMono-Regular-6.0.0.ttf b/sdl/src/IBMPlexMono-Regular-6.0.0.ttf deleted file mode 100644 index 8d43f3d0020d355bc7da5df6c84852d8c44fd462..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 136292 zcmd4434ByVwm)8VZzr8S=_H+f=_N^L?JS*r>Fgmp*-2PKK=xfkR76BZ5jRE|7ZgRr zWe^b+F~p5g2T)NNbsWZF5PiNGw^7H3K6Rk^e^1?e(;b4&@_E0{=P#bFx^?fZTXpK3 zQ|FvIRbiYl7KR@k%c<*bnt&_Lr0}UoeJ0e^H{|^_?*x-{TX6m4gpP^bv3q7DGRdov zF|U9L-Mux=8#Z6YB)=ZUe2-4-&dz(_*&Rz!#W0>9oV9r7vdv4cd4#dha@;>LYt>3q zP@%MgvBWCeubQ)L?qctOdx{u~i^4k>&z*VkG8V(HI>#WPng^Pb@Chv)Ca^QOfYt(GGF-wXWr_4v|M8H z!%W7tPU`2InHNenN&R@GMM?Q|R?Z82d?kM`UoS~!lDwTsORHFMnO1#MO`C~VG4>Bh z4mg*@Gj*JY4KjWQ-IsLI2oe*MQKtz|11JZ~0Q9K&TBJm#I@07{Q4 zz`c>~0lWAqE-&8++s|L^tpz%Z5x!)tDI`aS#0Qwd^OJ%WuTmZda2IvCBsn?X!xY0B8Jhhvy z2`EqXq^LmO{J(@qkNRl*|1W|1qt)k!JOSbAe4swkbJQ=QoBsr8tbXo2tsPow#&~^( zcNYAcK;t$WKypC?qG76s`0{)hLYXHJ4bwIC^rHn!NgVQ=)V29gV*| zQ$O4%`Z=FJ{@JyLpJu#EK-a4Pt37~d<9ynrYtQfhTwbHYpL<5TN8i%&$;fMcrj&4S zK2)NN<|0p^XD`&=|En@9>h}biLw^c#a8Co`T|1y1K-Vfa+TU@RUQ;c1;FEAWwZIpzmq8((pA4Ws?BZcj}MUZ|b}DT}r8(aOVkCxF*o* zZb6>DLGMy~2=@zAn1&Q{gMcBVbf54_5Dp-`5;y>aXTq5V^enXvaPE7ggmVIF6MfGU zXzU3`^sGz0mxGja1R4(wXgmmao}kewwTJ4UzEVo>6K~O2(l-f69?&=tP6xhzxv!Gcy<@yF~GwBs)O_=y4KQ1k^fhp3&pcP2f_d3KD|pk zng<{p(U=m%1BhPZJkm7e3Fsctx|Vh!PeAVz(7a7`=m5l5S~UJQ zJk_b)6ZO*=t3c&crU6|OZpCxzcxmG%aPIN0R>!}Sa`5*#s`muoHKiR=ppw$P3)MBI zoMh6ifbD?40d5C81vm$I7I1?H`~vx575)e53xMAPE@kZ80N@h9OMry{_zljz3)l=8 z^nja?-wikd>l&8*xu*cT0PrQ8+Y4v~O!fe(cQ%0P(9)TVxz2c$4WJGGQ9n=zwdFtg zo)|auK}%(13E~tFh5lpVo1kl*p8EPWTLAZP7aVmWuqJ`Tjf;wpe|UKt;^9()XmUctlOr0Q1__rDc!TW zmvn#BeW3eP_pR=Spvr__B;1?ugDKn;V@fbtOev-ulfzVMsxvJ!U1qx8biZk*=^@h| z)03vBO(Uk~O)r|m%n@dbImK)@cbcc0?=tT)KV^Q#{H*yE^C635@wNn8LM=v1j3vR6 zVQI7sS}sbyFZnfFn5{L#@ZIr$Nauuq2YoWJTvp1e*%UU9UBR}CK5gfZ^Edd9Qk3Y^ zv(h2yxXfhl(LSZ1PfQnrKIQ59(`JeK5aLJ znW9W_CX?t>o~cCiX{BkM+NVcMPnhFl+5BtsUh{tQh)16cWBN2ZnW=pW z`tA?^;OEZi^zuJ2Qt&dJ`!nDHN)RLFkZ`gBeKj^X#kb3s7 zXNCXE{k&)Hn`w;wS0iJm3Y5}QDW}R#)tyQ_mGn^;{OGpySk6AIwtT_;3MZA5Ou)<_{;Fy!BMTDIH@cdrq!Ax#HyPljSFqPbQs= zeE+3Ceex%86#SEW6q;mj;^GUycVN8$9!mG5qtctwG3iU`d%B|kr0+0BK1v=W9RLVY zuH+QCPHvC~=>~N}y484d zdpStzpgb3&4lWbnqE^Gd=TRv)Lkc5xY|7&8D*3*~6N0Es>vP%h<)> z)0@}?wp>2IE@A7WSES#{&r1fjlx>m^OE0sPY>Mv z$D{cRJeI%6jlV6ozli!wKmygQt%J0Z;$iI`{mygST zl71!KF1;ZAQo2(*AUz97{k-&$^oaDLv_sl1Jt2)qGo>MTpr=W*rC&(bNgJi>r4`aj z=>}kBrx>UMVnl9ZY&5*W8gVNp7Txpv$UwS}VD(#e(Ne@cPrH7@9rC&>z zNRLUENl!{^rKhB;qqTQFbTEcHv9rK!>#(k$sW z(oND`(nZoe(md&YX|Z&lv{2e5t&(<2tED~C<;ZNM+sPi3zLkEIK9|0bzLLI{{w{qZ z{f~57`cC>^>3iu1>8#|!%D_1}SPqf7EMdl#WiQzub5wv7&aRUEWFOgA*2|~lkC->s zAZYF4e<69qe_Ee*94wy|utHY!Gt$nV1z^4oV!Dkkjgi)o7tVv@j@Yur?6I?H)QRVkfM+#tdTW|(I+}s&u--NB|qpOcd&joz#eCN zK@pokACuTZ$mU-`Lf;O#tjOahNEV}quL~*s4fm2?1pgmUWx-)cf+O<5Va76BhM8|i z{}Y_wJTT19{bHEa#UB7A$TOyA4Kto;GS$zo+sg-WA!VW<-He=^X=>OjCpUEUTL(;= zOq-f!Z!$HQ=FObFSC=eO+_-4dK(=Ww>+YYA-=2Q+-l~Dv(dtCTnhkG6q9MvNdazvz*wP4QQc8TFfK;KT zq|vv0QL6=v6^8C8&-k%l!f2X?+EdjMf3(;MCf43U?}I>L`V62#8LJW08VYV4!IzM( z9D%q{Qc;fUW$N{IH5Jd{dGQ?5D)dZwPf71nueYn|TWY#YO_!nv^!y;O#xEX$%7YZw z!IYvtagFpoaSf!DV{{6@p*3t3TLF!06HwCx`3#+y1)M8p0lW!PbRl3OU^(-abSwbt ztO3%;0-<@(TkL!X{~j~tDY;k|pu62`w%0-L81E(CBi`TWFW0~4lkRh`Z>aAA-_QI? z{ciVr(?8w+TK~5LLIb7;91XMt&I{ZX_?MumLHmNf49*K)7rZM(3TX~`B;>SVp5ahv zap;23{h?onwTJBr`#L-)e2+2DSZZ8rJRZ>>u`@C#a((1TRBP11=*we5W71>tVwz*7 z$E=Fk9vcuF6`K=V9y>X9PV9!*9kF|2M`GWOJsJCBTtu8D&K|cQZcn^l{IdA%@yFu7 zONdL@oamisN-RxWow&}FZw@zSntLqFQfztCdQTEd+LUxMIVZU>xhMJZ|Ysr%G3sepI%) z?CbLW^4aCf%GZ={EZ`tYuFttp2+OsUfH#rXi&vuff^S+|bu>vf;A{%Ovv>w*J`W-4@!G(3alzaog8zXWHx9 zpPM*&;wux6O+3*t)bU=&M;%{w{MhN;8QPi9nci90S<~6x`DW+)oqy@-=^E@>)U~>6 zL)Yf6d%7O!+Shfk>uA^Uu8+IE?mE-$(;ePz>dx#g?yl?Z=$_g=uX|Yd)Zpm$ZDPhWVS zsV}qd#=b3m+xvF+4fnm$cdYM3-zR;iC$UKZlh#byIBCnIos;%XdT!E@{=)v6{`UUK z{d4;N(*I4rYrt*Ls-f1Q{-N1J%ZAnrZ5-M% zw0&sz(D2YJL&t_r41F?mdKQ}%Fl*JUeY3upT|9fo>@Q}Yo_*$`Wfz^C<2PseoH=v8 zo;!8!Lvx>;7c+0pyyxco%nzC0IKN|l|NM>fH!m1mFmJ&l3#EmP3%4#jy=Z9BzQs|C zcPt5A60>B;Z&lK&oK@wk8dvqKn!0Mo2?cvhOcnb@^Ye7`o!QHHB+7t+{>8nJaZy&c1Tl zm20ltc;%KW-(Q=z_MWwmTorQF+N(BQb?a5{UUlNCPp+}nQdhI@zaJ#_E8_XXY8bKmXv9p4_cy?^_W`-|^y zy?^lj)%S0{|L6{%9ql`A-Es1P^as{F@a6+w?DX52y0c;D?49d(Zr}OB&J#O-d@$<4 z!UuaET=C%M2lqU9?7?pyihHQ;p*at2erWii_a8d*aKgil53hK5%fkmB{^pUWM`|9q z{E@wny#H(6ugibE;@9{5`sglcSLv<=ySDB+_NdRJ#g8s~^pQtD+8wjIYxkzzFFnQ{ zt9oq1V@LL|J;i&L?b)^G{l|5W+aK?F{PM@|ef-Gdr}rl8?c2M4@9w=HKN0do=@ToS zxc7-8PqHU-o}B&Ut|#ArD(I=cr*3>|WS?$d>AnT~_V05&UHA0vr{8_rwLf)#*Zw8@ zH|~F7|MC4_KjZg|<(cwldY)PG%(c(#dgj=z;VDH3udiSaIOS=fC^S_Cq0uk`9$0>N&LJ(6xsiIdtsMH;2Oy+YfggUU~S` zk;WsdkKA+Q#H&HC+Fxydb^WWuubzG_<+XLMeffIO>!q)+dwuxzmtH^m`g^aRdi}H4 zPrvRu>T@*oXx!11qf3siKDz$s&ZB#d9zA;E=wIGQd1KBS``^rabM>1qz2)Ad`^!* zMb)!dQD0#80n|=qI^0V`*%~x6J8GE4;^|P_p_U(jdh2sKC)aFAu~~z-)sj+>Us%Qq z@>8r9Z@tYaJMtoo28S^@&nZ>lws$-akKkWrr+RrAS}HpS=U=jB zu+jCWL`RBoNn=e(PEwYmr>U{sQBsm-O$|?pH%#qvecVtK9BmEnO-(VTrV&2M&zw~$kc1IVkY;pv^z&VGuR-mq^s(!x+S4-8z0uB6KRN3`KXn*S3#)aHrp7sb z)Kou@&UmttpW0%DENQfIa=k2esUY7ep{bF2JLpeso+GcYC^Cq1XYr)+L{oWRQOop* zs-Wr|Yf(yASW0nHeqDG;4l8@h}b- zj=-jbD@X8H0(i`Cl=gmRbPmAcB7s3;lpuOwKuLnA#E?DALXHiy7y{%CcqZw*(MteT znT?<;b>L(%kmdp~7B46ssI*v}lM3>C%T2EJ5ou*f`K{q)kqf)b#c7c|)tFomkTPLb zg%bmrQaiOMB}y5@)cjihepGsiMelDZPJ{Uf`;SQ`@K`uoL9_=)6B?+|o;ql5&eGAa z>O6=soC?B;kx0B#cXjBBEOIn-0csh^X~-tvj=D|K-*7A^w+JIv;1JT&Xf-;FR!B{A z_nzV5qO!-{dh4xWS92X-Q@0?$rtY3P*9Op^jB&}7a;0Xh&^5rTbZ7)cy{u0lm1?g; z?UYU!9ieDyA^c{e(!opxW{(0@3Wm@VX0+LaHjiMuWfm>!&B(5nA%ij)Us3G^YdZs$ zdiM{0Nf$|zzPv`t-wVFauY-@8o74lz_KJ# zXcj3duwx!k6Jp^=4LJ$i7#;+Pnqmu*jo}exJTF3@pOV47m5V}I6GmdTk#g6szjASA zYD82)YED61L337VW}Icl%D9C1{Fbc1sDOZ&h#-6NirJG}@|;iQ7gn}6v_D^7Qc_-C zTwK=PQsYdw`v>@2Vp3E6bjGeCTTOP1(KjKr!5USQMi-JK`38pvMh3UkCMFwmEx8%I zyVxFYFDk-Mu_7lj#T=Cg2h4RNz$u)c0#hc8j+=iHXfP5y1|xIT0`!WBb1apHc@i+s8cVPl)~Bb38O|ry_1CIY`ok#QzI!2R4X6q)h#yO-mF+G3 z1vinp{-9DWZ~-|ugr75zgHa^y4J0stA59L9(=dE!aC|%lCz1*xJv58}DhN;te1I@T zf0B?!qT(yziO!D7$t9ga)fcS>MU^lbtV10-pD1gNsdK2qSsrCiis&E>BFq{SGIzS` zX))){ebOBBhW|{Y|~_q4~=rbxXDJHY(enjCmWh}{6!x^ z(TAMeNLUnTS-5uwAKT~uF+(8PQ+)(M&jB+Tl7 zl3)*-P`S@NH;&Uf)!9?nJVJ7*3ONc!fCVoWC|FQJr~QFhqJ>KC(1%%Nz0o?qLpOQ% z(0gCadUb$CKdblN*Ib8qKxgJL}MY)P#zw^*Ra6l@jaC{-1Dnyb-4>vUC>?Nslf`w z#1VDId2mk{6@)1TWT+`qvW%4B)8HPNjItoyAsvev6)CI{-0>s60xFFTC&YY2q>_NE z1b=yZWD1*g0ht+DnO<^GzP&P|IX20Z+8k>&rAfN-g!HT=tG&O=wUrOlIMUKxztGZA zSrMYhiqH$o3U_bEX};sSuOdhg3MoxwXp&-s6_e54YJF0SOSiBXCo3df&xIrf@g3RG z5g5lXs6VhY7z_?&76>2Zxu4Z%!-XUueKr<3PfH^~BJIRBMhD@Ug_9~wriw|0T3XxH zRSO`^)JY~r%7ob!&RG*u>N|Rxdpg7q8bx+rG3>sduy5UVpNdB}rpIxhsym1oQV^P_ zA$TFXPa3uRf*|)ayU*xp_Zh5VS~vfx-KSFxT-V239%uNG9bf$a){Yk)zmP2j!V>&K zOLrHvrO1vSrx5*fJ3gPwqqePP$a5$QnC-BZbU_>Ar=+}lSbxsOAYu%JrZR$1p+JpY zyh%8WwJ}HVyW@_(dW8(sH_oi@J>u#lD`R~{eMd+22X)|Yvi$-;e-6?G zth8kBA@9a1sGjUh`lnTJZwON2UTO%6^LV+!qzt<1M37Pn?K%uUYsjkV=lDyoBH z%!bZ5XTJ5)fFgUgEj_~?9vk8-2k3$l^V4l5wy;cFu76}$Kzgc2d0UT=_cn}I5Z3*| z*lf~;5Psm%f0c#eM(rtYlGWZGs)Z*)ywQ0tAvU}*)sPM7g;cQt71cuc(F({8B({Rs z7FHmyFDu%;F2>mQ`RAY4tDZUrXE+>Ns;Upg8I8J(wW5jU{@bsFE6Q`taN*G@W?hJ3F8?KZGWX_*}% zFdpreXs{%Y;T+dYPdtfcx^?jp5Thjck}CFL0jQW%C@&TyMw;BweuUaEn~GbgW7*sq z%qtE)JJ{~jmA6fO{-wc5WxA3c?(JIK*SGkr>xav_x-O&fEx^2G747aPx*XeXwGnQy zHf{jajtKSESCC*Z`AtA`UA^&fo=(A~E=g&gwYFd?OOl<6Bpk9_yOarfZXQ zXK#ow8~y8U=06^P%eyL?$UDV}5LZrreY>We;tbv2g`VakWe0%zXPPpE0Y zRRM>lZG^9?m$$iI;CH#6;*;9=#dU42n@E3@;4UXR9D&U9Cy4^ItE#ULdgV2$d#Ka5 zTe7H4b2F-YWJ%@oVmg5vKX~mWVXcyE@dcL5HmlwWqT^QC!9OhfwrtXSOW$*S=R5Sq z;lt9%*#>E*>v6QdAMHv+yA050lG)-BOoa&D5_QI*CVp&~Q5rhhLiPLZ78-~c4J0{8 zC!%R689i1O6Oz#^#U$6Gl*~zGE=a<2r0ok&vfii~%4XG@oWYH%et}&7-zLwg$SG@$ zbJVB0zWt`Orl$1X%?0J<1)HUjp{~ltU^%F*DZRNcu_?bWt)Sp6ugT7_<>dSY^JoixmRRo>e8FyB}`Ef_WhU4Fa7sb47{PDu%uzFF}G!nI_Em;a=wr0iWNn9f;y8nk;?^+Z z<#lV3eeYX2tlJ+6m%yX#3lSsH2jyy-ODu}T|4(FR}EMn7Zu z^*?R{SrF=X4U_q>>mJ_V8WCT0y{z=T7&PMtnn`1;$YY1_MT~;hcU6A5`#x?OO(SMV zQ>M`XAR|&BBN?f;7|mpw8BlH#REWP}y+(w7RztK0+Y)|Z&7?NC;QP+Cghwam_ZWcPcd!HxKEh?B)W$mhNtaUxC z^i{E;X`I)QOdLDTs^D?=Ri!j{!MK$(w`D<6QYWM&LlGDx4v@%8xXtH*Q zIuS((u}1S82}-eE2jNYQ_ntj__})ExYNhK}ll%A)Ukz zoYPC@xw0ZR%mRP_pV9XJ3}w*}aCQFi$NWRH4spE3i=a56dSqZvh_JcH3qRR4bIzQZ zyB;YhD=T8iNsob_I{NOEnSPcvVG{ zn4_aGj!|flCXwK2MhQu#7}3UHA8araXl z5Vx-CIU(TSCdNfAk?4M)(CvLOFKP~8TIdnN09>c2g>V9E@dX*&u%`#Z)8F&t^j%Bc zBh)*`$MrT(pCHBv+x$4o0^KKo?lZ_=t*5OA9&$rnPja`|lj5P7gGOkHCmuPnjp9`b zBENs&F>-@M2+@XRC6pwA6QL81`YTctcLgmAC@}}LEU7sw(xJn*P4!@2P|PBC&jueBLI%pjKW6{mri39;~K*y$Zpl$%== z79S;70YkB+1cAE`9{1(x!B=kKW?fN~n_bUM(MH@>6z%DkZ>lH-7M6q=7ilxBuSipi)b%prUTlZ#s7gMFq> zpXw7F-&Ay2uTL+Uz3d(Sn5+98zWHnezuyIuf@9zmS`ls6o!9np^XNFyH79sk#~cQ$PO0jLu2AzMiSaZ@BZ$ z8~AMAcil}lUFUif?KYs@6M%DnHklNQvGeIT$umwjb>rOa?tqvDYRRG{8<|`jX2WQk z3|#vA;Ot)Aq}kK|&O`Yl`|rDNzpLN%CE+j!ZAle02j78wo-Hzt-`p6Y5ikH=t|Q5pH5wOl&1 z4#Ej}sF^Ur$FK4zaY*42k=P5OE)k^Iu$S}#IuLMxO`T>#Z9btxl3X7cYR6$hx?X5N z26Hek&&oY}P|VD@a=nt3i$-7vyGjF+H>NbANoXWen&6Kq<87gzVFyj~AP<3Jsgw4v zOw4Xa^%&boZ$pfd!`OR7zc=^5HN#DkV`sr*nx0;bc0^;oO(ee>Elv_TwYf;01>EA- zlQ$B?2*{XRlz6Z(vdW4lK>Sr)FOVKX-r;eJM>Z*D}i=VZy z&5_?(6d##ZVttuk(f77}W??{OeRW}6VwpWIp|CkKwV|paVVB@%%x&Iia|vm2AvhAC z%%jb!q2O+FGMV(rib+pVHl!zvfB{2TGF}^I#o(M0Q5J)=Sd=B>xg3;Xj@1^*)Tmgc z`EYq*4TEBt>=G0RvInV?aBA|Prltuc#q|-DfhmsKjI8!jGlFR2;tSKGt(60X_6bF4 zA(cT1wmft5#J09>ervTuUtFAI$#F{!RWUjY|{8#fWyLlHVy7 zhD*A~xY%eIY|K3_o)(=A_$G~d1bqup-Io@$i<}FXL2;KnlNb%NEY`x6Q8*9#aOvr7 zVP^iV>qxjhrmEQ9R%|jk`-?N{3sQme_>>$|V_SPuUQy(PXud{T?)ui67GFHk;pnZf zjH0={-lx9Z679#=0oA~D75Wr~K2ZeLToN;iMmo$WhSD7y{xedW{HcB_uE~8w(o5kT z1MawEDT(G3PlG1GiyT3+K9WKJl>VN#Hl0<}Ut!K}nw+tyWT4I->=oTrZEr13v|zQm zapJ^AKtOW+Y-juR%O}OAwfD}fkTOaViaTtAa=yP4*NBH+~w9wxk& zkpBut!U{eHX4N?{b97!9r`EVB8HOA1<`8t^#%wZ56wD@*_v#c7O}?s3(M%tt#QGyJ z2b7#PL1?jyWBuA=(XM*7RsS*1te9Gn*j`m!y=uXNRn^5+?TOAQ70o@}EiK(W&2!opIW{I$XkHprAUkM~QT!nU@%ygly9IG};RQO(x(e@^^v5Al39%r(9&C zT>)BbVS=O|m4oCqm!h(p2Q<2i>8!T56(yPx%1adag-U#U!uHrx8Uqpv8d7753v$DfTC-A|Sy5pL zDPfkxw4w~3kOXr|)Y8ee**UdQ>8WXvo9E_NPDnDn9p97@Tv!F#a-Q3aGn@`VXRKt` zlA4ZV0R+y;NmW5OuO?YVt~XDEs2X?q2{NVp)#oV;Jo4B9#Ksa{WLTADwT!d z9^s#adkM=^ze1A%1?7=bCkHw3peMb-dsi^JF-4#gG1l0pK&v(hQW@b-RVxUE{LmGx zb1P#L%LmHJdkdo{c=_2gD;gUsBFs@CUS1PA2HHY0TFS4On3R!`l%A2X%rSTCyp`Jq z(^6`tTugI&-MoKJvRi!L+Z)q^E|H%=n)?&*QPr#=sTPq9JExxW<0d;iJ%bY_Wvb2DmX zc)njeA2{YK27CoK=pp>a5q~pDiaU3|QZG5}$F(aGZ=l{^(;J+9bnbuoDt0v-I7!rx zv3_VbLy?-~OJj1LsgPzirvZE|*uHELKlpOyxieBGy9#yEq>t6nAa&h^!rRCttE7pe zob3LNN@8OyYNEL@(R8Ro4>zw7_0VdX6>3T3n07gS=4zoDA$jk4sV+VO|9ZC>%))rii$V{pe?|y)Hf2FDW`UZ5!S@jQ93v@A0wkdE>oGZYzp3>JM+( z68+|z%eQSS!JE{dW}H$P!Kpv}+o>J>>=&Y+wI~lpxs45+TZ5K)vw@j3hktagnXi%p z(KLubauX?$NQ!Gn8s!N0J~vW&eG$ft&4|Q*1qcB!%46so8`w;7nJOple;qL#KfW%N zcyOfhUGO>dIr=Wv55|7it+$Tz#CWcN1&W3Msc1;DT;MRu`mqg(i|daIQ5*6p_kR6# z*N@+v*N(~OoRUBK9|w_8PE@Y#L5dfB*XEccnz@rGB8J?z)c*i{7DI^H;XI`n?NH`T z7+thO7S4YBbOw) z6(_J+RB%D7SZ^GQ;IBNpkfU`hN$txY#`eJ7vs0d30jot(-ylAa(M}&|&v1WWdZNb%Ow+^% zIQtOw|A6}SER6_y)L+g8k1Hw!{BkO}P^caz2&_obitvX#>YaBs$?KhGesoe>XP~Z2 zMP12cu#DEFM_m$B7G>KP#cO+udN@Mxu}oLMFLizL&O6fgXVyApZ`4F}%|~5dp{_Er znTqLvB?Yf$wOzCki+04(!ZZBdxCv_o-%w(s?MNvYUm!C2#I=eQ?|7-Hq{jEA08i)Vs%Jh9UZ2>ZkNQbu=yUypEbuf3BlqOebSZT^Lg_w~GV( z&|n4{6JrXxLiE~XKDE)c6GU}}CNspkc)l~L8@k`|!aIDwYiA>$s=g$Vfgv?OKGU9a zc-}OsRva(xJum8v?UpZYhraTzLRB&>Ivr|Ku5`q6nBMg?#k(4}H|_>tdEwn5*Sj=_ zso#cfIuGslW}&dZ8X)WZVIjuDvzi5pK0tPuLeUi6LZKE)8my-AYMQ8~ZaZyUWR@Ck z?Cyk_grr%u(@2b3sK7!6G)0)zU>gP+QZ`?bXERJtSSnUT6befv>O;9*P+2N+w8;z7 zOl;^u~VXG#grl*Xzlc}3{DeN<-{hi7#oqI>-| zo6wrAq19WsL~t4a%Z?h4*d#QNTqf9ll0^4upB$}`2e5L{HgXXVf=6KUmJ9nA528J3 zSp3QxEy>1HwPHZZajzMg`cDssJ~`4$yBq1bJwaH<9@d@eT~%E!H&iz_)eu;clP!Qu z{!+Nh`EX@ZQ)R8qE4hv`b;;hL1&Qhdr`42iZfjTmq)O6nljmJHnNA#q7w$TDYRH>o^@8SSeCc}m#MPF8G zX=s36JJ)sB+5e_rJb?qo&Rqmuiu~R!6b&89T3MSIn^54of#`x}bYZir3?*2Omah~x zB6d;Eh6qJUmf{G=!=i=|SB;j%ptMTdZ5q8>h;7!2@J=3$gQD0A2x=^xyCpR_KgKSg zYqOP&p2Xf?68VWd0*+1o*LZB}wGUXcqns%vRYi`f?&i`$Tk?fVGfJJ6218|JX(?Gjp^=@AoMxj}R9)&%-8hkvZ!qZe#v-eh0a_uyv(7EVd0?dE<;w#9_T0L98!Q~%%ikTBvItT@!@ndAQ5TK z3EG}b_tSCTiQEW0U`5O+s>=s$RgYd(V_wi2>-H(19|wfhK}9;MQ4r)`QB-X#k5w-2 z%+BsyT&bmwNtH=Sm6IH5THMsx)tFOZDDbz$*)r185|V=Q4Ar6bzRTNMujsSe`>tqh zyS&d{lu|p{QSO*ri;ewiW@u(-*I;H+d{mqxtt8!%5S?hX6N_Df+4jFc^MtEH^zUzk zEgWJw23y+B7xFJ7JF423ZAZ~wz(K$(Cj^t4Ye*m#Mv??A&^2i73gAyV>HYQ$;U35?XQOyp0@#FaRLqt9F*Ej__!ngtmP~AHYO`khr|Y9FnQ>K3 zDSOj1D^kn(MRC!QDTP^ud2#LWk(R*d@PL|%UB$nOiuB91CZqxJHv%U|Rb2%!dvD@7`2=IXbE)5@QX1ge%VWTOCq?Dq`sonVrEsSTj_Vl*2^!B!7+3i^XK5ekK zXK=8mcd(!+qb#FH{Ge*I^J>0~|3S!yL?upAS;5xgB(-sK%LBA?(4$pu*&3rF*}dkW z?gzyTrGEW*hvoBW3;r&nU&=v03z7ud<|bLbp3G5(PBnX zy?fw^oLq`t`n0S@lJY zWMdcE@Gla4K;ACB?eUT|hmD7u0c%RGY)s!(Q&V;PcvVf!uJqkXT@Bdh6AE9Sh|j^% znIeu)Yn{4)9OI(w-0_tvS-6j2Ab|njkq`(nW(Yc%M(L(frb8L+$aHtefDV!H3`I&} z(uj@~pX5_IU%F9Fsq8#YH?b4yCo6;0gHl%if7uerA7I5rlIhztfKiziAMLcYU4 zP1~mERJ8MGL{KnCNSlrJDt-FA?s@0ili$YC?0v3XiF}P~176H$zm<}3es?r$qHd|N zh|iLT1;SQ`^Jghej$v8Jm#idw1V)IjvH@1J`y%iph3!FTM$qo~WC%k#7>XpI*dvbv zQLw~DheO?(QV<`L8WwL0sW3E@rQpbu)G%{OU`0gZZ!;{G6lYt|)Y<9gq!iv#8MLq$ z?Q`NUFvdtp7*88XI+Yim#YkhPm}np2l?a>8IUvGLCW282+sM6-9BDmr#QD3r-{F7a zQzs9WVt6;+J%6pp2i0q9MNXVf9$Z(4uRuuP`wQ^>U`2*0yBrZOeGF?qLEe!_TzNi{e9-7P`LRC+<94cNqWQiSKgwuBYM5W9;o=!UVaX3~;lX z>Ulr`rXs`?LEH#g!!sYqeL(x4GcAWA(siBq3zIRD9XSK0J$RyZm>DU?NV9^J6#LU? zElt6XLcjjdY<1!+55cCmiWc#0w9YoxO*+;C47`4+MyyfP8Uf+?PwvPxzQLU*OghA{+c0{h@OWQ<{%N+PP;0`-g*ky#45$lu*NQqx{Q~d6o>EkZ zEFm>=@FfbP2Jhq$?~o#KKJO?8s{yY+afjcTSR4~m5FMWsSP|4v{I6Z`&alA1C|g2w zPUUFk@Mvx{BX|aLTZ|M3Js;loU~MjVmSh}c0#U!l2MQMw#>Cb-H6`&*QiQ}iaVv>; z;#Lyxm9*;$V$fWqi9oad`l9s@Pxz9D6*h{D>u-gPv+Fk0%@#kP#9sWBR$1!8_r&~x?lBq72ABydv{cBD$%3#Zc%bUasgiM<+Jea z9#_{adh>^lA5b^#6OyG)^wytc(z=bpm8yswom<>1sLE`Ia2t@Kfm(#h)ke9x_@25t ziQlQeqwB?igF*nc-y4IQpux50aal*|1v3*UPnqtj|3cEjW7;=X5{BA3{CA|L8h3;Sf>< zI6S|W+M=Rbz({#!TxPlW!GpkOI)1V-hmn+nmtNc1gTA7_KeGHq_K=sk5r6u)>~Mm}wV3bOk)%YykL*ov>>XAiI~6 zwvRu?fo4L$q2bn1IiMc<;I^&ZkrcF+Mc#)5&=Ku-N0b+IMw=)jy@s%(45<<+7lo0s z;x1Snx|F&vdQ3=;IT=U$k;-NKWz@v{6lZQ~NLr}9yw&c!sL^I?yvRwZtD6_bv^yN_ zF^dzj!jtk6BF!b4(M5wB+S@k_7Ezk|Mq*l8;v48D`iVW>Sa10nU$#>$s{x+J9MM?v zt|g83Q(b3uQ<^7nYbQ^VDz2@w(R`_ddZnULGF{cxwIJv&P!tWy>7rsGP4jPIDH-8K z%E~OS?&u1N4K_#EjYeCMxxR7HoUC+g051(NXP4(C+G^^P0%o*E`5Sx-gW?Q4wXh}I z*w;DhvB_C=Ma2$lydyb6I#AGXwsu3U!2uyezN`#%xJ`PA<`neT#`aS5vNocG1xK{Y?zs{!tf{?B4U77jH1M z9ODB|@7wpZ>tV4;=d3h{oi5Y3d=`Jq&7RScVHZEQl#~eCP|kW%VsAkkV`FdKXD*E6 z0eAc(sS;6Y)FZ9DCJUKF4sqo)(@>NU^+P98qq(*7YKrVKtyGUa>`#g?VaH?f0FHzz zX}3EkM(Yd}orC$;*rv1>e5RfZ#Y37FHdvDCW|Wl;)+UwMq@3=?^61FgSvR_lwBm@$ zR`DbH*UF4Iqvt^pHMJaaJxHkG2%ZuQK$<TTZVt(#@$r`K0B z7Zx^G)Tifr<_oK<3jvpgud=Rn&Y$mGYh4xYnXj#`sj8_KKj8JAb2sCR;8kdgg?yXK8n3y$v8>ylZ}RUh1ClXkYUfs@Qe-8A<1 zXts2pTcJdWM*vZD=nWaZ)n1T~5%7OU;0#PU5r{fYTOOmC?sT<`zvvdNc;M(nA&Pj8 z!4pF830K?bc?9GIAg_#D(0d$!(SQRmvQ3R0{vZGKuOIt&G@7z;3`WD4V=zonI0j>S zXZ_Ur&gnP^Bg&*6gu&1K9{R!%bQU8?b8ocMbHDvK6-7N3OXv$wl+hXOz+Ez%0@Qsi z|45W?dD$}o@0t$yA818( z{|5eD-EH|5^)J@Bit;hRl23wR%j6-rda=(mn*P8QP^{@zKO1RGLB!FBl~y<~UBp(C z>s#SkIxT|^zQO7Y+DeWm3)<>Hybnb*K;_Z`ggw^CA~6!+JcL6)xo$~MmZh2P{zf`= zu4BHrGOnehrY+3>)%|jCRD2L$H{Vc~jni-IEp_Sk_Tt1!M^;)^O(v3=<(<1lN|iG(%h<7O`!Ffa_>*0NjL2-tK;tNNjB?$$NG3O3k_JcprFqsJLa(^n zXqpbibU`UCUS%SsC2OT}I*uk>Jv5gzuVmbz)CPMs1C*}pIn09uOh(f{sNhLxACU$` zyntehFqf#DYK#=8hT&WTV$}k;d4NvX-aN8gNUl{m7JGQGa_!j9c4YySeD zInrgh#X(A~&;7JakPMx( zKn^Vsv>X2cS@h}?@D^9JYMy-uIWbEZ$b=6J?k$qxp`BplGh;RDz)9jqIoZPeTax7@%7iJb38X{D8V_(W6| zlf`5x2Xkv!I@ik)kzv>z86Fu%(;{NV0-V9cg#%Lpoc?}+QU3nUz_eULoo}R}G|L`R z7m$!%V)J*Vc=>vjMO!QUjQef*_AGzdcf~K$w_m*FspusaNz(i)BkEHUBGZD;e&AnW zON_}&i}#u7do= zGB6zmusE(+5aT@At`$f;bNEAZ;AfgR^hmXhH;xBJsDW&Sn3}h zfwk}~<}dhNg>gXoh_Zyj@e!JvmD9?}%jSnq(pm!<3>2DA7B(%((jp6KT>(fbXo<>c zE{sJ={7=tQ2!AsE{Dg3$1F!HzXzp|pZNjYZf_cHyH#St`zbkvybpB#J57e*x{`=F_ z9o47R1b>nh3uhm5Cv?nQ*v)OgQwhS5cF;Z_Z7)#;KI`UC_xhVEpxv{bTHv7ADTfj} zMF$Bf(_JlOb+KSlAACUBEb0JSD87aU2M2G8F^R=<%3o#)M@j;YHfzUE#>Dgn)kj!5Tk3@H511Mf!Lb5`aR8DkA z^hb0^#)btcMb8i&5?>Qn+Z>T@2QkjxqhlJ1!mMFtz(8e%Tw1Jek$I}zq%SU&D=K}~ zSBF%u?JCrm(iSyoR*x*Y8NH&y8yhOH4KPN;X$?KwOp?2V4f^HRoX?Y!njLeSEn& zD=y&dwIB)5L>Ky(2_In$6pI|18)LDpOxfo}NY??`lxB_gRMm#u-kdCAwk%>c>IZ3* zTA-ER3CQt-w4ofv8J84u_HH*joS@f6#*AF*?CQ^Zx|lWhHch z!sJL&(X1LJY~on;_{A8CjPlrSh9kG#D}3Ys8jv2oc7C@mVnSZ;;9zgwgb3YTceU2n zw_XyJZVCenMmg*1Zs+@srKB#MH-Bkr%EpTF@(P!!C@{)mywqrk3al)wFRXltR*Oit zj065?KX(}YC8U6VTeoyiek%XC9X(`4;bamalH69*h15*a8Yq~?K}X7vYX^!ZshM<$ z&u~v;V^87lXBVkE%}7vcF=1 z5l1Mo_I@a#;6+UFC{uR3hkybMYB>dPDese>rPK+Ezyzf)C=x(BnXSemBccb5d{Xa? z`0o=b`4c36{NoeV_0>}Ve`;!aO3Bi=>cqH`tV{sIr!qUgkN)nL=HE;M!bw_EYE@IW z#yw6Jcn&f}0+PZ)hDee+x<2F^_y7f80i8>tWgQAO1GWR?X?SH2 znPmV8(P|V=0B%b}hJ%qPpj;sxUACxC;js^J<0}B_^M!_`!}B`yN(Zk53a8;C+7stc zAU>i!MIXU3-f7{+knw_yrYcr4NpP|mNrIC#5s8#&G6thWtH(-1>`E`~Xn>u1DdXHt zI~`0&-L<2;c6?m9i{{DbF785bDE0(d<1_=%cgVJ&@1!E7?_`bAzQXv>j6wYo;l?;l z&JuDh7|eG3(voi^mIBPi2fK>!-{0H&4Yehlm(&{i+wbH4zJ=M_c(Cj5+p-INUHHrL z&{J+}udntC^Y;()tFCY7vr^JO?CH5FJq1V3(b+J!NCB|VENE#F1BJvxzfg87$D>)v zXdb26qq1|Ht?9Yri1amOw>LvYih4-;mmg*X+Ji)Y5QRhx{>ZT48Y7-oa``BW7aWog z)rF4oQa)^krA~kQn~uN4TqzuAj#4hn23h#fASHzFluV1T|Btsf0g$Vz^2X~`^^)HA zuHL(XK#)Z-D5#@|s0gSixPB@+pt!QhD53^gRb0k} z{|w`xIVuEPaR%+#&q0qUL1VJiH`3cdimCis>26%3_R{YEd&3o zhR!zzU(QJ2d&FOIJrIK)D@3iPcMl{3TVjZCTo74|d zD~qO5W=EwVhiY?cK&{N3QVeE;GL^@d;A#yWiQ*xRpiDYL{ggS1B>T)dLzjqJMkW<9 z1kvgaq-h8<{Mh{F*IiKvYOHCCR%TDH`0nDZQ**lJJZe&{!XZI^93k0N1Sxx7vrUPiis$O+X0Q*|;`z|- zu?23Rlb}xGNnIIyLZ1Zv5!x*$i~80PJRBrWaghaa!B}Z@q6hQVTG}CAbC67#JoW*; z0gqK}lho~6g|r6Ab1T8aYj727gYfhm)C^MxlZPhX{>QGFuKoD=@fUW@?Ani?e?xhy!O55H>`tQ7a-iz9%p-}qNqbnV z3UBZF>prAtZM4gvxdC5RR}08`yV@65o+s_JzvvkzpQBV~hf0=D#>}8!ffo2<%wSlw z2xJCzF#D(7d30$f{(JJ!xi3C+-4jn-&%&P9Uv<@0Gy7+p?Jqfa@VxyqKO5*7?-{_V z(9c;9Ia(iR1s6qBX4La>l38$Qd~Nq;mm){)sUd7fT+VURS4a6tjwEsvaL$WvBhFjB z{}HUPdjBJko&B=@rI@4jYf|eVw`-MoN5%7@-c0dud+D4!0zkcYcmBYN!Gtq0Gk(rF z<1^!vn@9V`<}2Dq8^_=MP9o%;H?G^TVcm`D%)Izj`TJ+aHx-5Qw{32o9&AW|h4Vbi zsrN2_e~{JHvHYMs_zF>t;At#;B=B~I0J!VVB`WtYWOk0V(o#ZBQtVM1eL$^BqgIJA zmnNubF;YvN+Ime1GNO4<#g11cBnOk|ifU8*m)!a4C9|``qZhy1S-0@t3(i@lq=WN- z_E$vng$ELo{+?ale9>!z{XRr&AFqUudz~Wh)qfqg1?59Y!Tb% z#qY4-A*DLbXz>#P4qo%g)O{0%nu+Pc>m`$X?T0`7;RVcd!JQp?j|HDxu0edb^b!u` zAU@o|1T#YS{uVDD9OqgF{$LV3-Ge{ihXEv6Cn2rrabM8lskrIl*{yXE>v(u)eG%yEEQ3 zv2Mg^Y6;oy%Io|6-If1>#>#!{uw2ntU7A@|Sk+oo(bCd5Rvij;#a8RL{Bh~DEIpTf zYZUiz{uTckg}&^;zv_nvAM@Z}@WVrIHt@{QkGwSnZ9q6G?Zrc+JwqYB=iBf7r}sV3 zNa!`*{eGMTzu1F6o&1&R^jVCc(M)6#jr8F5^}B{Ymggz3cDs;7jbYW#F6{Dt}_{ ztsKvN-u3LSO?>eF+8zr^ct&3f#;eC)j@SfL-{agu9=#WWq^I?1AIowzJy`OpCdp4LU`%2+Iw{Tp~eWmcH2#5Zbjr}=mc>igE z?@$Te8rY^vFeq^J5oo4(-Rx1Kk+hcZ%rSxmSVJEQ2gQO-hlkh)L7~Pxz2$f}gm*O9 za!meZ1|qRg$jtoJjVs`1qc0urkU4>C>j9aT;nm~Xe%wLxa;W8@X)>;*K0k+dQmjgp z8cifKDSM`=V=en*`z%$&h%o(~X`Zn28Bpi&h4nDrJ~F?5&qRAX-kv|Wrn;%Ou5-L7 zRJx&g+d?AI+g(y0F3MQ9rW#G3`sd3+`BTw~#){m5;jxI*+tv3#U+!u1+qMr*4R*w$ zrSZDrmUw&YRhObv+P;FyNKtjTv$1^r(CF}ZQEgM%QcG7sYnSt}navFin`f{*pmSLt z;d_q*0KSLcgiGIdfu|`P9qcTed{5yI5DvX$34Dfq;z!x5n{@+B%@kQ6vto7$R~Gc6 zhZ32wz%2c`d`Ih2Uu$_7Pypk%9zWgq3Bk442gSVuhY5&x#6qC#h$c0g()&=aDZL9E z#=l7f4g|TsAiOE(=Zvk^Xa2yJ>`+N*ZF6ybOL=Q|PCTowyriipK6qJqNwvW&Fp{CB*Jtt2hpWWOoi?)T#}_(Kn$M+KiDsh59^ z=(Y`DmdIW$f}+wQgvpSc0Fc91>ya^@Wvk`Js7}RvZSIX_WLF_&BN>0buyhhU!w_{N zX^Y@2Kp3*dxMjCBBPzCG6ClixhYMuhzD@{A=Lq5Y_5Q<$_n+IHwy==aeOhH#tgI~7 zRk?BFM(4nbd&hF$am5wyILK!AO9wZ3ta#_yNMwxvsAeHj2 z09R=cAHz`9)k23L4P?4Z%3%vMB?&Yd2AVk;OTm<{iRA}Au&`mn!j(P|W@Oi$-_Vc`q9qn%Pw5ISE6jY#}+EmDyQ1c zjvt6_j$dBXRC>lgm`K~;S5bNOjEgs1`&NmzJ#)HoZA)v94noCuHkdNcI20>mt&7x541>+5Us zN@~NUIciQhf6i%G+ge`HHqyY|?ctR+kmB_VzIEsmok!R032z;^p462HhpvR{&8o`% z+Jye7E9v)s=6w(QQFJAR|J(;J6J1H+PZ2KbN;)Wnh-+g_>afS>!dO=u1!TCiT~|L_ z<2k6tP_R?F#+YMIm>6@$o@be5WSkoIv95;iP&NrwFpA2scNK2%OY14N9ep4L_y} zldeieUoaSik>Ct1#6dlF>g(W!(kA1coI!ixl*Zf;Eg$&6iuSOOR4mM zhI3`n3yvOjvhP5@ceFrGOis&Qljo7wI|!d6uaDpbbfs|6m4QE)1P8A-@CW>G(u0BD zp9BZ5H}HF0_^~C-^J&aKH=w)!G~aXWc|0Q0Jy?+6C`Y?RA zOoq9RhYK3HuE&YiN0zepQ?h~lJYB?qjT-TLY&Bj&rMqU;lC@U~Yv*SO7J&B_91T3e zHO;kRBA3R;&Xbe0oga!to_L!qozED*%lLGSA#Rq`XN8AmYRd~*xEH_N^`t#*@bm@2 zQ(9mVc?cQ66SD}a5Pg9>>>Rk?)lHuR5(;8p@Rbd{_*&;#mdDe7EqNN3hR8#~osDN< z+@l-2LQLfeeoBEK#-1?Ti?%1-nQ;C>5f*irB@dJF6HQ8zJ%3?g;pYp(qhD=tJ>Dhe z4I1S>e`WBR^sJxoC92}4dnDZ~jj3 zk!;OJMLsXMnafvG%9I#mNBq74QCeI+BKfFHx+U(<{Yzrm**QG=*p?fr(S|ad0mCf3)f*@4+owW{48L;Ykcq@Ivonf z^&5Qfqk-=y-VHp+^B@z8cs}jGglGPPi-(lcCd-dp8NhQSdH{F|3l&Xf&B~WK-e2q$ zsG+#X(l@8GJcnW|8FRqVF$dq`{M>hW-gT5KJd#181x{RHqDG5&-U|7$aMuSuaF>G^ zzq=q~e$+SB=ZsA!-iO!UtE@Q-*n3{=iXz|#bP`g$DV+9$!XI$q$I_u2Q12(4bb@nP ztcP4LzCpeI3%njYAODFiQ2}H6N?Q1}rnvNybRfAHyc}=GXt7fD)Yb|N15KwzQ zh-b29FtmmswT2QHrYXt-&Mh-zXDzJI78Z7&J}?+eGacmA9hMi1L&)+eP0#1V&=@PIctwOyi zkt2NeFXQVgx*E%|ch*$IdLlc{B6n~e^ak(E=xlHctex1@LIbF-N*ds*d2s5n3V$#O zPTElT1Ae&Zp9;S}2~OKm;rDp(CD`($CBkWK=pI}-p8LG(Y5UVg(d!>gx<1>x|Cf`l zCy&tQaxcak=fSE?EUnhG5e3v&-43GHZzrg(DAbwoucduxIj+)?`;<-nbzXr@KEyyoS)$EZP!A%bUe%ve( z2avObC)2k7dOvN~h+Nj|{j^;Zq^_>_hZyyq+aq?o?u943y}&)??h)AW_UZDy7W&V8 z-}{;OJ;HgGsQ3GM5}aoq3V(`l?4f+zhi4vnh+^9ixSc7H8%_R5J60t0h^D{Uh>T`> z>i3x1r-x;r|$ zPjA~84z=uTS=U@r(!8#vV?%MMe7bGRmOu4&71xyIXROPtZ0zmXwEKeHo8;FYaVUYK ziNtq0T6ei*)!jYN#^a35FY=BvFlf-$R&1tj;mc(JfVLO| zHs{Lq^d08PeUJ&t`yA8zD4h3E_ydH4pV9Z0i?!VrcpuL>wg(n~Hdoha<+8QtZCkA5mYftN(eR0!3R(110!`Oy`s%X)Un(=|yMQ3EWXD8qD9feG#54LtU zx3zbbRu^W4a?|qb2ilrPnoByGdvn4ixos^z`214HJ5BMeh3s2hi~GGbO6OXbHR77a zWsT-Q8xc7>+3T*1ky2JeN--&H$d9X*x+aARCattgWsmeF*)`_U$?S+`>B(zYAW3+CDdI|7qsIGY1xiYNjVQRZfjeKbe@6`PN`h ztw-&bk-$GPT{SswJ7pzOqOp28y( z0fNvYRt9Nm#2S4g4CrOJI+LfEJi)+Tnwva&rIUk&b`TH+WZ=|V13D=l#u%w`|ZEnIPiw?w@sH-U48fT+r~c!asW@(IsFOyGzeW()p&iRb zY$-U6d94ucKmu`%!wPfQn`g{K@of1E`WMAYTy?>myq~$?i^X|#cI(zzbApMEQr|u( z2fe657Lqed?*x-Hascrr&xstaz`P-cjXz)Ew6hfMo);*b@>$^z_^%gPp>X#cL9VB+ zXs*AE9JD#goJ zlJLc|IfZ}0!`HLHEwG#F1FvUsx8z)alyev3EwBhQ?oAu4X3Zxi#MPLpt7$NrXe_Cs zl?l41dIjbPcn$bM9dd4^kqsjqQV}%RAKU`N$@483q@1xSm|xYD=rzs~KPrwGTm-j> zo%Es=w7e$FW!J>_y+6C(BTc0>diVQF61>`j z|1}BT@4=6`aCyGF=J60|D)sr^_t3LJ_l)2rt;M}xS}+Mt`5-ixmPR;er4+wdzS9XkE z#degwX4euI8>Z%o#fCq@ryo;cW+Kc1ywi$ctf)|~N_06$w;W0|YDOX&t{wi0CipAr z#9zV7m|!iTtkwhgRCNpY8X z7oPXJD|%-88uB)yf?a3ub(f$2^7Ai8dAe}4W~jZ|@w^2-hr@Ki-5>eP(JB=7jD}It z4iofnM;eU)kP&(&38z>L>YYyxrO*x@W zmFLl*vFt6@?V$!adYU=MVmCA<&#Nt8CM&^wDjH+tOj>hCd6gy>c}aD~Str?bv7&Pq z7QTGN<(FUa4f+u!aiOge3M-hTU?bC+E3k_#^R>15*1f(JAHV1{$O{JjtJ zStGPYr#5Jip%uWF!pTz<{`(|2@uKkGCBcbbg+G%7r(99^uan@!yTYG#;ov1}MBWfi zF4F585BG##|6BKZ8Nd5n${YXnls9_)uXsK7%0BFsTC87$-EFE@#PFm@nFw|7h#Ho{ zIgFH1OA1>CX~EzU==LIgV5lteV3Hduf=`uSM6i%7YQti@#n?MoNTha~f2#0uMynpG#(_k8Tu;TTYiznhc!yA_^BIQGq2_=O_iQH*axd@B1abqLqBF&Zfq zz^tJ|hXyEDzK{WZoR+(?V7e+;Q&V#>Stmg=>=C)<#v1w(>ow{&O2?df`kmtN5}+EkqQMsd?LF}heLQq=;3qp{;$S; z!g3$I{)epLcl6pkMT5K4=(ynDl^IPO9poqnYL09<8*BGDXoN{b$qfB zwZjOVY~j*wgbtm|nklZaI?OL%a4Vul_!75HYy{%3WqjmZvRwo(^ClKzm z^soQwY;g*zX5d3ge0FAL=Epj3J2g0~F4^u5fp+YkU*&LAd3;dOhh;um2@J zrM|ELKLhj;2=2?&@L<0|m*b{X{YWuAtOzs4i7DA(ol(XWD;fo9oUy%h#Oj$7bIMsM zR%&1s{a0H*m`b^?I(AUhne3!tMuXj@$J*m4KH869rbu+nPOTX`FzT#{El0 zKVVd3T@W{+yOh+c?|37g*i#Y~VE<^(g<|hrEk4VWZ`9Ftkas=ayI%7twG_>m(KA_A zYS+*V)(QWqUJ~JA-1JN@Wit`qV46P^?CcNN$~YAEAX`x1YTg*6P`@}{_Fkp-$eb!T<`kK zNaszAJrQ_DNK8;e|2?9Ub3HOct{+DBo#;t~r}|+S;}Wso^ZYQfpG23J`+0sCg;SST zxa)_37AAg}LfF8_RjKl9V74K!KD!ht;@@FRF)YC{GAD&nsKjXZc`%@|1YH^O`BHm50{3} z_ZmN(d{5!ObK#JwO(IhXr>q!|`*Z@2b;4JB^ghS*J__f36#hHHAwxQ0bJoH3nPtXS zIif!{`(#KHP0Xf%-3)#SF`<5#3HV_qFgO&~gC8b2ZiEGmSX2wX8NAa{?zMPjJTjwH zn#>-eY#9Y2(^I@MekY9_cKgfJqUf6>`}zDj4&pg%W^CT#g^gpa`zKCYShsmw|4du) z&& z_1&X+fb@+woc@yZK6@yNHB=l^7AcOO@o=9`92+>-iu#Tdn7~?1Lw?rtcl-Zd0w)qxoVCi%@3;t02*>$v_wBeJw}- zy6v3ZOE~2Z#(#OcvyiwG9;Tnp&HZ!|%Z#kn_zIh&oT4pSe#q5v3-Bot(Md=lnBA^P;M^V>R@MpaicP#>@JK)#~6YG6|G&hK>qgtGM}0&TP@gGOC7q zdxxtsmNLrQ8;hD7!j(m~fKV{EB7bIAR{>1Y)B6`j>gq-o`kk8-7mw_n9jVT(u8kBH z7qM_qYdp5LDlHUj*>=fRhrGgDSDrg5Ufgph#TWHCgo$J6?E^}v}sTG zirF7I7bohU*mlXb=4SlDY|UKviZ1qNOy26Qq&>_DIuzl_wCBIxPkVbs7t`xqU5q%} zE4rB8-_^wwmrod6W~PN4=t?f`Mb{#ZGegfG2cAfR_qz89EgN_Yyk$^y4eVjxycEto zsBm`=Dx7;z;qD$>c^`MaMY#Vax&Kho{T0spE8M+*5%9T5_&MPgpL#u?tJk~FU3vfH z=eqZ|dt9$4ZR&l5Hi6F`jI%|?Ih-_3g_B1q{CD`2_*Xc6bP9i(aPU~>rXc1An&lg5 z(&_1q%M%QTwxJ^_F?K0OiH3*U&?;M}%%-R~(yr_>Nl{@|MS7qNy8$T-wCLR;?k1{) zP43THcvVGbtPF`NS>b4B-IkiP%x?t0o%qVN8bh=E{8DMGs$gL0L!46(0Yq9OG#wHi z-Iz0|K+A5UAh$iZQsj^ER*1GkM*|&ztbj;O0Aa3Zh*=iJ;div_>o0pak>&)mKISZJ z3BGLkom+xuE&oFCxed?GhV07>%=5{~{iIX$D^rfo>Q_k~X*?P&J@M!gI*jqN&H3_D z;%R6^=wN*@H6ZG9Yl9_tUS427h32XGt*|;gszVe2YFqN4j$!x=ex&L~OC(DaXzPJW zj}AKcheMi@vQRWwlIU=9Q26=L#8rR&tMi&~Y;i70b1vC3m$-Q-akJt9)QTM8haoS{ z;Nen-H0O2cDy8ywifFN^;LH#B67ES|lE)k+@{YQvDX`fu9nToJsgcw^wDJP&9x<(Ao(ft3RIdV;K9yF6|ofkxa*&)6C!;P#~QskRL&MnvsVw0(D>lR2^Z@cPlx_}a$AbI-MQv^U=N(P%s#{b(?4)A~cX zY3R6DzqYM%vLzOdHQ)BDhWhgQ*avhkU5$ejqDwpI~lipJ3fn?#3ji zROLpGcF8qq8WKwksb-P@dyX<+Ud)ZABIL?sWR|asG&x&$%_T0Lb1wT#UEOs5+s^n{ zV)Bt|uYJV1?3~lz3@!p1mzp+0(-}aB!zdWUV6n;io#ZVVA?1$N@D@8-Bcd!IrJP?9 z(`Cd=WaTlpjjm!IpXZ_P-Vh4h34nvL(VCK*iB~mL*m@xhBruf-^anIBtcsB_!|3^j z_z%+#2*teFP6>Vm1Xq>CoXou&7DftN;`zg;P3)cDJGo`;ns8}*{@NXrd*^@o%YPou zNXzQG_Xqgz-m04Pw2a|@{v}ibCoqP6|15HG+5)JRQ3STj(_|MX#%i}Husht%m?(tA z((H&r(7IN5ufr;8Uy4TTD>_j8?h<3V_xBl2tH;W;!i<&ggwfh>v|`)H=E?q^x4q%4 z;pz2r;kB`IcD(p-U+2KkD{=>Vdg85Jjrq;3Q**h|)|SS2|JwNtJ!^B))=czn*&}O> zxD%oMJkD!nUitB8-%iw=0b3?)V_jA1u>vytU87DxWWlSN&VFx1^V@BwqWxQYw$b%|Dmlf2d%D^zFyD9rodgv$C2ZPhvT$Wwqc zL0_e^*o6xhy=~{^MODS6H3duS)>Xwz&b|mN;jzcQT2+>w7AmWH*K|$fwx=*3a6jk& zVm_sTB`O$NQP`bNN(WPaSzsBy-+9DRgqo_%I>Fp4!V(~*soDrHVEc)x$%1gf8fVYK zms%T2x+3+{B?Ft!oOm~j9`CsJ#)gst!SiO|xe((jB%jY&A%{BPyzSf>uKWz2!Z zy_m8~zd3=6=n2tI_bv`kK2{@}E&bZDa@lhXO=KxeprXB@30;5G9ETQN26Fz(0{-VB zFF*g(Q?A~?oazjmPZAan2cVoiR_f1A24^^wlC%ZQRNl`^89W+%9{H(vS)=U$=NtF~v_EQK4ax zWRzM9808hC0c#uiwOrXUgS=u;T`W{~Rqc)7oz{c^u4JRrPZ$w3K-J?-!UkHv_BY9p z2l0*d0$YpMLBO!xR-~tdr56-@3?>IsO7Ss~Rh2cc3(BKmJ#k}|GO1wrP_(fzT3l0{ zon2hBCsfo~Th|>aER1y5)pizz=2HOInM;fDwc&U?5s&9Lj&<+r9&3c6R988E-uCV1 zjaSyi{`g1#U(3h5tK>UauQAX=4WdahpxBXKm!P{)2gnI5(r!)OS1I&ij$Z6i7~2o~ z$}Ql+C=W{M9Hceiz?~Hw@ecy|nZ(%(&n)bk!}gve`931gU%(*#8_zEdT$0KgQ_>bw zk}@o_Z8G9aY{g`potzh?%*rw+w=)K7RzC}eKl%ElXnCtIC^5AS(Pba~>eo1ks)-5X{1QaBgQ&^1D3_ z)P>^$8hTugR*kj}55Yse$6hu}A8!#t|sIeaU- zDZAYcJ3u|To)k=BY{u(hIjva@tHD^6+&akMby5umZVYHJY%fE#OS5j7-|0@C&7vrl zz?r2wkLy_Aw7pWAjAngve0^IucyNCHPzZC1w~V!wm$!|zlt;^Ra`4N%=9Uc~oCh2K zkC}Y~rQ!AGFD{Y~Q#2sfE{VDsGJoJG-wXyH)a52UWvbRFzyriOYCS<{Uz>riegtfS0}y$e=E*7fsg!S7X&A9U;0a~=K8z- z60X0M2j+(M3059x#ISmn)n5XEe+)Mifqy36umxL?EvK$iC|ecsW9wHbG`?A_HMhdA z!V)BCmzKZ2v&)&?wKZ|xR_84@wKx2FZt|Nqd}sN($KUt9Q07~2{07bdfVX|X6LSkP zBsSf_3{2%BOpeA`c;(k zIphV`VIhH)5SkwH4ZBp4cCzuR4wlkKRIWF{(NYbGzzRy#_d~Ig@esF1GaJcM&ZEmG zT`cIC#!jEmlR`7+?p|6`SX7cz7RjAo+&8m0vwz2yyt=ZS(&BZ$Rn7#J8OR`AKv%q$6Ivw7w$oM_vVz!w4Bmlm+{tlYWZ46*-I`p3NO&JM6`vClNQs|DqZn&$w~gy_)+aM0cxGx%<-i*!%@Ei?j1*9k_aG#+m33r{;rMSjta#YWX|y!~%|cZ~9iSZF$x?_^svdgLY+1 z%rOW~0{R&4LbCv7 z7dRDL`O4jVROoZY6vY;nUi+RGy}78axV)}---e00uA-N`mf_YUwx?s&q4f0n)^E+l znxA#)42mP@jAL11yo+k2?jB8v7_l0)hd$MbAj2LUVY8Rzp~ z&8uh(mkl)5Plcmp+0F$^!HX|{$pk`4`dk_N-URhkE(+|gKq zEC-hg_v3Ielpkt$-gaK>zxTbZH5h-*?k969>RW5p&+S9N_ZN@6%$ZyML|wd$sZ~|S zo3uUvlATO zGg4JGvS)m0Y%kyJ9a{=+pPt^1v$*N)V`<%653gHycx!jszJ0#;!5wSP+`PE?%r)i( z_{4g`KWWx$P}XY!@|nGebEusi$0uOdD|xgO&V2#*q9eX^g}7%O(1#vZc(%cH1+WSiKk zB2}Q#Yf;<NEy{T<{_ds|lKRVpfxgoN&`Vrxz6d!@W5e@_F$|ng45&s4D8TS_^DC$?G=_IDZkWh;P)ByEWQE%5>QMOXTTeW z!m(g|cVa9BtaQ8viU9YL%lnKKZl9F0zA2bpeZiA`2D0!4?!ucl^%w#ufD2!Mg)dmK z@W!{WCegBZG5&4Y;+u+)0G1zCsKjB?w*cMxB)$bZE2~SozJ*lXTBVOqw}v|*HBYwa zmYbe^6E*99|2HbuZ$Kzt;wiso4V;I(^;draDiS376Ed*nu3jag@DaS;MEX(mEK!N39FybkokxuAD# zvj4#L1Thw?ox$HQz5`3Qit)0{v3+{wxIojR&>PF3H^u`yY5XVq3!+qyqrO;ZYfRHf zCZ>w;%6trH8^Dy>28_GY$Vf4lHN8u#R%tC*)1C>Yf>YMJsb7-G8@i&q7Ju=J#a+=Z zv2z<25*Af2d-0H-vdc^2?Ju8ZLE?#b5D z;I_rZZNbvk4P9Lu+e%B@Hg=Uq%W`tc=pvBU+?u&7&Osg~bh3k&ZLX9ryuU)8_ z%WvPl;f@X4+w;9Qf(vA`1%sfmQ}YqnsrwZ(QIc2s2vmbu=_4Qspf7@SK~@vHLN?1v z9|6L9lYIoulZyqBiUa!&pYeyK%P%TzubptNzL7+Fh$#Vvv=;l5bAcv&syt( z7Y~j^M<8P%b2Z+Cbz>aNHzQ>GW+D!SXQBrF&~NcEuGe@JUe9~WBs22mqKv|$M5!XX^++f{Et}HNQV?8iQx+AoeAha3Mj(bc6 zjYXm(TIVD@;@U?NlXwR8KgN{gu_E=#oWPXhTfbFfD-j0qZH8;$ne>nT252~jA7l}y zLvjQIuXq3G#+wQGL;XQy?>-HLx+2TYiJJtKHDVul=}Q^R@!*3CkB|Ri`sQ(}78vtI zlAp)e2-M6&f|gW|aq*JkfAo)8s4hZ|6X6k+d-y~br23-D@ybLl#9?tNF9VIg9zQY_ z*2I{QKNv2j0YUI(<7#Y3qx*?}3UO?#3t|eULPvz-YHF#VSdJ{5+2`~Gqr7Ffq3g=x z$<9O5gY}X3Az(ugD7k+S=`8~ng5F48-u0wUg+D|%@MX>& z)nDuO6?5@r7{8?>%a5of+ZY!&GAaz*Y4&CqJc{i|lpncgY?+$JR?FjfCf(!YhRxZW z@wI!_*V9Ss>%sODapDe4myU!F<A4ENZ%di&FQ&t ziB(YeLxf}AjQ!aK+HMRy#F!$Dm2&58PRQ-N&2g0l7#-9CQlj0G+-h|GSZ#|I9~z4Z zDP^kKiekMN>nSVa1K9YEUX)CUuw(uLDEu})M%XW$1tZfp(k z9W3ZV2+iuzHBCdazr=s3(KS~;0vu{=4RK5VB5*4~wT!vBL}G3_8HZ4`!sl}eF*ik+ zpvK%#c5As#f6PshdlGkABA&p z?_73zU;7CHai*8nyisXJ?fEAmd$-VCmwbL`_@Rv=^lSRZPpzj3Y%)-!3&CB^7w=*V zg5UvksbJAq;Qvez?4Mb)&BWW>!+0APF0vsa>vEqT&b~6{`Y#!{#OKKM4RZaxgkvIS zV=siU7b+RI!%*mIkfysAQcgWn-1|wwg_Lf&1kKPjhX1E4rKigY2kVZi^_DK%fWk=6 z{-Rxugz4~vBRPJ@`EIzO2(dc;_?^kQP)+S^PbG!#Ji<94dP>eNBdj}Hzd{bA$gY$* zCHYo3CsT9X^VISfntK}dksAT0sZ;(o@;r8}!``Kps)Sx?=BcjR2#9&uoaXSvGkLxy zUgRy zRjG9|{+GL$-a>n7&6qCZNNjTXR9Ub$AK_hzb=#MgwqN;@oYI=YlCG)(=L6lnz1>|K zO9to8nSRSXySHuI<2*Ds|MHz{3rh+%##>rj;$M?}>c)H&uuf>s3j04r=Sv}SzeX+V zYB>~zO+X?X;rY_lL=c9FrNUVkrm57;ljbZ?Eu-sbR3n;IBCM7qMC8CT5@W>xRzz9J zS~2E=YgLRmqmR&fcKe#pYPLDyCm+4POqq<{D08wS3vqw7mkV|DI3)3+1FP{=-LBQT zp4C8IwXCO3iYlJAM9^t@p$Ci|v#U2WfAhR9-OcmPVOg_a+7Esp3--%jdJCm-CX<+R zUf|VqhbQMGoP4G!N~zT?xa?RdAI=O1MSsw}f;v9w+KJTP|e&O@7I*5Jd z;y`{#{}8Ez%NRJLGeONzIKvof0x_-5)F{VGJaSVpT>S#wsvo+RD#SWUGL@rkM_F_r?sT|qC*0Sm#l-g)r=}L2w#8hxIY4}qp5o6e zEM;UYEjaI5SXx@}=7>1E;Hxl4jhkY8LX7@~0;qpHHgkDnimbDe45kREMB{=ftf<>6 zD%?^MF>H(UF7!~88se+6u7o5OQFhW^mvaq(kR zSj$mUSgc_wHpI`5L1x!bhs%O77Y+3zD0jl?4*@A@U)?dkkhU|O@P>{->ynEjlIk@1zWQXF9cMwkwB(;Fo16^!%h ztG$t4ef7flxI5bMw-R^IhvAP;!|6EAB%rNJHi3&fvxzP43_G@f6g|psF)%V!E99_m z4#g8)z`2I8u?ZwAY|%8(uGv&Q%0Q9P7__MZCsu-<;ezkXFy`Vsw{Fj9+Q`y@2ktqz zZ9HxL)&uumziaLv78n1)$w<6(_wJW2e+60;=?Xe8;{2$4b^~B_|1Fp+;}0tWS8*S$ z>ch8FGa7^i_@`C??MRQb3&_&8<96a)F2Ealnkjh6sEN}IBcwP{E!ED6R>(wAg&8p* zC{OQ;3FKU#Ignm6yVea0+%@Nq3pBBAa&9W|j1__FskEMCJ}EI`{xgYu%tv*fe99Y~ zTuTgt6i(ZYKX^c);j97trJ4q}2Fs;oz%fI~SYTd@c&vnZfprWv$jrh}6nU1!gNzTR z7k5>T-?4DUc-z3j!qa6n83%UXc=u=areA-3dR;Yx9%T(R?vZQQuWN|kTwCdDBEBZX zqkw)7!JcCbbWULR-xjOwhC@d##S?0_Y3u zt(gJZ<{0&FjNM_#Am}fm*RgNk*X25DF6hN7J;t7dA%7Fs4`sD{e9LTnYnvHO(9)y- zSV6RlCOU$etRrYcCLU7@#i17B47NA|QW&1#HXU>GT#ncIn+NjRm~2H$GWxWjOM$(J zv$niUN;-M1$&#A0u4qq|)T8xmHZ%mjqCf_E(xRScNd7*{{Tr+5t~vWrX9WHtf5W0j ztzhQqs(<9ym?p1NIBNhXobE}&;eKST7u^3E`CDR*n1>r2#RbT-rkfJ$q3{I|`tkjf z*87{p`am?R`zNn-)@_w=r1w3Jcq9ikodQz3GmrWFEA=eHVeB*PDTeY%&^cI2uE%_G zxmj-n;4}D<&H8%0;bzr(3rtdE%+eZc$r5<+5U$0t5is80$0(_XA6`1!x!!3*1q`W$ zf${Asd{$7oe_Fv0-7$iSxt5mj{Z7vdca4^k6~ z9)rS}B!f#?7Eg|3U$1Y{Q4*s94oTQ(7(eUhknH3LS|ZsqF>aZG#=GN$8cFxRyE$Cr z9*A;Aa!R^uF#MvW*X=LfykW1Nj3)j+goFJ_PfPc{`X!`^eOL#7&Di8xtfZ|5p^fBP zucR%7Kg+ERe32j3;vA)oy4lJcF4OhO;>sFt8bibuXKeLC^t3`yXewVTG|o1ee~CIA z$AfG_&eT$r4>HAvwF7y7*@sG7C)%UkQiIr>|Gd#X9|E|2qP6rF&R8WYK1s>p+M$lB zd+xa79LKRlqs+C!FnG23lzk{GNJOa=vg% zbweX=EHX@jo&{K&!O+@C{gE1iFaF5;v2BH%u9G*`G09W4qhNxW#uZrw=SCD_S^WSM z$}PBp`-|nFL73NJLUN~L?wfjj!?WXd_|V(s>^LfMX+;|OM38R7;? zbdaeO2#$)p_#}Onp1crQVeB0{{S(*`)8Zo~-sKeh zm?}z>{Fv9N1ZCVUWvZ6%rr#ymp(zm=5?A8RJ1MU5qc=T!_?j2}Ls4y6d2P|+hKbtl z;xi8;Y;aB@8=q;c#R+qL%RkRGbw16=#^A98elhl#K21b#)tcyIYMK5jl_!sI^gB6I zBOFz0)q09$c$Df8We}2aEbOZBk8WitFK4C5={=HFTw4+MsKqVeL_hV!j4v+ z8=_)#9fXsibH349CZu1jEvuAVpu1hnPKLLcH>*ZDMMdQkGe+E>&NGXDU6L3#sa7qK zs~YEqxDd0Z^nHGq9M1K9GLzE!K2vsfO3+x!&bH3bl%1{N;xcvr&SpOcmZ75}3SXz} z=WHxHqer;xx{o=p)EJ}M8x|LDSZw4Pzw(t_<3>Bz6^kqT7SW^d`BG#e8fK+lo-$kG zA+qcHLj0fCVPl&0`1BNQ`?^)B7*6x_3Pw+S&uS_5Z786~l~Vh17W z0kTW%AP|p<*@fC2L2SEJs&^}hxW3SqmA+6r73=+SBfFxWFEnbfuf9;~P5!fT*B8pf z9;r_s^6J-@IZ~?LU%Zs{N)z8g<+(%u7C?_vp4sZw7l+N(2H=+-ZF z=gv6b;H|QLvyjW|AJU{TS;@9^%Ko%-r0if!03`(Fsmcm!HL*ujyJ>>V5Fe>e^_HX^ z7CoX~P8Vs-OV9WB_=woXAi1?|YvWt9^1tBk=V3d5RQ0yjroSCiz?`s(!M+f6ku9>G zt}dc*>g5V|?Olc6<-y%ratf!uuGhPEu+*gzT?yV5FfsEtUeBZcp6shku|O%$QUo-fmzIL>Qg!NQ@X18HaOIq>AapSfdC+U}(T zk9~H>*0;a^{cm^9aduz*_P1Z1xHa+Q3{gJ{O6W2W=8AMs9u1cWSIQ918-!J=0+;fqQuYb8EYz~3q^faoVBWB@C6GZJ z)Bp!H1_wnZZ(Y2BUR1LYI$iuHh+a^@@3? z?DLd-C*uT_1330YVhuC+`gGpPjkT8$-G8~zEcu!bjS^O*Im04vsCqs&{lpx(-k4(r={XY{%wK;dWgV@{9`r}^ z(K_E4d&nq64mTac%?95v6Kk6TUrbx5W3IQ@ z87=7)y~+Ii=Q=y!8AI5MInjq$5qKSwx0C0TqB^C_$?8&!5)GJHAlWz_E0z&>06*gD zyaI2`l=#M-iOaEgpp0l@DUMN#k|L67%r)D)0VBb~36cOFZjpT3-;;KB|Duo}J$rm$JGgn%WX$^69_N(G#g&kEv%YuiB zI<9f~u|iXFCAMK68qphw7WN1U@7OXsyM;gPu~<8QigI&{@Z(IKePI9DXYW67cK^EG z4ZZ8+*G2i+IobL0OV;ay&YW`})+?JeCRjotSNEAvSc=&9&jEF{KyHjJ3E>@;2y8o< z1GU+7`IHPr{LhHJ$ZSUBSby1_FTJ4h^69gjxjrXrxMM@&!9E$sM=*}Nf!k{4;qxG$ z$(625OR*vi3lm&c$&-R0`w+gUo;@0p+B30|l#W{gpV2|&VcYCWgqjnz1x~+ncjC8i ztc^w^vDlg+oZ)xXjc*)mJMFy1!S z3Cf3C&|kkRCHK~NHZb0!FqGY9-Yq2SNdOk`6MQ*26;Y~11c*|`&bA0jwJW%gg+}Kf zI5(w$7fZl+afV0;bgJS_vAWup{Pv=;zLB}ENJHP&jl<(5-T5uCc=OiIU_)&+)5hk9=1Cpvt6ypqh5$uF&zUB$+Y% zBIewe*&%sCEg`(Be^)6zuHHNY3<*E6*6X=pBpw= zj4{^>j0e#a0EUaeR_TYW5_YGN%ubxHsrjn5{>r7;P)l`YT3&m+e|qa=thF_UA7^Uo zRLksWcR{$VqZh^CBV&VWXL=f9>9Gd+#l7$`(8yhw$C|(xe`SfBbt%JmKkORV8ECau zsJI~dG-y-REO!P06#D6VZWwY3ug7gCre8S#gEtiT5ODQn?6=B54B4KDuLziW*j6EH z^muQIZeX9uKI_VH^Gq{xa@=--Wn#ry37ZT^->DuzoEl7`6d*%p$a^JPSIXlzGp9S& z0A?KaHYbxPosQZ0&RAX3^u)w;Q(a@n{4DzZ%*30UA)$=P*#!UAuBhqiH!W;keoj_nc$Q?`);hoSA`x< zC_4>@v@^BTO_eMlmIq`ccB++~8iwwdovh)ivJ?Hajw3s*(q+_I&g7T7+h|aR7k1si$iB%;h)kVrqjQt*r z{p7OK&cNc;<$1;6dFoqUO?C>`_#0$r#>qq{WhWNmF3jUZvU9Z=tdyPoB`aj-#MGsf zoaD710wTx> zD`jWX-zqzQaT3vq_574`*trwBklLYdq%QQr?a&(Q&41b#H>znxFnyQEQ?_Au!e{tvjzP|RccKvl` zes&g3Rs7;QI)Pi93!U4cXNs-Lv=P6pnqqGoe~`>mc@^z#YMZnak^l}*|s+Ist`S_Ar^au28TOr>CX2#5#Gd z5ri#rq1c1j7#i$VhMK9p%5Z7h-`t!~+Syxb(GyD3(-ZtHUvPnHVy3$=-xKMNS7l`6w8lC|XGUuq8*B079NxUXadNONzr3ZTqtYpjtnKOF*i&DfQ(Z5= z$U{C0`uHT~u_iE~X{}snckiZnF^u^Q{Ht$*db(SoUyRlMvQW&q;0?onxO_5r)#N2Z ziC=)^h^t$%?b`tU0& zZB0u<+weCk(J8J{e!agsoE5H?UqUyx0^h_%SwJ##%1s6fdxQA*y< z3q9&iXV(j|S<6q^W>vq_|HNiJj!?}wC7Fs%al5nqgfi96!Je_W`v0lP)ZtW_+UJT? z$Wx5r)4=PAAF?KsT24l*8XOU@kFxJj?r&v{IAJUDA7LV zF0};zr?NET@Ag0mZJWza3?28ma6a9FH7nB<+%tGNL=%E zg)E))iBj@%tjTQ{--%?Y9aZX@R72m=UALMnO-A<%l%*NR6{W!I?HD)r+{tC>>Z5mM z>ECL*o=BEvoJ^Dw|DcagVjg0bU$NyZGo=B47T(uaA`Yg`+n}h#;>_TV>@xXcbof-VEfW{0IAN$LypFffG`2+a= zV@colKF9s%R=NL^?)T+$e*Xta-}gR0&hvlfeqTOMeSYfa$@h0ZFYxaF!=&fCpC51j zKTmo-@%JA|-}gR04t{@;^8IA|Kb7=(Dt?kbPknys=l_{BKJWA6jPI9xKKMcxK3@!e z0}Q2&in?|KgpTg82&WtW;ZGO|(QF?^9QSZZ*ok$WHoql(=f^fBp4#m^S^mw(9&`Rr z`t|fq zAZUEJ838|;;Rz?4dAk2mUy&y{zfP36PvWy`fvXu8SB^Wcdmwokt3g~fHBUIrnbE;$ zc(~gc4m+dMw3=yJbPd6`%K?`i$Khv74F91sN6Skt<#fq-^5eXk9zm51?`Oock6h4 z_qF3CbHl!43Z(A5S{?2siAT(B2XyyUc((;bRN=m(?tOiDu;bPHVzg1Sqd%;}eZ;vW z>AsInC!TV5wG&vxIx-JR=!K!g{=S-idSRb4-QFnD&Q0*VXMie%Rc-Sl(dfv0+gtPz&aY&E`8A#O^_?~O)8X#fuI`!cu%Qa-B);c6_W9%?)q(?+ zvoW`NO&Jiu4C7JA3E3o<>Hypay8|%;*ecHtAl~zo_fAG*rS3rH{6f(3XN!MmgIaXQkS_KSgTG+b9{t|iKK{8EL{&5veuCsiN_ z`ryDT#+;Vw!PctgO^3(VpEKXOe?Go(rf&GOuI|$YYl|Zt)$O75>EO}n^iZ^XXw!kw znOE%WYTt43rb9P$_O`ut=g9uahWPaUq0Y_gdc%#*$R$n$TO6ZFnqOqa{JI%u*6oWk zi)dL+v30vKt1g*e1n*ED1oNYg%l@3yX=(pbv&csYc814~qq>kddg#7%my0SAln!Sa z18=YHh!ocjp4Q!U+Hl>>#`ygH*7Q-QtYB!ihK^ykx{_4EL_v+&Q#= zI^Hn3e`M!t+j={1IJD{F9qnB^UokU!VAD`}6v&YI9s9A99ej=C(^-`Ywp@W|JiC%TpH{^fJoc|F|crNBYEb~8==PGvo zr_Vid4gceF(}2(K1D|ie2>9Fr?y^h>VC_!8;j`l*U)|{xJOXS}VPns5##cJXXkhnL zmTbwwPd0vdj+KwsJbGa;Z!PR0?V4nDWa87o^*=fv|I%*#k&Fdv^?j_>75@*%!gcVE z1uL)rxkI_-KR{9?pn(~W1_~G#m*L@rX~CodzUG)$;N;Y)0jR`R8Q9G{5LQ_7QwOP} z2gqlc2taw6-B6?kF%ERBJCQ?-~UW-;xl@uTW-nj4G|AO!O+9dw1 z|61CkC>od!+Ik4I_4gg4pW56pdK7ofHOWI%-X^>F=JoeZ@3z#1{Y2{mb9Ig+ zU`1@3UVgvfi^4xw|E|kTlfQf9nq|az2#-94=W6Hhr<`{sp1a08*8A=eV0g7LyU$Jf zu0EDLpS&FVOup;uXCA_Q$zfbcLG8Z5IWnDi)Y-PY6ogaH{jPa#%6Hx8hJY#WyGNG$ zg0t>-kDT&z-*rvmx#Z^_!5&dQ`cZSUt}K3@1D|gSA3U` zN51in;K0Mp+9tqAa_qord%s;i~W?z%I zDEYa^|L)4?%6Hx8Qa6z`$_&&~L?U9Hx(U}8jBk}S7EJ}K{3x^)EHgH;=r^n0Ba{9~ zxkvI^C*9*HH?-N$n15EFJ;~4Mjw21!LVB@!3M&!n8q)J3H1OzjgBcYzod2H9e1zva zWdoxujEjD^H1D2{-cKkB_iG?6lWSmgb)MAAR-LCiM(%&5X?I*Qs^_4o=`E7Rbi8>g zu1;Z|#2lEz@#mR^d6I6~ed46^Bt@)Pi{sOTyPm1@lv}vG4(loRKm|aTu84ce2IAi1 zmCO{C-6ZTXc-~*0=K3kGUF9C>W?npITm2rG4DJEk$UV3>vI3w1pZ}cWAboQtyay-( zlA2w&!GAd(8Bz8%4Q5EoOUv&k7ni+i^mDoA3c5CvDzvm0V zSN1jLMm~J$TsxBHD*0>1?nFcxEV8uMv>qyxVt{08)OE6hvVD} zCxQ%sq^ZroaRximypv@QehBQ1f*b73x8|a&WCf;DaclBZQ%)Hre3t2`;9htTt$Sib zA7viq91(7c#~o?l^C0aF&du`>n}tG0fgHn^eZ`XO^UMHzi>uEH%CnG0WORxSQNTF0 z1~Lyz&+@Dj;Y)4}m+^`4MI|YD{M)e4|Ho_aoKW}QWewg1IVg|&JFG#rXrt86{+HK4 zC*WTLqeErtzFl=sSsJpTE8JwHSKSoghM*tnI!*^&A0iKxMvb18#rQ!VZ(Xrxllg*d zJiWhvKj>53=OiA5UPb=!{^SaXBlQElVf}ZnTEUnbWiB>2F<(@ z*KH_r z7jdurzgruFJOA1kJuinjobK893jghi_6;kr<+y^?@?I*GDpzC+t&lB{eL37m$9Y}~ zt*zKc*;XvB@VlQPWZe$`j=Ps z6SKPINqN0`5%YYzLkjVVB1hgL`nKUNx|)`D^4PnF$qYO$c!Bh)_*EHz62ZxIi_-)u zl6XEWNRQj_@sI}o=Pz+>lb?UQxm#j3Q6#e1IBtd@((q1-Bo7gYc<_|8X$;1wBQ(m4 zTDW2yp%vq>Dxg^d^H@6$KNT`mnW|_qLf93A!rmA^#sn5s#aM@~U1s>l@oCRK)-n`V z4zEt@c5j_ML!H?}TkH?R3Ztjygu5SeL z1f)*8m>z7N6J_JouhqB~auTf?ECvHxpnK+-^9Vd4Xe>HCe#!j*+k5lCDvRrXc$S-7 zvJjFGNZ7&+Ae#{GP43MCiXv7csHxfD_9j#Ecz+TVj&(55mPR=Sbn!G)N^+NNZ#rou--$W56TteA zCR%r6H%$|*$oFvMi?y*{`6kzkyoyP#*I=EVR%`J0fWCPrGLv~b?J;oqnO0n|PTCw@ zP2fzPX|o3PORG_Y%-b9EsHU@goUo=H9@6IAet^RUOg$TGHt0z^Yd}A(;&2hXH7k!d zfjLnaSZL3bb|Bq%mfng0R-j~-p73Vrv=~4O5ZD{qoo&W=YcLmiya}ua^j>fBHa`Pv zS;Cu9`ev|Zr`r@@)e~)u+6HJ9-gugzV{wRhLp7R;=R`HNH*gd~2IWqhV>F1sw#AL5 zSTEy&V>DsH>}?W|T?FcHpb0#R!21Dc(`bv_ZeStruW4`%;LIdop`j5w?!e;J0Pl}W z%W*plJJ4`8VomjVZslk2Je01%GUdK*4Bn+swK-RZL-GUO`u<2^nt&zfIOjv-Kw*lC80NYVD=C3FO z@rL?4?;S920_`UCv$Y$yqktyEI}&I&X3Xwv&KF&c`akmqpPzWVp>^Tp+Gk(hSmq2V z1EZ)%^~`hAesJd~C!07=;f*&UI}wsjyy?eO;{*oODD526<}=OqAstY&?n<(x4(8Mj z-YuKAliR(yVR!apC<3~0V1FS)#pq00R})+V$m=t=9e>w>#f~%|2p*9UEF&{iImyU< z)HPZ>IObUkQfZRo3rhzlEir0|#N4@)?Kx8NWa%Gmh2HZEq4X(r_g6=kN ztWR5%Edw}-iyl6@$qLd+xJa5T$rEo+o+UP`l1VzKQTVh_mqD3Xm*n=wJPnNhWK&MP ze&4dn>+NhMi4yawJ-0d$S7r>>qQ_7hk#UDCG{Do4&r>op;r`QiC%~x&v?%RKTyBNn zC)vkPJKPDW2&rhuRdcntA<{R8(nGv=E7}3{kr@ zeh6=YllqO6PikTE4gl_py+cuR6K$y={ix-rF-l+O6`YN*3z~8|5!u$>S{B=FVWd0 zzOlS^+M}x{=?bnNxNhybgbKO-hW1C}UJeYLPoh(p3)B#-MmQl70vZw~#0rABuJ(Zc zbJt)Fb1#!KahIbGLZ$-{&iC4vU$aEN^q^o3vkE3XiA0pEXTULT3=lp3aIh72FsL)O zphH~`kde-Pzf%PB;At>BB^y~9L!(PhnLBFK!OLpL*33O+Nwj8c?XrV=u3}vSGM{W? zIKSZC0LYIt?0%R}LZartMwP$V#X0FYI-TAhHHxyr>&J7xgo3hk{gB5g&a;1##w_GX z_Pp|~0n9@=OBjsilChXahanJPts{` z>AA|MlQV_w`<_&@#4bSFU>i@dlkvt;8b=1fwAIAM?cO%dE|~SvN3)og-tW;D0(78- zKi|WN>o5^Zl$s^<-j-RRlv$W3dAJ5B%vdu$`bkrF+9-`{o-#qLJq zDLo&J(g<1_Q6n&uqq1J>m=vLWQ9h0y|Y(NfNu#J<)~Z`wAgYSC1pcrBU^zm*+pn{O}6`S zCVUptZ+wv~Cp*!XhGfG)GZ_4lWy~nhLlV-JyEje#e!H`WBTAeF3auu2V?J}5#6EEw z`R#)yKOdrn=7vNYd@O7&jYf_pp5N|-od%iyct#rNBYG10BdvR61T3v_KzET@vin*j zUGH!z=uk`c)F2(%dTXC|q#aPoJ4p;7-ld}xO@jGhEaOx|dgxtFqt5G-CLw9g>nZTk z=t+IjcywWhHAt7?3>xIuKv29*7SjXX^R- zOS(yS=&$JW^>=Wd$4_yF;Dh=J{gVE({)hd+?RuU5r~ZJtn_hhjFC3mA zaFW330uL2AVyui^I&!Vxrkp3cqHI)5?(H0v|-PXfMz>Bw_@BfuL7?ZR(Q_ErroU1JWb>GEX?=Sz45 z;An)W08Ri*NO+ors{m&qej(stfX7Jq1PM0+u10(};5xw50J{Otl<>KL7b1K);3a@R zl<;N=-w3z`@xKJT4e-~1zXW_#!jEI%b4T9GDcs%>%yc4mz@HNE2jheOV44F0>B|HD z{D6OdX}+j@g1CZo2Jr@Q?JrF@Ky$Dx!F+yl1WF^K)41CGYWlehxa~CI6f+F7e*;(ddm{RL{#E#w4DNUew<7 zB{A?U?1*I)AWV)9qM6Fe?R-qfbSIc)xviOr+XavJqjNk$BnfX3_cdWBKQXZ9s*bj zI1ayB_$vV?j5vu?`rsO0{k9+ z_rt#p@K*dDhW|;xyYPDh{^tPS0(=GVIlx_juK<34fhPlS4`2=kq7=mM0nA4@1#kr5 zG{Dh-Re%!!M*~g)oB)^rJOOYPU?bpRfPDwhGZDWU@#g}r16&Q*4Y&^QG{DV(7Xsc0 zcnM(N0rXMCZ$bRyfVTl|0SpeH?;!j%;@<;&0q|+S*8pDt{L}viRCiu+9|a;fSpI)@ zK)ripe*Q@MJ+zlcPxmCcKce^^{i>I|XRYV+b6D@ammGkSFAnvRf6f;dc<=dW^hPhL z=Z_x7Bp!dXUGJsxUrb{5DfQn=lB|77|64EGTU=Fm1pBaVe*@?L(ld|t#~)g&nYibX zo(D~Bh<44s_;rHK|63<;gVYzePH=~Q8al!YtT&9~-D=WEwy1P}qMz3H&w$$Pp#Q@1%K+(Ejc{MXksS^hhz`JLpN7$Y5TFGd zCgCE$aR`?JP6n)$@F5be1)Pic#ehcw9w*`D5+)6%4e_S{b^)Ft;d3N>0pP`mzY_3! zfLBZSdI^(8b1UNS2fPdLVF_=O@RNYgA^r`(R{-CV@Gc2|0JsOEe+IOG6u=w_=Sz45 z;Aq580h|Dsknl7KR{_pK{6fIP0FROI2@-AuT#fi{z;%G9N%%|&p9^>);x7lh1n`Fv z-Ynr80iVvkP2={qclXw3w&0CNk>WPM$2nzCpKe)Iod{pLqgMva<6 zzlZkn=vm$X>ksh0M?d2w?^)~l{2adGy_ZxZk}u|X$v@|dbG-L_GOOmX8O8;9g+FM*zcm(@!+Wz#I|D|Uh?TUbrQ}$S}=VSVNyE43=M_Vo!2eHRRn~;6ZZK6Fc+L3X)cG~UXczR!O4(cG>N?F`BuBhsg+(X{X8+Y5Mmq=pFPJyL_yw1`5tZ{+qoDG%fJ zDH&&2O(5$g&*nXpO| z&N4g=TSZd}s_4!cBsvbBDV~Rq%cYnBUd$2Xp?higcIv zR`KXyz%=Cc>z|R;e|%n%??JgPGAs21W(UfJyLa|3mtP9MT>sVLtj;gW8d#8(JuIsz zYjPwQFe?Xyv6@cB@jX;eo6Ko?tGO3NsiE?ST!3~=Hv`dea9jhlchA)o^*r}@kFwcA z=7}jVF{KX+HtB+%o#tM)1y^sO9jrFbqDEjxqP*|C$i@Tp2<@C}r`N5=qlXV4jh|^4 zF*tkh2zJoVFEy+5ec*e4ta#!}jSRU-81`xC=Q-52!^qFGF2;r_TV@V|wm7iYt;(gn zLfb~-(k1*Z7id-u&CALg+Q*^3wZ$B&-?Fet0-g%ulqY8_@S|^0!wxp*KGiN}C*yG5 z>$vj#*P@GR%kVRY4!*AFO8h`N>SB+XqrY!qX-@Lcm%01S8GJ)Vx-`CMj+s+%-DrCM z4!nPi_kQ2HoI>^F`WJb?m-77Z9dj1N&Cb!+6)^tu&3yeEoPnCEs;Pwdeh8fiB@0o> z29VgI_rFRW*DcB8%(F<^Quic@NumMd<%h@NzAb%x+vKjU1KZ5*3rBW!jl7WPJl`zV zzp-=x6TZI;;9(A~B~p6H0oeXKg!k|xAY??4{Z58b9fKDr&WCNqU0oB~%;F%fg}|k- zT7c03_l~oLqgPvgB{`&YPX#q%4J$chScsdXryHDPnq~E*@#O~$4wnwf%pI7YlU*<{ zH*-*Fc<=$`W?|vvME>AW1Bd6PrR5GEIBIZyVsatM_;s^TKLNZsDnfX3Ss?YsOCMD@ zbYtnDP8LdZZfX{!f9Z^Z;j_nQL^3lY8RKUUFPLE#PL5XQuL>CxT9scJolI?VtC^uU z*|D%2EmDUOmQiXnvn0=NkA4Z~Gw*^eG(s_`zG2V1JGIfyAWLCYBOLIKQl;Z6dtbJr zClwqxGB5BN4I3Gpuy0) z9huq?nXnV&n{Ru!yEd-@PVqeRJx_xrQy-~kb{S>vHk8M z-e`5Vf$5@o9q&)4e&Nev?IeqK{h|#ixr5C2JQsQ%T;GOA=ngpS@}q;pO=cW&*6PVU zF})czJ50sN%GbYxJsXxAdCymc(YNz(^IskYV^sffWL6CE30|VY&=`^zrr-cmwA9jY zzPl$m%jk*;Q!>V;6%HOaWaz}<152{)z`POTaz~EeHY{sGM$UljtU;-v`Xg=XsK}s5 z9&T(`-JqokEfyC>(O{m>7voXC2%H6zP3PIPnwzvCntIaC)9gI0)dxsyl!5pgY!#EJOclvQV%pBDY zxf`~3`EeT&SIlvD`*BOne03G%YuNrW5Vu%e1z8xj_XOe=A`Ula*n9oB3(O4Fg}xrP z_q#aQPu^gv)CtHhL)C+W{iu~}$GF6eV~X>nU^B3v`YC?6;?b(FP%O#r0q(KoM;xOl zroAeKvv`?afNaR%MnFOevXE}ZrnApSAGKy|WN3at@znKZcO-vsd_-aXO5*E7X0AFC z?LTaP?a_^y$~e`AHXXJP?3qdY2i;p>3wRjLTkoeLUd_v0xkKqzEsMU3MJ%*HwB)>0 zvV~5R9yVm`mln=4SwjynWbc$zT$)q^9PfR3wXNfFU)M#}_`Rd3?n<9Nj&rU_w#Rgn~g+ z`VARZF*3HO&deD$AvQQaGN!O_OeDXcWc-j*@~;~O`Twus&nK?-#cwfq>Twn0hV)C~ z59g1oxU6nb%*+`wzNEmzKR7mFm>xRnx_lr1$teFwRrXoSKOD7Nh-Nj&>5F?&5@=`p zqUiMFrR_ECgRYq08o_&mzCTB8w(CIOWZXnUwfY(AhmO%D^)U}{DCjH#owwt9JE+g z=>}l#7MLCMCTCLT#ICMMofr0{=$Va$9UX-m&q`8~^nGUjSkTg!KjndH9gF7t6MRZlb{8+8mgGijfLIag&Tni{h{8>R#)0~)WuNc>-D}iH9(XoBL#QrJeDt5 zr6hGA$8}l8bcm-U`!cL}?WogEi>xgkTXq1%SYFNeWavumJyQ%F**Ns^Kxm z1m^yMJ`)VuW3Ae0Pe&)nRdc}aY^PggJDH7ZSKy&jj_CufT)u>bzQW7pV_ML~`%&lj zMn$}hy2zrA6OdX`6A6Eiv_i`1o^(3gr^oB7mC{$E`|hh&hT(m*NX)mPBki|~e1qoV z6Z$sE%9>5dW|;IjnOO^&c`DX7B5F0LDRlh7l*Y};$g@>NP#1J6^gy35y2}$^*F#i# zD>UF1R%mE7W*~oV4$+C#7`jfFIyi~cLb(fI1V&q1GB~*`L$(|Kf(>1wfLfisU%~i& z;qR$?i^Gf0E^KQnJR5rFSvE`WuwO&DR+4U=yjvqMCZxd5TtQ(1AE>bOk0wa})nzA} z<*zpR9+06fvbp*JwA+7~eyBoj{Q87tGgnclxKCR)+y6R~&Gna;d=JsT$&S-o?P{I+V@Bw;!hi&oH_re6MvV z)-r>r@|LO=mOY4ZfTXtRJzKLdk_4yOLLX$}5ZaWelB8UaW42q?6^zVya$$HaUsa-l zzK#XMT34rTu?jM1Bx0zwa$&CI$N$z6*3>C;>RP>}D-U{0SkDbyH>Bq>uP`1A7K+vr z{;W?z&4| z6Pki{Kz3X#H3L&-udV&7Q|4X!o-%(=~bDIq1&NhC!#8td@hO zp^m1Z9!=!A4L5)hO{DRS$IO6cB2FRtEDA;MeK?G3Nwp3db5h|8_bu1{wY^pQD5t%C zZ`E(>-X(T(zdh=TP$~K?&5dhOY92M*V1YL74V zYTnTyuK#__HQn;5Yvuc&Ywml#%{AI*zWsM~WoS6+a9{K7Pts3N+yBmd+n(|%^X+%g zwx|z$5)b~R`8Kn^_k8;uwKFsxaxWKO^wNBr&YVfXh#fkRyPM3sx690%X4EwMra5;u zuRORpc5*)c*=F0J@t)}OX4^E^MjN%SN#C#b`o7=geId}h%YV_c@7MM>`*t$<9mwRd zJpWz+3imbt2H&=0^8D!&aVMfr{ltm*7cun?jon-RdQ)%I^f>)2v`qI88<*nOm_tn# zKb?ItRs0qbuS)!(KpHx!W|73F!oO7fVb}_<7Jom)-zokyRjM8oe+KyZq4+aZt{y1< z{%WJ9TY~9dmKv&m;rxim#U2I6YYqP^;y1XPY?t^kf|ykChtyD$k9MU)bUMbdCMJH| zFKEseKkf!LSBu}O@n(zo>0K{PiW+R5llWBl|0e!0_JeQ|6zAVh4YofPf0|lhe<}V9 zRT4Tt{F!P@XtDVF>qzJn@n@-c%H`r8s5(>Ft|OF&f{sa@>Ecn|F{w@BH)?FE(59^# zm3ouJhg3ZEiJ5I{Pik*&TG<&X!DZW#In{?nX12AjEsj*Rv_$wpN2I;6qp^K`V?!}L zJ)))Y#K>W7t!)b$o4Q)++e?Z|r%^mP^o$-I-PqpI+}0Ywvp}r(_O+4bj!1o^v%S8d zaZP>u>PXuPAMYd}YijQ3Y;13Ah%~oG*3`G26j{~P-rUj9yu1^6c0{_{+FKgBn;RO7 ztJ_){h?IuJuxcNZ^1Mr$~q;^l2?0uP#(t)ZBsR)XTXw${$b!nPHi-SzE_RD_o1 z<&CW!ptGx$5OuC>j4YfpFEW2^W2;L%&n1`?@%Rxdjul5ZGkKRevA({!rG8mUW2C#e zb0rv4S9N5hzH@3s=;&D9-n_Q6qqw8FrMRuVY0CV%dHa9T(Y$6Ya%ySoZfsv(-_aPU z-z%o6qouxMCCF~=TC)svpWN8qwzj1n%nn4gFwIZz`MmeMw~prAJ3uGpLR8Xw4oly(eR2N; z>6<}c1Q;VIV>@62@>|0ctVUd$S^?L4&nxdF#I-ZE^mZpxK&7MHhz5GU72J!cRVWYf zumew*qesrKLRQGG~Mh7rb8*KrkT5ke}=Y#Hf|I$0Nkw-W1QF}d^ z^IHbra%C$*6#q*;MfZSYZxTaHx3FDeW5M`|}j74<@TV;%VI_}+wCUW2st zcuv$8aXQM^@u7t&pn9P^skVqCl-DPxAv&mkcW_-&4Yva48nhT!$0su+*+Z#ZZ`yZ? zdZchIny4fnm5cb&n<|n&lq%Ei~; zcj|u7(I3DX-A;A3+Mv$ISyA6m-&a3SXX-Sa4!!Mb>ORbTs=&r9oY!-)`YW{A2u|_2 z5cBRL^=)+tzAHaR-KPFeU9GNA7wHV0sr#$%;_I!mbe6hCeH}XCrFwwQ);Ss)o6ggN z^kAK@3-k~@R1ed`^$1;v6M!PR2wMgBs%AY(k49r{(PPvqeSjXT$Ej95Uaip+^hA9i z43sD9Vm(DibxfD&QXSU`U53+xDs-ius;B9L^uc<%K19#ZRk|8y3f1UZU8iU1*?NxJ zqz_d+sMa<;SI^UjK}UL1;iNM?A5FDGAEA#_>+}M>P#>ii>7(^xy+j|QkJVpNALuXZ zt%YmZov6Pow`x4Kv(J37?|`*-KMp%r zy{5bMiTWgUqCQ!lqE6DM>eKKQ{;B$Obuvyn`l>!d{ZM~Rou<#!XX&r&v-LT8gFaWE zhjWm=p)XK3>2K-_^+x?I^#}Da&d0u3e_NfaFVUCcl%((K@2R)d>H0E#Icz%5&{wFh z=0~{*nGaeYL(uZ`Rl9AM5M%_4)>Vqy7obR{ELxi@r(Su79q#=$rK~ z^eyTQeXDv{;|nfm+{^Us>PDQubf>;cE!TIepXgued-T2fK7GIbmHxGU04Fm&1ikxV zwOj2`4f+xNsD4cUM*T@O>fb`^en>r{x9i8%qxyF^!D)wnQa`1i*1y-!=x6nF>Yw^~ z{epfGCp*2Y9@DSrSJiLSZ`FtTHMLE@uK%F_sNc{#^`G>cIQi)>`Yrvoen>Cc|W!{wB)|Fxe&tha2rMxn`hxQmr(3>M1kG3^w_uzzi`%%`h|Ej4*{} zq=}d!Gs=uMW6S|&tQlvXg^WK*pEXr`#|sB6___@41fb-9U}m?<%( zCT6e# zX)|lhI@4}COsDBG>rJ;g(VS#XHm8_V&1vRz^A+<|bB6hvIn$hFzHZJo=a>!VTyvf| z-+aSdV7_TCG#kyg%thv6^KEm9xzv2eeAj%>TxKpeSD5dcADAo6Ci6pcmHCnRKXbLY z#%wm%njf3%%=P95bEEl*`KkGtxyk(8Y%w>RUzl6Wt!Asa&D?J8Fn5}}%-!af<{ope zxzF5her0}b9xxA@hs?v~5%Z{d%>2gu)@(D|&Ew{G<_WXIJZYXXPn+MHXUwyhS^Z7@ z9afspn-|QB<|Xs8dBwbHUNf(oKbSw7H_T4+C-bKHv-yj8%e-yg!C}aMGk-U`%)912 z^S=3q`KS57d}uy0ADi8#2WtuM;^1Sg-c#>eW33I@6q{SgKfSoutV%nJIoHZBW$4^X(P7Cjf z*r>(kxGlADo3LfJ+*a61JJn9J2ib$|bbE-MVXJJlooQ=qt*x`O>})&79%|>>dG;`S zxSem0ut(YjcA-7WF0x14#de83#vW_GWWQ{Wv&Y*L>{45Am)YgE!8Y0zw#lxv&32Vt zZCmUb+iKhFTD#7++YZ}lyX<<~ZBMi(*^})l_EdYCJ>7oAe$}2~zh=+0XW6gYv+X%{ zgFV-tXV16auou{G+6(PQ`z?Esz1V)+UScn`-?87d-?Nw5%k35R`}PO+O1sJa&|YPK zWdF}zZLhJL?X~vD_BwmLy}{mSe`0@Xe`as8Ket=#&Gr}e7JIARYHzc*+dJ%?_AYz3 z{iVIf-fQo(_uF6DU)u-lgZ3f&uzJQmqMlXHt8c28)CHJO{2pHdKW86RFWATIZ|rZ? zi|S>&&2G1k+uzwI><;^+eab#i>+|7`za-?DGpckEy7-|XM*F8i*1&%ST}VgG4Aupiow?8kPu?Xi1sO{or3j z<4dBYr5uix)H)cGaI7p;+tuDikjlOkiAKnZOExl>(~-Rtt2%03#K$=Swg9eIGI;rcc8%iG&p!}V=V zZLN)~)9c%tTbt^acXjeBNWvwG)mE2N7Jy6G&eHBxNaCK%qXY1IW z>Bw?{{z9A!<sjd!V zg@>!Z(XmFJOC45A-OrS|oGEobQ|f*u*Lk!gQJK+{Bqy^8OEPOv*+fu!laD*IiHUOM z$O*jQVseA8xv1>q%Pu0ca#?+Q#>ynF)aF3zu8BJGXL4tX)y}L8H%sxFUGY+zxmr#0 zs&MlvzmZ2vO3PDSFk@9R@66SKa%8SQg8hD=3TEDifqZ3OyD;sTNYg1}lumIf4LAdaLW?lcbm0jqL?OkhH>bp9_ZLT;O zZM;UrNK)EXwzapqg4amFYo&f_B%d0o$6Bes8V^c4sPWQEAFe4+ZFeQWA{ClbW_zFn zsU7?%qa(nnj=*yw1xuB6*-ee@SRQOx)*`(&)QQSV?c^LeQAv#HjmFAKQ@es}U?CT) ztrc==%lmirVndhIYL~0k6s$jjlU-c5DP2i^)Jpm4q`b8re}pf!%$I1ahNUwgOH;s^ zt`IdF6|)EbD3`p(o^COfs3BhzdBWUj<+SOWH{aIF-ewwHhrDyoJ0nL^D>uLpY_ zfvJwhD(W(3C8^4s6=-hhvwYm4*(4oiCljX52{P9cW;{fqlU0Q02yJsjI?Qp>Va}o9 zIfpVYQXR-RG?{1S+yL^_c`n&J*G=XH9);$CuGGWb^TXZq!voK4Et>570DWGkoGHAS zS=)bpuU0?bQJXP8(D~;l8+(nEt5$gD8gZ<)MtEE+y!E7{YwURLrI!{`QOy{$u`s}pg@Na;`YUTgM^RrtiZh_NXi1F8LtigRT@);}h@M)(UR&0GQLj=j zk~&%>eSH!4^+m3)FG?1_RvL1h6tmW2hp@Aj*?_+7#;jNkcS?js2GvxiE_P*GoGe?~ zV!y91c76SrB?f|+mO=4L@MdMD}1Z2Jd zx*iZOb!5j&Ie)}E^5dmWWW`Ha)FIxnIbQ0D6))xDBc4SbpkqVaDXP(UoY@5^_~YCf z5ElG#!5cnx-)Z9r z3;vkkj|u*m;Fo3=m!Tyd6Z|p39~1mB!CxZyO9X$3;4hK#mk9n6DSwIJFA@ADg1ux zkt_jR!q#TV(BWukZCk@Q*h4tTBRI(Ga5x{~;BWaMbvQU7;~*ckQ!I$M^2eMU zLD-c)=K2r9uKY3Ae-L*3jJbY~u;XjY^(Tb6JpyvO1?2Vu==fePD=g)13@M4Z{sicr z$DG_l*ws(W^%I1J4kyPDcJ&c+?GUpLm%iGKIVIJuot0F(u?S(ILl$3RPOg;1oR$jc z>OWTLLE(dI&&Xf!yQL3kKHM%5??G;_C5foxN5aVggdJT8CqEE&`6pa|K-jgjMAYS< zaQy)Bj?amx%RdqI@^}1AL>+$@7(M-xF@^Mc9=u;l^Hs1-~16 z5f=Pz>_u4cyRjEx!SBXigt=drB;42w=;|}!#$JS7J4?8+7h%Eg#$JR4zZ-iI7W{7P zMOg5=u@_;%FDrKmH};k!N`?QWf?rnp60*{laJm=L34W)0AuRZv?uD@6ce)qCg5T+0 z2n&9vdm$|4ce+0Ss+{X5+YVb`t`PWM7s>fh;J2uuAt-3wvY zt`ko8DoIFxOh|uAxbY70!hh-K3F+qv>E{XQ=LzZO3F+qv>E{XQ-wEm83F+Sn>EDTj zl)p^)Uncx7lk%4d{xT_lnee|%%3mh@FO%|@3IEHa{AI%bGAVzV@V`vTUnclvd`OfF zemBmQB%D41DEMXENXWR6kZ~g+<3>WpjYPTNFBkmfg1=nwmka)K!7t-XLdKbdj57%t zXA&~bBxIaP$T*XbaV8<-Ork={Um^G_1b>C#uMqqdg16_DS(|I;ny!@p0mb6}r8_^O` zxJs%yqiCrpR;8jSl{!I)cy0)QTyQ|HP(W^AfZVVEy?nSKAyNMo`gQtRN>|NQ`w%R@C()RbJlAPKP@f6t)@#_PUb4*Sf z2y@1PFsIK-rYD?P9wIk$4y0x}(jD)~`Ybz84aF3Z;a*{XmOpRhpid%)a)NVoj!9=y zotLR%ZveS_#Km=9sdu0qAYL?q6Nn6M2b?I&ZxI~KBz+mx#R2zL60Hj-acB9p5J;XZ ze6Gi)WDb5*l1e`+nQvZ_-N{!2k;!reB9pHVOukOF#k39dXbJE?@N$x9Zg){p;GB4GbGTf++ z!aYVG19yYo0CyW%=)y+ecW^(@AK;{sVYn4RW5+!Lca#|gcMA5^HTKa<;Z8Ht;7&I) z;8vSzxO2^1xbw|?xC_lfrR~G^DP^$_xD)O_LLpeYDyBIdwlXIxTT?ghD3wRzKt)>W zJIM?#M5RI5sANe$onYw28)eBTdHqT~g7{7T-wpV*;3@2!zY4pq zw~_J&^)CKCWFLiYzzI{S$q@TC!E!5?bGQt=;8et4gAX3zqBis}^6Qc8RQJCxmeb|I$cM!WVcFWi;VYiZ8oVG;R$(RtkvV$jj~%oYXy1+yLIf&TY-&4y@1^%>>f{x z8@hqrRqU>9Yj0@P>)Ab(-LG{Jt_|#dlihE3G`FtMm$AEv-D^5J@Y(wf?EakHt#C{9 z-R%B~-ACY->h0`4#qRTP5Oligfe#xVu#7P31A zl|b!+>S}N3#v7Qp@i^f@*Lr)qDV#^pP34%N%QBJc$&Gk)Q8Hl;y7Wa1-9_qL8l^HY zB9Yt9?pnB+Ffb;!f!$?r`@`~;+@sk&3T_tcWXYY)ZXMhKY6iPg*`@qhQulJx8L#gW zQ((P`PK_T%O4#7$q7@axes((YKN|L}D_}gl9wX-2keQdlp7k1xlD8mldf`Lnnhvdy zGH*aDS7@6bLIM_|PgT2gy2d@%L?t2nnZWx5?hyD#fsiSj?tKUCc!AJ)7_S?zw4Eg3 zi4KOqm(Y69l!+270Z!7iAiL}v%GgKj%W!{dUxmBPz5@4gP)c%+q{|q{5(ByN8mIXK zr-4j^J=7oJ?g-@d62cGL=i&a=z5sWdeUVep*GQ@OW68&YAk4}*)!AM@M4u!?^u`u#pg_d+O zM!ECUMd~v1Cg@n94#KzW1P?ZEF`RDRVR(qy#c+msk71R0pJBD3x?Q1W^7||0{gv|m zO6A^PsoeW3m3x1sa__HH?){apHS@JL^B2zXVDndohj5s3q%d+q$+A#eg|7XXw>i~x z{^n~1@ce_r)f`Xw>02qtHCb zmUkO8kjJ5QKB0ENuJ$R|OaERy18ctLpq0F!UW9cmELrhpD_V1!+JnJUqc>|)hWxaN zA~$W8a^6cZ;uxfv`g!vAa@2J`rUe*DKNpRC^VrK~@KfvsQGzj$eG#;bC?wNCsO{O1 zPDi1&FGXFpa;ef$*Rc0TU7?@D#uYsrcCN3&ea4_y!`>DB**s^^w^4`a)v$VnT!7^( zdOoaQ{{+2aD_JxG?t%s7yD)ox4BtAY=z*|?{0D3z_iN7x?pjz%-mOi)V_Zt=3Rt6$fCc)G zR3R+PZ&o8&JBhG%QUv?&7t|>IKCHM$v!*hJG3i+?NGoF>, request_tx: SyncSender, response_rx: Receiver, - on_key_rx: Receiver, - fg_color: Option, - bg_color: Option, - alt_backup: Option<(Option, Option)>, + info: (LcdSize, usize), } impl SdlConsole { /// Initializes a new SDL console. /// - /// The console is sized to `resolution` pixels and its default colors are set to - /// `default_fg_color` and `default_bg_color`. Also loads the desired font from `font_path` at - /// `font_size` and uses it to calculate the size of the console in characters. + /// The console is sized to `resolution` pixels. Uses `glyph_size` to calculate the size of the + /// console in characters. /// /// There can only be one active `SdlConsole` at any given time given that this initializes and /// owns the SDL context. pub(crate) fn new( resolution: Resolution, - default_fg_color: Option, - default_bg_color: Option, - font_path: PathBuf, - font_size: u16, + glyph_size: LcdSize, signals_tx: Sender, - ) -> io::Result { + ) -> io::Result<(Self, Receiver)> { let (request_tx, request_rx) = mpsc::sync_channel(1); let (response_tx, response_rx) = mpsc::sync_channel(1); let (on_key_tx, on_key_rx) = mpsc::channel(); let handle = thread::spawn(move || { - host::run( - resolution, - default_fg_color, - default_bg_color, - font_path, - font_size, - request_rx, - response_tx, - on_key_tx, - signals_tx, - ); + host::run(resolution, glyph_size, request_rx, response_tx, on_key_tx, signals_tx); }); // Wait for the console to be up and running. We must do this for error propagation but // also to ensure that the caller can free up the local temporary font resources, if any. match response_rx.recv().expect("Channel must be alive") { - Response::Empty(Ok(())) => Ok(Self { - handle: Some(handle), - request_tx, - response_rx, - on_key_rx, - fg_color: None, - bg_color: None, - alt_backup: None, - }), - Response::Empty(Err(e)) => Err(e), + Response::Info(info) => { + Ok((Self { handle: Some(handle), request_tx, response_rx, info }, on_key_rx)) + } r => panic!("Unexpected response {:?}", r), } } @@ -116,144 +91,51 @@ impl Drop for SdlConsole { } } -#[async_trait(?Send)] -impl Console for SdlConsole { - fn clear(&mut self, how: ClearType) -> io::Result<()> { - self.call(Request::Clear(how)) - } +/// Number of bytes per pixel as supported by this implementation. +pub(super) const PIXEL_BYTES: usize = 4; - fn color(&self) -> (Option, Option) { - (self.fg_color, self.bg_color) - } +/// Data for one pixel encoded as RGB888 with an alpha channel. +#[derive(Clone, Copy)] +pub(super) struct SdlPixel(pub [u8; PIXEL_BYTES]); - fn set_color(&mut self, fg: Option, bg: Option) -> io::Result<()> { - self.call(Request::SetColor(fg, bg))?; - self.fg_color = fg; - self.bg_color = bg; - Ok(()) +impl AsByteSlice for SdlPixel { + fn as_slice(&self) -> &[u8] { + &self.0 } +} - fn enter_alt(&mut self) -> io::Result<()> { - if self.alt_backup.is_some() { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Cannot nest alternate screens", - )); - } - - self.alt_backup = Some((self.fg_color, self.bg_color)); - - self.call(Request::EnterAlt) - } - - fn hide_cursor(&mut self) -> io::Result<()> { - self.call(Request::HideCursor) - } - - fn is_interactive(&self) -> bool { - true - } - - fn leave_alt(&mut self) -> io::Result<()> { - let alt_backup = match self.alt_backup.take() { - Some(t) => t, - None => { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Cannot leave alternate screen; not entered", - )); - } - }; - - self.call(Request::LeaveAlt)?; +impl Lcd for SdlConsole { + type Pixel = SdlPixel; - (self.fg_color, self.bg_color) = alt_backup; - Ok(()) + fn encode(&self, rgb: RGB) -> Self::Pixel { + SdlPixel([rgb.0, rgb.1, rgb.2, 255]) } - fn locate(&mut self, pos: CharsXY) -> io::Result<()> { - self.call(Request::Locate(pos)) + fn info(&self) -> (LcdSize, usize) { + self.info } - fn move_within_line(&mut self, off: i16) -> io::Result<()> { - self.call(Request::MoveWithinLine(off)) + fn set_data(&mut self, x1y1: LcdXY, x2y2: LcdXY, data: &[u8]) -> io::Result<()> { + self.call(Request::SetData(x1y1, x2y2, data.to_vec())) } +} - fn print(&mut self, text: &str) -> io::Result<()> { - let text = remove_control_chars(text); - self.call(Request::Print(text)) - } +pub(crate) struct SdlInput(pub(crate) Receiver); +#[async_trait(?Send)] +impl InputOps for SdlInput { async fn poll_key(&mut self) -> io::Result> { - match self.on_key_rx.try_recv() { + match self.0.try_recv() { Ok(k) => Ok(Some(k)), Err(TryRecvError::Empty) => Ok(None), - Err(TryRecvError::Disconnected) => panic!("Channel must be alive"), + Err(TryRecvError::Disconnected) => Ok(Some(Key::Eof)), } } async fn read_key(&mut self) -> io::Result { - Ok(self.on_key_rx.recv().expect("Channel must be alive")) - } - - fn show_cursor(&mut self) -> io::Result<()> { - self.call(Request::ShowCursor) - } - - fn size_chars(&self) -> io::Result { - self.request_tx.send(Request::SizeChars).expect("Channel must be alive"); - match self.response_rx.recv().expect("Channel must be alive") { - Response::SizeChars(size) => Ok(size), - _ => panic!("Unexpected response type"), - } - } - - fn size_pixels(&self) -> io::Result { - self.request_tx.send(Request::SizePixels).expect("Channel must be alive"); - match self.response_rx.recv().expect("Channel must be alive") { - Response::SizePixels(size) => Ok(size), - _ => panic!("Unexpected response type"), - } - } - - fn write(&mut self, text: &str) -> io::Result<()> { - let text = remove_control_chars(text); - self.call(Request::Write(text)) - } - - fn draw_circle(&mut self, center: PixelsXY, radius: u16) -> io::Result<()> { - self.call(Request::DrawCircle(center, radius)) - } - - fn draw_circle_filled(&mut self, center: PixelsXY, radius: u16) -> io::Result<()> { - self.call(Request::DrawCircleFilled(center, radius)) - } - - fn draw_line(&mut self, x1y1: PixelsXY, x2y2: PixelsXY) -> io::Result<()> { - self.call(Request::DrawLine(x1y1, x2y2)) - } - - fn draw_pixel(&mut self, xy: PixelsXY) -> io::Result<()> { - self.call(Request::DrawPixel(xy)) - } - - fn draw_rect(&mut self, x1y1: PixelsXY, x2y2: PixelsXY) -> io::Result<()> { - self.call(Request::DrawRect(x1y1, x2y2)) - } - - fn draw_rect_filled(&mut self, x1y1: PixelsXY, x2y2: PixelsXY) -> io::Result<()> { - self.call(Request::DrawRectFilled(x1y1, x2y2)) - } - - fn sync_now(&mut self) -> io::Result<()> { - self.call(Request::SyncNow) - } - - fn set_sync(&mut self, enabled: bool) -> io::Result { - self.request_tx.send(Request::SetSync(enabled)).expect("Channel must be alive"); - match self.response_rx.recv().expect("Channel must be alive") { - Response::SetSync(result) => result, - _ => panic!("Unexpected response type"), + match self.0.recv() { + Ok(k) => Ok(k), + Err(_) => Ok(Key::Eof), } } } diff --git a/sdl/src/font.rs b/sdl/src/font.rs deleted file mode 100644 index 0e009bb9..00000000 --- a/sdl/src/font.rs +++ /dev/null @@ -1,122 +0,0 @@ -// EndBASIC -// Copyright 2022 Julio Merino -// -// 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. - -//! SDL font manipulation helpers. - -use crate::string_error_to_io_error; -use endbasic_std::console::{CharsXY, SizeInPixels}; -use once_cell::sync::Lazy; -use sdl2::ttf::{Font, FontError, Sdl2TtfContext}; -use std::convert::TryFrom; -use std::io; -use std::path::Path; - -/// Global instance of the SDL TTF font loader. Trying to deal with the lifetime of the derived -/// fonts seems to be incredibly hard because of how we hide the `SdlConsole` implementation behind -/// the `Console` trait. It might be possible to do this in a better way, but for now, keeping the -/// context global works well and is simple enough. -static TTF_CONTEXT: Lazy> = Lazy::new(sdl2::ttf::init); - -/// Converts a `FontError` to an `io::Error`. -pub(crate) fn font_error_to_io_error(e: FontError) -> io::Error { - let kind = match e { - FontError::InvalidLatin1Text(_) => io::ErrorKind::InvalidInput, - FontError::SdlError(_) => io::ErrorKind::Other, - }; - io::Error::new(kind, e) -} - -/// Wrapper around a monospaced SDL font. -pub(crate) struct MonospacedFont<'a> { - pub(crate) font: Font<'a, 'static>, - pub(crate) glyph_size: SizeInPixels, -} - -impl<'a> MonospacedFont<'a> { - /// Loads the font from the file `path` with `point_size`. If the loaded font is not - /// monospaced, returns an error. - pub(crate) fn load(path: &Path, point_size: u16) -> io::Result> { - let ttf_context = TTF_CONTEXT.as_ref().map_err(|s| io::Error::other(s.to_string()))?; - - let font = ttf_context.load_font(path, point_size).map_err(string_error_to_io_error)?; - - if !font.face_is_fixed_width() { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - format!("Font {} is not monospaced", path.display()), - )); - } - - let glyph_size = { - let metrics = match font.find_glyph_metrics('A') { - Some(metrics) => metrics, - None => { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Font lacks a glyph for 'A'; is it valid?", - )); - } - }; - - let width = match u16::try_from(metrics.advance) { - Ok(0) => { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid font width 0", - )); - } - Ok(width) => width, - Err(e) => { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - format!("Invalid font width {}: {}", metrics.advance, e), - )); - } - }; - - let height = match u16::try_from(font.height()) { - Ok(0) => { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid font height 0", - )); - } - Ok(height) => height, - Err(e) => { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - format!("Invalid font height {}: {}", font.height(), e), - )); - } - }; - - SizeInPixels::new(width, height) - }; - - Ok(MonospacedFont { font, glyph_size }) - } - - /// Computes the number of characters that fit within the given pixels `area`. - pub(crate) fn chars_in_area(&self, area: SizeInPixels) -> CharsXY { - CharsXY::new( - area.width - .checked_div(self.glyph_size.width) - .expect("Glyph size tested for non-zero during init"), - area.height - .checked_div(self.glyph_size.height) - .expect("Glyph size tested for non-zero during init"), - ) - } -} diff --git a/sdl/src/host.rs b/sdl/src/host.rs index 48441577..b5c9eb88 100644 --- a/sdl/src/host.rs +++ b/sdl/src/host.rs @@ -19,19 +19,15 @@ //! All communication with this thread happens via channels to ensure all SDL operations are invoked //! from a single thread. -use crate::font::{MonospacedFont, font_error_to_io_error}; use crate::string_error_to_io_error; -use async_trait::async_trait; use endbasic_std::Signal; -use endbasic_std::console::drawing::{draw_circle, draw_circle_filled}; -use endbasic_std::console::graphics::{ClampedInto, ClampedMul, InputOps, RasterInfo, RasterOps}; -use endbasic_std::console::{ - CharsXY, ClearType, Console, GraphicsConsole, Key, PixelsXY, RGB, Resolution, SizeInPixels, -}; +use endbasic_std::console::graphics::{ClampedInto, ClampedMul}; +use endbasic_std::console::{CharsXY, Key, Resolution, SizeInPixels}; +use endbasic_std::gfx::lcd::{LcdSize, LcdXY, to_xy_size}; use sdl2::event::Event; use sdl2::keyboard::{Keycode, Mod}; -use sdl2::pixels::{Color, PixelFormatEnum}; -use sdl2::rect::{Point, Rect}; +use sdl2::pixels::PixelFormatEnum; +use sdl2::rect::Rect; use sdl2::render::{SurfaceCanvas, TextureCreator, TextureValueError, UpdateTextureError}; use sdl2::surface::{Surface, SurfaceContext}; use sdl2::video::{Window, WindowBuildError}; @@ -42,7 +38,6 @@ use std::fmt::{self, Write}; use std::io; #[cfg(test)] use std::path::Path; -use std::path::PathBuf; use std::rc::Rc; use std::sync::mpsc::{Receiver, Sender, SyncSender, TryRecvError}; use std::thread; @@ -99,26 +94,32 @@ fn window_build_error_to_io_error(e: WindowBuildError) -> io::Error { io::Error::new(kind, e) } -/// Constructs an SDL `Point` from a `PixelsXY`. -fn point_xy(xy: PixelsXY) -> Point { - Point::new(i32::from(xy.x), i32::from(xy.y)) +/// Constructs an SDL `Rect` from an `LcdXY` `origin` and an `LcdSize` `size`. +fn rect_origin_size(origin: LcdXY, size: LcdSize) -> Rect { + if cfg!(debug_assertions) { + Rect::new( + i32::try_from(origin.x).expect("Origin X must fit in i32"), + i32::try_from(origin.y).expect("Origin Y must fit in i32"), + u32::try_from(size.width).expect("Width must fit in u32"), + u32::try_from(size.height).expect("Height must fit in u32"), + ) + } else { + Rect::new(origin.x as i32, origin.y as i32, size.width as u32, size.height as u32) + } } -/// Constructs an SDL `Rect` from a `PixelsXY` `origin` and a `PixelsSize` `size`. -fn rect_origin_size(origin: PixelsXY, size: SizeInPixels) -> Rect { - Rect::new( - i32::from(origin.x), - i32::from(origin.y), - u32::from(size.width), - u32::from(size.height), +/// Computes the number of characters that fit within the given pixels `area`. +fn chars_in_area(glyph_size: LcdSize, area: SizeInPixels) -> CharsXY { + CharsXY::new( + area.width + .checked_div(u16::try_from(glyph_size.width).expect("Glyph width must fit in u16")) + .expect("Glyph size tested for non-zero during init"), + area.height + .checked_div(u16::try_from(glyph_size.height).expect("Glyph height must fit in u16")) + .expect("Glyph size tested for non-zero during init"), ) } -/// Converts our own `RGB` type to an SDL `Color`. -fn rgb_to_color(rgb: RGB) -> Color { - Color::RGB(rgb.0, rgb.1, rgb.2) -} - /// Given an SDL `event`, converts it to a `Key` event if it is a key press; otherwise, returns /// `None` for unknown events. fn parse_event(event: Event) -> Option { @@ -186,9 +187,6 @@ struct Context { #[cfg_attr(not(test), allow(unused))] sdl: Sdl, - /// Monospaced font to use in the console. - font: MonospacedFont<'static>, - /// Event pump to read keyboard events from. event_pump: EventPump, @@ -207,26 +205,17 @@ struct Context { /// Size of the console in pixels. size_pixels: SizeInPixels, - - /// Size of the console in characters. This is derived from `size_pixels` and the `font` glyph - /// metrics. - size_chars: CharsXY, - - /// Current draw color. Used only to track if we need to update the context. - draw_color: RGB, } impl Context { /// Initializes a new SDL console. /// - /// The console is sized to `resolution` pixels. Also loads the desired font from - /// `font_path` at `font_size` and uses it to calculate the size of the console in characters. + /// The console is sized to `resolution` pixels. Uses `glyph_size` to calculate the size of the + /// console in characters. /// /// There can only be one active `SdlConsole` at any given time given that this initializes and /// owns the SDL context. - fn new(resolution: Resolution, font_path: PathBuf, font_size: u16) -> io::Result { - let font = MonospacedFont::load(&font_path, font_size)?; - + fn new(resolution: Resolution, glyph_size: LcdSize) -> io::Result { let sdl = sdl2::init().map_err(string_error_to_io_error)?; let event_pump = sdl.event_pump().map_err(string_error_to_io_error)?; let video = sdl.video().map_err(string_error_to_io_error)?; @@ -259,7 +248,7 @@ impl Context { let (width, height) = window.drawable_size(); SizeInPixels::new(width.clamped_into(), height.clamped_into()) }; - let size_chars = font.chars_in_area(size_pixels); + let size_chars = chars_in_area(glyph_size, size_pixels); write!( &mut title, @@ -270,51 +259,17 @@ impl Context { window.set_title(&title).expect("There should have been no NULLs in the formatted title"); let pixel_format = window.window_pixel_format(); + let surface = Surface::new(u32::from(size_pixels.width), u32::from(size_pixels.height), pixel_format) .map_err(string_error_to_io_error)?; - let mut canvas = surface.into_canvas().map_err(string_error_to_io_error)?; + let canvas = surface.into_canvas().map_err(string_error_to_io_error)?; let texture_creator = canvas.texture_creator(); - let draw_color = RGB::default(); - canvas.set_draw_color(rgb_to_color(draw_color)); - - Ok(Self { - sdl, - font, - event_pump, - window, - canvas, - pixel_format, - texture_creator, - size_pixels, - size_chars, - draw_color, - }) - } -} - -impl RasterOps for Context { - type ID = (Vec, SizeInPixels); - - fn get_info(&self) -> RasterInfo { - RasterInfo { - size_chars: self.size_chars, - size_pixels: self.size_pixels, - glyph_size: self.font.glyph_size, - } - } - - fn set_draw_color(&mut self, color: RGB) { - if self.draw_color != color { - self.canvas.set_draw_color(rgb_to_color(color)); - self.draw_color = color; - } - } - - fn clear(&mut self) -> io::Result<()> { - self.canvas.clear(); - Ok(()) + let mut ctx = + Self { sdl, event_pump, window, canvas, pixel_format, texture_creator, size_pixels }; + ctx.present_canvas()?; + Ok(ctx) } fn present_canvas(&mut self) -> io::Result<()> { @@ -327,30 +282,15 @@ impl RasterOps for Context { window_surface.finish().map_err(string_error_to_io_error) } - fn read_pixels(&mut self, xy: PixelsXY, size: SizeInPixels) -> io::Result { - let rect = rect_origin_size(xy, size); - let data = match self.canvas.read_pixels(rect, self.pixel_format) { - Ok(data) => data, - Err(e) if e == "Can't read outside the current viewport" => { - // The SDL read pixels operation intersects the requested rect with the viewport. - // However, SDL2-compat does not like it when the two don't intersect at all. - // Fake the response so that our SDL2 code remains compatible with SDL2-compat. - vec![ - 0; - usize::from(size.width) - * usize::from(size.height) - * self.pixel_format.byte_size_per_pixel() - ] - } - Err(e) => { - return Err(string_error_to_io_error(e)); - } - }; - Ok((data, size)) + fn get_info(&self) -> (LcdSize, usize) { + let size = self.size_pixels; + let pixel_size = self.pixel_format.byte_size_per_pixel(); + (LcdSize { width: usize::from(size.width), height: usize::from(size.height) }, pixel_size) } - fn put_pixels(&mut self, xy: PixelsXY, (data, size): &Self::ID) -> io::Result<()> { - let rect = rect_origin_size(xy, *size); + fn set_data(&mut self, x1y1: LcdXY, x2y2: LcdXY, data: &[u8]) -> io::Result<()> { + let (xy, size) = to_xy_size(x1y1, x2y2); + let rect = rect_origin_size(xy, size); let mut texture = self .texture_creator .create_texture_static(None, rect.width(), rect.height()) @@ -362,85 +302,9 @@ impl RasterOps for Context { } .clamped_mul(self.pixel_format.byte_size_per_pixel()); texture.update(None, data, width).map_err(update_texture_error_to_io_error)?; - self.canvas.copy(&texture, None, rect).map_err(string_error_to_io_error) - } - - fn move_pixels( - &mut self, - x1y1: PixelsXY, - x2y2: PixelsXY, - size: SizeInPixels, - ) -> io::Result<()> { - let shifted = { - let src = self.canvas.surface(); - let mut temp = Surface::new(src.width(), src.height(), self.pixel_format) - .map_err(string_error_to_io_error)?; - let src_rect = rect_origin_size(x1y1, size); - let dst_rect = rect_origin_size(x2y2, size); - temp.fill_rect(src_rect, rgb_to_color(self.draw_color)) - .map_err(string_error_to_io_error)?; - src.blit(src_rect, &mut temp, dst_rect).map_err(string_error_to_io_error)?; - temp - }; - shifted.blit(None, self.canvas.surface_mut(), None).map_err(string_error_to_io_error)?; - Ok(()) - } - - fn write_text(&mut self, xy: PixelsXY, text: &str) -> io::Result<()> { - debug_assert!(!text.is_empty(), "SDL does not like empty strings"); - - let len = match u16::try_from(text.chars().count()) { - Ok(v) => v, - Err(_) => return Err(io::Error::new(io::ErrorKind::InvalidInput, "String too long")), - }; - - let rect = Rect::new( - i32::from(xy.x), - i32::from(xy.y), - len.clamped_mul(self.font.glyph_size.width), - u32::from(self.font.glyph_size.height), - ); - - let surface = - self.font.font.render(text).blended(self.draw_color).map_err(font_error_to_io_error)?; - let texture = self - .texture_creator - .create_texture_from_surface(&surface) - .map_err(texture_value_error_to_io_error)?; - self.canvas.copy(&texture, None, rect).map_err(string_error_to_io_error) - } - - fn draw_circle(&mut self, center: PixelsXY, radius: u16) -> io::Result<()> { - draw_circle(self, center, radius) - } - - fn draw_circle_filled(&mut self, center: PixelsXY, radius: u16) -> io::Result<()> { - draw_circle_filled(self, center, radius) - } - - fn draw_line(&mut self, x1y1: PixelsXY, x2y2: PixelsXY) -> io::Result<()> { - if x1y1 == x2y2 { - // Paper over differences between platforms. On Linux, this would paint a single dot, - // but on Windows, it paints nothing. For consistency with drawing a circle of radius - // 0, and for consistency with the web interface, avoid painting anything here. - return Ok(()); - } - - self.canvas.draw_line(point_xy(x1y1), point_xy(x2y2)).map_err(string_error_to_io_error) - } - - fn draw_pixel(&mut self, xy: PixelsXY) -> io::Result<()> { - self.canvas.draw_point(point_xy(xy)).map_err(string_error_to_io_error) - } - - fn draw_rect(&mut self, xy: PixelsXY, size: SizeInPixels) -> io::Result<()> { - let rect = rect_origin_size(xy, size); - self.canvas.draw_rect(rect).map_err(string_error_to_io_error) - } - - fn draw_rect_filled(&mut self, xy: PixelsXY, size: SizeInPixels) -> io::Result<()> { - let rect = rect_origin_size(xy, size); - self.canvas.fill_rect(rect).map_err(string_error_to_io_error) + self.canvas.copy(&texture, None, rect).map_err(string_error_to_io_error)?; + drop(texture); + self.present_canvas() } } @@ -452,17 +316,20 @@ impl SharedContext { (*self.0).borrow_mut().event_pump.poll_event() } + fn get_info(&self) -> (LcdSize, usize) { + (*self.0).borrow().get_info() + } + + fn set_data(&mut self, x1y1: LcdXY, x2y2: LcdXY, data: &[u8]) -> io::Result<()> { + (*self.0).borrow_mut().set_data(x1y1, x2y2, data) + } + #[cfg(test)] fn push_event(&mut self, ev: Event) -> io::Result<()> { let event_ss = (*self.0).borrow().sdl.event().map_err(string_error_to_io_error)?; event_ss.push_event(ev).map_err(string_error_to_io_error) } - #[cfg(test)] - fn raw_write(&mut self, text: &str, xy: PixelsXY) -> io::Result<()> { - (*self.0).borrow_mut().write_text(xy, text) - } - #[cfg(test)] fn save_bmp(&self, path: &Path) -> io::Result<()> { let ctx = (*self.0).borrow_mut(); @@ -471,101 +338,15 @@ impl SharedContext { } } -impl RasterOps for SharedContext { - type ID = (Vec, SizeInPixels); - - fn get_info(&self) -> RasterInfo { - self.0.borrow().get_info() - } - - fn set_draw_color(&mut self, color: RGB) { - (*self.0).borrow_mut().set_draw_color(color) - } - - fn clear(&mut self) -> io::Result<()> { - (*self.0).borrow_mut().clear() - } - - fn present_canvas(&mut self) -> io::Result<()> { - (*self.0).borrow_mut().present_canvas() - } - - fn read_pixels(&mut self, xy: PixelsXY, size: SizeInPixels) -> io::Result { - (*self.0).borrow_mut().read_pixels(xy, size) - } - - fn put_pixels(&mut self, xy: PixelsXY, data: &Self::ID) -> io::Result<()> { - (*self.0).borrow_mut().put_pixels(xy, data) - } - - fn move_pixels( - &mut self, - x1y1: PixelsXY, - x2y2: PixelsXY, - size: SizeInPixels, - ) -> io::Result<()> { - (*self.0).borrow_mut().move_pixels(x1y1, x2y2, size) - } - - fn write_text(&mut self, xy: PixelsXY, text: &str) -> io::Result<()> { - (*self.0).borrow_mut().write_text(xy, text) - } - - fn draw_circle(&mut self, center: PixelsXY, radius: u16) -> io::Result<()> { - (*self.0).borrow_mut().draw_circle(center, radius) - } - - fn draw_circle_filled(&mut self, center: PixelsXY, radius: u16) -> io::Result<()> { - (*self.0).borrow_mut().draw_circle_filled(center, radius) - } - - fn draw_line(&mut self, x1y1: PixelsXY, x2y2: PixelsXY) -> io::Result<()> { - (*self.0).borrow_mut().draw_line(x1y1, x2y2) - } - - fn draw_pixel(&mut self, xy: PixelsXY) -> io::Result<()> { - (*self.0).borrow_mut().draw_pixel(xy) - } - - fn draw_rect(&mut self, xy: PixelsXY, size: SizeInPixels) -> io::Result<()> { - (*self.0).borrow_mut().draw_rect(xy, size) - } - - fn draw_rect_filled(&mut self, xy: PixelsXY, size: SizeInPixels) -> io::Result<()> { - (*self.0).borrow_mut().draw_rect_filled(xy, size) - } -} - /// Representation of requests that the console host can handle. pub(crate) enum Request { Exit, - Clear(ClearType), - SetColor(Option, Option), - EnterAlt, - HideCursor, - LeaveAlt, - Locate(CharsXY), - MoveWithinLine(i16), - Print(String), - ShowCursor, - SizeChars, - SizePixels, - Write(String), - DrawCircle(PixelsXY, u16), - DrawCircleFilled(PixelsXY, u16), - DrawLine(PixelsXY, PixelsXY), - DrawPixel(PixelsXY), - DrawRect(PixelsXY, PixelsXY), - DrawRectFilled(PixelsXY, PixelsXY), - SyncNow, - SetSync(bool), + SetData(LcdXY, LcdXY, Vec), #[cfg(test)] PushEvent(Event), #[cfg(test)] - RawWrite(String, PixelsXY), - #[cfg(test)] SaveBmp(PathBuf), } @@ -573,42 +354,20 @@ pub(crate) enum Request { #[derive(Debug)] pub(crate) enum Response { Empty(io::Result<()>), - SizeChars(CharsXY), - SizePixels(SizeInPixels), - SetSync(io::Result), -} - -/// Implementation of `InputOps` that should never be used. -// TODO(jmmv): This is necessary because the host-level console implementation requires these -// methods to be present but the input operations are handled in the client-side console. This -// might mean we need a better design. -struct NoopInputOps {} - -#[async_trait(?Send)] -impl InputOps for NoopInputOps { - async fn poll_key(&mut self) -> io::Result> { - unreachable!(); - } - - async fn read_key(&mut self) -> io::Result { - unreachable!(); - } + Info((LcdSize, usize)), } /// Runs the main graphics loop. #[allow(clippy::too_many_arguments)] pub(crate) fn run( resolution: Resolution, - default_fg_color: Option, - default_bg_color: Option, - font_path: PathBuf, - font_size: u16, + glyph_size: LcdSize, request_rx: Receiver, response_tx: SyncSender, on_key_tx: Sender, signals_tx: async_channel::Sender, ) { - let ctx = match Context::new(resolution, font_path, font_size) { + let ctx = match Context::new(resolution, glyph_size) { Ok(ctx) => ctx, Err(e) => { response_tx.send(Response::Empty(Err(e))).expect("Channel must be alive"); @@ -616,14 +375,9 @@ pub(crate) fn run( } }; - let info = ctx.get_info(); let mut ctx = SharedContext(Rc::from(RefCell::from(ctx))); - let input = NoopInputOps {}; - let mut console = GraphicsConsole::new(input, ctx.clone(), default_fg_color, default_bg_color) - .expect("Console initialization must succeed"); - - response_tx.send(Response::Empty(Ok(()))).expect("Channel must be alive"); + response_tx.send(Response::Info(ctx.get_info())).expect("Channel must be alive"); let mut budget = LOOP_POLL_BUDGET; loop { @@ -634,39 +388,13 @@ pub(crate) fn run( let response = match request { Request::Exit => break, - Request::Clear(how) => Response::Empty(console.clear(how)), - Request::SetColor(fg, bg) => Response::Empty(console.set_color(fg, bg)), - Request::EnterAlt => Response::Empty(console.enter_alt()), - Request::HideCursor => Response::Empty(console.hide_cursor()), - Request::LeaveAlt => Response::Empty(console.leave_alt()), - Request::Locate(pos) => Response::Empty(console.locate(pos)), - Request::MoveWithinLine(off) => Response::Empty(console.move_within_line(off)), - Request::Print(text) => Response::Empty(console.print(&text)), - Request::ShowCursor => Response::Empty(console.show_cursor()), - Request::SizeChars => Response::SizeChars(info.size_chars), - Request::SizePixels => Response::SizePixels(info.size_pixels), - Request::Write(text) => Response::Empty(console.write(&text)), - Request::DrawCircle(center, radius) => { - Response::Empty(console.draw_circle(center, radius)) - } - Request::DrawCircleFilled(center, radius) => { - Response::Empty(console.draw_circle_filled(center, radius)) + Request::SetData(x1y1, x2y2, data) => { + Response::Empty(ctx.set_data(x1y1, x2y2, &data)) } - Request::DrawLine(x1y1, x2y2) => Response::Empty(console.draw_line(x1y1, x2y2)), - Request::DrawPixel(xy) => Response::Empty(console.draw_pixel(xy)), - Request::DrawRect(x1y1, x2y2) => Response::Empty(console.draw_rect(x1y1, x2y2)), - Request::DrawRectFilled(x1y1, x2y2) => { - Response::Empty(console.draw_rect_filled(x1y1, x2y2)) - } - Request::SyncNow => Response::Empty(console.sync_now()), - Request::SetSync(enabled) => Response::SetSync(console.set_sync(enabled)), #[cfg(test)] Request::PushEvent(ev) => Response::Empty(ctx.push_event(ev)), - #[cfg(test)] - Request::RawWrite(text, start) => Response::Empty(ctx.raw_write(&text, start)), - #[cfg(test)] Request::SaveBmp(path) => Response::Empty(ctx.save_bmp(&path)), }; diff --git a/sdl/src/lib.rs b/sdl/src/lib.rs index 7c2ef928..637d33ff 100644 --- a/sdl/src/lib.rs +++ b/sdl/src/lib.rs @@ -17,57 +17,31 @@ //! SDL2-based graphics terminal emulator. use async_channel::Sender; +use console::{SdlConsole, SdlInput}; use endbasic_std::Signal; -use endbasic_std::console::{Console, ConsoleSpec, Resolution}; +use endbasic_std::console::{Console, ConsoleSpec, GraphicsConsole, Resolution}; +use endbasic_std::gfx::lcd::BufferedLcd; +use endbasic_std::gfx::lcd::fonts::{FONT_VT220, Fonts}; use std::cell::RefCell; -use std::fs::File; -use std::io::{self, Write}; +use std::io; use std::num::NonZeroU32; -use std::path::PathBuf; use std::rc::Rc; -use tempfile::TempDir; mod console; -mod font; mod host; /// Default resolution to use when none is provided. const DEFAULT_RESOLUTION_PIXELS: (u32, u32) = (800, 600); -/// Default font to use when none is provided. -const DEFAULT_FONT_BYTES: &[u8] = include_bytes!("IBMPlexMono-Regular-6.0.0.ttf"); - -/// Default font size. -const DEFAULT_FONT_SIZE: u16 = 16; - /// Converts a flat string error message to an `io::Error`. fn string_error_to_io_error(e: String) -> io::Error { io::Error::other(e) } -/// Context to maintain a font on disk temporarily. -pub(crate) struct TempFont { - dir: TempDir, -} - -impl TempFont { - /// Gets an instance of the default font. - pub(crate) fn default_font() -> io::Result { - let dir = tempfile::tempdir()?; - let mut file = File::create(dir.path().join("font.ttf"))?; - file.write_all(DEFAULT_FONT_BYTES)?; - Ok(Self { dir }) - } - - /// Gets the path to the temporary font. - pub(crate) fn path(&self) -> PathBuf { - self.dir.path().join("font.ttf") - } -} - /// Creates the graphical console based on the given `spec`. pub fn setup( spec: &mut ConsoleSpec, + fonts: &Fonts, signals_tx: Sender, ) -> io::Result>> { let resolution: Resolution = spec.take_keyed_flag("resolution")?.unwrap_or_else(|| { @@ -79,32 +53,13 @@ pub fn setup( let default_fg_color = spec.take_keyed_flag::("fg_color")?; let default_bg_color = spec.take_keyed_flag::("bg_color")?; - let font_path = spec.take_keyed_flag::("font_path")?; + let font_name = spec.take_keyed_flag_str("font").unwrap_or(FONT_VT220.name); + let font = fonts.get(font_name)?; - let font_size = spec.take_keyed_flag("font_size")?.unwrap_or(DEFAULT_FONT_SIZE); + let (console, on_key_rx) = SdlConsole::new(resolution, font.glyph_size, signals_tx)?; + let input = SdlInput(on_key_rx); + let rasops = BufferedLcd::new(console, font); + let console = GraphicsConsole::new(input, rasops, default_fg_color, default_bg_color)?; - let console = match font_path { - None => { - let default_font = TempFont::default_font()?; - console::SdlConsole::new( - resolution, - default_fg_color, - default_bg_color, - default_font.path(), - font_size, - signals_tx, - )? - // The console has been created at this point, so it should be safe to drop - // default_font and clean up the on-disk file backing it up. - } - Some(font_path) => console::SdlConsole::new( - resolution, - default_fg_color, - default_bg_color, - font_path.to_owned(), - font_size, - signals_tx, - )?, - }; Ok(Rc::from(RefCell::from(console))) } diff --git a/std/src/gfx/lcd/mod.rs b/std/src/gfx/lcd/mod.rs index 90a093d2..c9259583 100644 --- a/std/src/gfx/lcd/mod.rs +++ b/std/src/gfx/lcd/mod.rs @@ -68,8 +68,8 @@ pub struct LcdXY { } /// Represents a size that fits in the LCD space. -#[derive(Clone, Copy)] -#[cfg_attr(test, derive(Debug, PartialEq))] +#[derive(Clone, Copy, Debug)] +#[cfg_attr(test, derive(PartialEq))] pub struct LcdSize { /// The width. pub width: usize, From c6c5c642811fe51ac79485b45890f97a4b9ee481 Mon Sep 17 00:00:00 2001 From: Julio Merino Date: Tue, 12 May 2026 20:02:59 -0700 Subject: [PATCH 5/5] Run the SDL console on the CLI main thread Split the SDL backend into a movable console factory and a host loop that can stay on the main thread. Move the CLI to a sync entrypoint so SDL sessions run the interpreter on a background Tokio runtime while preserving the existing async paths for other consoles. --- cli/src/main.rs | 208 ++++++++++++++++++++++++++++++----- sdl/src/console.rs | 73 +++++++++--- sdl/src/host.rs | 132 +++++++++++++--------- sdl/src/lib.rs | 59 +++++++++- std/src/gfx/lcd/fonts/mod.rs | 3 +- 5 files changed, 372 insertions(+), 103 deletions(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index 658c5a38..09b3e5ce 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -29,6 +29,8 @@ use std::fs::File; use std::io; use std::path::Path; use std::rc::Rc; +#[cfg(feature = "sdl")] +use std::thread; /// Errors caused by the user when invoking this binary (invalid options or arguments). #[derive(Debug, thiserror::Error)] @@ -104,8 +106,18 @@ fn new_machine_builder( gpio_pins_spec: Option<&str>, ) -> Result { let signals_chan = async_channel::unbounded(); + let console = setup_console(console_spec, signals_chan.0.clone())?; + new_machine_builder_with_console(console, gpio_pins_spec, signals_chan) +} + +/// Creates a new EndBASIC machine builder with the preconfigured `console` and `signals_chan`. +fn new_machine_builder_with_console( + console: Rc>, + gpio_pins_spec: Option<&str>, + signals_chan: (Sender, async_channel::Receiver), +) -> Result { let mut builder = MachineBuilder::default(); - builder = builder.with_console(setup_console(console_spec, signals_chan.0.clone())?); + builder = builder.with_console(console); builder = builder.with_signals_chan(signals_chan); builder = builder.with_gpio_pins(setup_gpio_pins(gpio_pins_spec)?); Ok(builder) @@ -237,7 +249,16 @@ async fn run_repl_loop( local_drive_spec: &str, service_url: &str, ) -> Result { - let mut builder = new_machine_builder(console_spec, gpio_pins_spec)?; + let builder = new_machine_builder(console_spec, gpio_pins_spec)?; + run_repl_loop_with_builder(builder, local_drive_spec, service_url).await +} + +/// Enters the interactive interpreter with a preconfigured `builder`. +async fn run_repl_loop_with_builder( + mut builder: MachineBuilder, + local_drive_spec: &str, + service_url: &str, +) -> Result { let console = builder.get_console(); let program = Rc::from(RefCell::from(endbasic_repl::editor::Editor::default())); let storage = Rc::from(RefCell::from(Storage::default())); @@ -269,6 +290,11 @@ async fn run_script>( gpio_pins_spec: Option<&str>, ) -> Result { let builder = new_machine_builder(console_spec, gpio_pins_spec)?; + run_script_with_builder(path, builder).await +} + +/// Executes the `path` program in a fresh machine with a preconfigured `builder`. +async fn run_script_with_builder>(path: P, builder: MachineBuilder) -> Result { let mut machine = builder.build(); let mut input = File::open(path)?; @@ -296,7 +322,17 @@ async fn run_interactive( local_drive_spec: &str, service_url: &str, ) -> Result { - let mut builder = new_machine_builder(console_spec, gpio_pins_spec)?; + let builder = new_machine_builder(console_spec, gpio_pins_spec)?; + run_interactive_with_builder(path, builder, local_drive_spec, service_url).await +} + +/// Executes the `path` program in a fresh interactive machine with a preconfigured `builder`. +async fn run_interactive_with_builder( + path: &str, + mut builder: MachineBuilder, + local_drive_spec: &str, + service_url: &str, +) -> Result { let console = builder.get_console(); let program = Rc::from(RefCell::from(endbasic_repl::editor::Editor::default())); let storage = Rc::from(RefCell::from(Storage::default())); @@ -358,43 +394,155 @@ fn app_build(builder: Builder) -> Builder { .extra_help(app_extra_help) } -async fn app_main(matches: Matches) -> Result { - let console_spec = matches.opt_str("console"); +enum AppMode { + Repl, + RunInteractive(String), + RunScript(String), +} - let gpio_pins_spec = matches.opt_str("gpio-pins"); +fn parse_app_mode(matches: &Matches) -> Result { + match matches.arg_trail() { + [] => Ok(AppMode::Repl), + [file] => { + if matches.opt_present("interactive") { + Ok(AppMode::RunInteractive((*file).to_owned())) + } else { + Ok(AppMode::RunScript((*file).to_owned())) + } + } + [_, ..] => Err(UsageError::new("Too many arguments").into()), + } +} - let service_url = matches - .opt_str("service-url") - .unwrap_or_else(|| endbasic_client::PROD_API_ADDRESS.to_owned()); +fn uses_sdl_console(console_spec: Option<&str>) -> bool { + ConsoleSpec::init(console_spec.unwrap_or("text")).driver == "sdl" +} - match matches.arg_trail() { - [] => { - let local_drive = get_local_drive_spec(matches.opt_str("local-drive"))?; - Ok(run_repl_loop( +async fn app_main_async( + console_spec: Option, + gpio_pins_spec: Option, + local_drive_spec: Option, + service_url: String, + mode: AppMode, +) -> Result { + match mode { + AppMode::Repl => { + let local_drive = get_local_drive_spec(local_drive_spec)?; + run_repl_loop( console_spec.as_deref(), gpio_pins_spec.as_deref(), &local_drive, &service_url, ) - .await?) + .await } - [file] => { - if matches.opt_present("interactive") { - let local_drive = get_local_drive_spec(matches.opt_str("local-drive"))?; - Ok(run_interactive( - file, - console_spec.as_deref(), - gpio_pins_spec.as_deref(), - &local_drive, - &service_url, - ) - .await?) - } else { - Ok(run_script(file, console_spec.as_deref(), gpio_pins_spec.as_deref()).await?) - } + AppMode::RunInteractive(file) => { + let local_drive = get_local_drive_spec(local_drive_spec)?; + run_interactive( + &file, + console_spec.as_deref(), + gpio_pins_spec.as_deref(), + &local_drive, + &service_url, + ) + .await } - [_, ..] => Err(UsageError::new("Too many arguments").into()), + AppMode::RunScript(file) => { + run_script(&file, console_spec.as_deref(), gpio_pins_spec.as_deref()).await + } + } +} + +#[cfg(feature = "sdl")] +fn run_sdl_app( + console_spec: Option, + gpio_pins_spec: Option, + local_drive_spec: Option, + service_url: String, + mode: AppMode, +) -> Result { + let signals_chan = async_channel::unbounded(); + let mut console_spec_parser = ConsoleSpec::init(console_spec.as_deref().unwrap_or("text")); + let (console_factory, host) = endbasic_sdl::prepare( + &mut console_spec_parser, + &endbasic_std::gfx::lcd::fonts::Fonts::all(), + signals_chan.0.clone(), + )?; + console_spec_parser.finish().map_err(|e| { + io::Error::new(io::ErrorKind::InvalidInput, format!("Invalid --console flag: {}", e)) + })?; + + let interpreter = thread::spawn(move || -> Result { + let runtime = tokio::runtime::Builder::new_current_thread().enable_all().build()?; + let console = console_factory.connect()?; + let builder = + new_machine_builder_with_console(console, gpio_pins_spec.as_deref(), signals_chan)?; + runtime.block_on(async move { + match mode { + AppMode::Repl => { + run_repl_loop_with_builder( + builder, + local_drive_spec.as_deref().expect("Missing local drive"), + &service_url, + ) + .await + } + AppMode::RunInteractive(path) => { + run_interactive_with_builder( + &path, + builder, + local_drive_spec.as_deref().expect("Missing local drive"), + &service_url, + ) + .await + } + AppMode::RunScript(path) => run_script_with_builder(path, builder).await, + } + }) + }); + + host.run(); + interpreter.join().expect("Interpreter thread should not have panicked") +} + +#[cfg(not(feature = "sdl"))] +fn run_sdl_app( + _console_spec: Option, + _gpio_pins_spec: Option, + _local_drive_spec: Option, + _service_url: String, + _mode: AppMode, +) -> Result { + Err(UsageError::new("--console=sdl requires the sdl feature to be compiled in").into()) +} + +fn app_main(matches: Matches) -> Result { + let console_spec = matches.opt_str("console"); + let gpio_pins_spec = matches.opt_str("gpio-pins"); + let service_url = matches + .opt_str("service-url") + .unwrap_or_else(|| endbasic_client::PROD_API_ADDRESS.to_owned()); + let local_drive_spec = matches.opt_str("local-drive"); + let mode = parse_app_mode(&matches)?; + + if uses_sdl_console(console_spec.as_deref()) { + let local_drive_spec = match &mode { + AppMode::Repl | AppMode::RunInteractive(_) => { + Some(get_local_drive_spec(local_drive_spec)?) + } + AppMode::RunScript(_) => None, + }; + run_sdl_app(console_spec, gpio_pins_spec, local_drive_spec, service_url, mode) + } else { + let runtime = tokio::runtime::Runtime::new()?; + runtime.block_on(app_main_async( + console_spec, + gpio_pins_spec, + local_drive_spec, + service_url, + mode, + )) } } -tokio_app!("EndBASIC", app_build, app_main); +app!("EndBASIC", app_build, app_main); diff --git a/sdl/src/console.rs b/sdl/src/console.rs index 48f63afa..d0bc09e6 100644 --- a/sdl/src/console.rs +++ b/sdl/src/console.rs @@ -27,6 +27,36 @@ use std::io; use std::sync::mpsc::{self, Receiver, SyncSender, TryRecvError}; use std::thread::{self, JoinHandle}; +/// Transport used by a client-side SDL console to connect to a running host loop. +pub(crate) struct SdlConsoleClient { + request_tx: SyncSender, + response_rx: Receiver, + on_key_rx: Receiver, +} + +impl SdlConsoleClient { + /// Binds this client transport to a running host loop. + pub(crate) fn connect(self) -> io::Result<(SdlConsole, Receiver)> { + let Self { request_tx, response_rx, on_key_rx } = self; + match response_rx.recv().expect("Channel must be alive") { + Response::Empty(Err(e)) => Err(e), + Response::Info(info) => { + Ok((Self::make_console(None, request_tx, response_rx, info), on_key_rx)) + } + r => panic!("Unexpected response {:?}", r), + } + } + + fn make_console( + handle: Option>, + request_tx: SyncSender, + response_rx: Receiver, + info: (LcdSize, usize), + ) -> SdlConsole { + SdlConsole { handle, request_tx, response_rx, info } + } +} + /// Implementation of the EndBASIC console on top of an SDL2 window. /// /// This is only the "client" part of the console, which implements the `Console` trait and @@ -46,26 +76,41 @@ impl SdlConsole { /// /// There can only be one active `SdlConsole` at any given time given that this initializes and /// owns the SDL context. - pub(crate) fn new( + pub(crate) fn prepare( resolution: Resolution, glyph_size: LcdSize, signals_tx: Sender, - ) -> io::Result<(Self, Receiver)> { + ) -> (SdlConsoleClient, host::SdlHost) { let (request_tx, request_rx) = mpsc::sync_channel(1); let (response_tx, response_rx) = mpsc::sync_channel(1); let (on_key_tx, on_key_rx) = mpsc::channel(); + let client = SdlConsoleClient { request_tx, response_rx, on_key_rx }; + let host = host::SdlHost::new( + resolution, + glyph_size, + request_rx, + response_tx, + on_key_tx, + signals_tx, + ); + (client, host) + } + + pub(crate) fn new( + resolution: Resolution, + glyph_size: LcdSize, + signals_tx: Sender, + ) -> io::Result<(Self, Receiver)> { + let (client, host) = Self::prepare(resolution, glyph_size, signals_tx); let handle = thread::spawn(move || { - host::run(resolution, glyph_size, request_rx, response_tx, on_key_tx, signals_tx); + host.run(); }); // Wait for the console to be up and running. We must do this for error propagation but // also to ensure that the caller can free up the local temporary font resources, if any. - match response_rx.recv().expect("Channel must be alive") { - Response::Info(info) => { - Ok((Self { handle: Some(handle), request_tx, response_rx, info }, on_key_rx)) - } - r => panic!("Unexpected response {:?}", r), - } + let (mut console, on_key_rx) = client.connect()?; + console.handle = Some(handle); + Ok((console, on_key_rx)) } /// Issues a synchronous call against the console host for a request that returns nothing but @@ -82,12 +127,10 @@ impl SdlConsole { impl Drop for SdlConsole { fn drop(&mut self) { - self.request_tx.send(Request::Exit).expect("Channel must be alive"); - self.handle - .take() - .expect("Handle must always be present") - .join() - .expect("Thread should not have panicked"); + let _ = self.request_tx.send(Request::Exit); + if let Some(handle) = self.handle.take() { + handle.join().expect("Thread should not have panicked"); + } } } diff --git a/sdl/src/host.rs b/sdl/src/host.rs index b5c9eb88..d7a6abe2 100644 --- a/sdl/src/host.rs +++ b/sdl/src/host.rs @@ -38,6 +38,8 @@ use std::fmt::{self, Write}; use std::io; #[cfg(test)] use std::path::Path; +#[cfg(test)] +use std::path::PathBuf; use std::rc::Rc; use std::sync::mpsc::{Receiver, Sender, SyncSender, TryRecvError}; use std::thread; @@ -357,81 +359,103 @@ pub(crate) enum Response { Info((LcdSize, usize)), } -/// Runs the main graphics loop. -#[allow(clippy::too_many_arguments)] -pub(crate) fn run( +/// Data necessary to host the SDL loop on the current thread. +pub(crate) struct SdlHost { resolution: Resolution, glyph_size: LcdSize, request_rx: Receiver, response_tx: SyncSender, on_key_tx: Sender, signals_tx: async_channel::Sender, -) { - let ctx = match Context::new(resolution, glyph_size) { - Ok(ctx) => ctx, - Err(e) => { - response_tx.send(Response::Empty(Err(e))).expect("Channel must be alive"); - return; - } - }; +} - let mut ctx = SharedContext(Rc::from(RefCell::from(ctx))); +impl SdlHost { + /// Creates a new SDL host that must be run on the desired thread. + pub(crate) fn new( + resolution: Resolution, + glyph_size: LcdSize, + request_rx: Receiver, + response_tx: SyncSender, + on_key_tx: Sender, + signals_tx: async_channel::Sender, + ) -> Self { + Self { resolution, glyph_size, request_rx, response_tx, on_key_tx, signals_tx } + } - response_tx.send(Response::Info(ctx.get_info())).expect("Channel must be alive"); + /// Runs the SDL graphics loop on the current thread. + pub(crate) fn run(self) { + let Self { resolution, glyph_size, request_rx, response_tx, on_key_tx, signals_tx } = self; - let mut budget = LOOP_POLL_BUDGET; - loop { - let mut did_something = false; + let ctx = match Context::new(resolution, glyph_size) { + Ok(ctx) => ctx, + Err(e) => { + response_tx.send(Response::Empty(Err(e))).expect("Channel must be alive"); + return; + } + }; - match request_rx.try_recv() { - Ok(request) => { - let response = match request { - Request::Exit => break, + let mut ctx = SharedContext(Rc::from(RefCell::from(ctx))); - Request::SetData(x1y1, x2y2, data) => { - Response::Empty(ctx.set_data(x1y1, x2y2, &data)) - } + response_tx.send(Response::Info(ctx.get_info())).expect("Channel must be alive"); - #[cfg(test)] - Request::PushEvent(ev) => Response::Empty(ctx.push_event(ev)), + let mut budget = LOOP_POLL_BUDGET; + loop { + let mut did_something = false; - #[cfg(test)] - Request::SaveBmp(path) => Response::Empty(ctx.save_bmp(&path)), - }; + match request_rx.try_recv() { + Ok(request) => { + let response = match request { + Request::Exit => break, - // TODO(jmmv): This is inefficient. Most of the operations above could probably - // benefit from _not_ returning a response at all, being asynchronous from the - // client perspective -- but the code is like this right now because it is adapted - // from a previous version that was synchronous. - response_tx.send(response).expect("Channel must be alive"); + Request::SetData(x1y1, x2y2, data) => { + Response::Empty(ctx.set_data(x1y1, x2y2, &data)) + } - did_something = true; + #[cfg(test)] + Request::PushEvent(ev) => Response::Empty(ctx.push_event(ev)), + + #[cfg(test)] + Request::SaveBmp(path) => Response::Empty(ctx.save_bmp(&path)), + }; + + // TODO(jmmv): This is inefficient. Most of the operations above could probably + // benefit from _not_ returning a response at all, being asynchronous from the + // client perspective -- but the code is like this right now because it is adapted + // from a previous version that was synchronous. + if response_tx.send(response).is_err() { + break; + } + + did_something = true; + } + Err(TryRecvError::Empty) => (), + Err(TryRecvError::Disconnected) => break, } - Err(TryRecvError::Empty) => (), - Err(TryRecvError::Disconnected) => panic!("Channel must be alive"), - } - if let Some(event) = ctx.poll_event() { - if let Some(key) = parse_event(event) { - if key == Key::Interrupt { - // signals_tx is an async channel because that's what the execution engine - // needs. This means that we cannot use a regular "send" here because we - // would need to await for it, which is a no-no because we are not in an - // async context. Using "try_send" should be sufficient though given that - // the channel we use is not bounded. - signals_tx.try_send(Signal::Break).expect("Channel must be alive and not full") + if let Some(event) = ctx.poll_event() { + if let Some(key) = parse_event(event) { + if key == Key::Interrupt { + // signals_tx is an async channel because that's what the execution engine + // needs. This means that we cannot use a regular "send" here because we + // would need to await for it, which is a no-no because we are not in an + // async context. Using "try_send" should be sufficient though given that + // the channel we use is not bounded. + signals_tx + .try_send(Signal::Break) + .expect("Channel must be alive and not full") + } + + if on_key_tx.send(key).is_err() { + break; + } } - on_key_tx.send(key).expect("Channel must be alive"); + did_something = true; } - did_something = true; - } - - if did_something { - budget = LOOP_POLL_BUDGET; - } else { - if budget > 0 { + if did_something { + budget = LOOP_POLL_BUDGET; + } else if budget > 0 { budget -= 1; } else { thread::sleep(Duration::from_millis(LOOP_DELAY_MS)); diff --git a/sdl/src/lib.rs b/sdl/src/lib.rs index 637d33ff..d6eace31 100644 --- a/sdl/src/lib.rs +++ b/sdl/src/lib.rs @@ -17,11 +17,11 @@ //! SDL2-based graphics terminal emulator. use async_channel::Sender; -use console::{SdlConsole, SdlInput}; +use console::{SdlConsole, SdlConsoleClient, SdlInput}; use endbasic_std::Signal; use endbasic_std::console::{Console, ConsoleSpec, GraphicsConsole, Resolution}; use endbasic_std::gfx::lcd::BufferedLcd; -use endbasic_std::gfx::lcd::fonts::{FONT_VT220, Fonts}; +use endbasic_std::gfx::lcd::fonts::{FONT_VT220, Font, Fonts}; use std::cell::RefCell; use std::io; use std::num::NonZeroU32; @@ -33,11 +33,66 @@ mod host; /// Default resolution to use when none is provided. const DEFAULT_RESOLUTION_PIXELS: (u32, u32) = (800, 600); +/// Thread-safe console factory that can be moved to the interpreter thread. +pub struct ConsoleFactory { + client: SdlConsoleClient, + font: &'static Font, + default_fg_color: Option, + default_bg_color: Option, +} + +impl ConsoleFactory { + /// Connects this factory to a running SDL host loop and returns the finished console. + pub fn connect(self) -> io::Result>> { + let Self { client, font, default_fg_color, default_bg_color } = self; + let (console, on_key_rx) = client.connect()?; + let input = SdlInput(on_key_rx); + let rasops = BufferedLcd::new(console, font); + let console = GraphicsConsole::new(input, rasops, default_fg_color, default_bg_color)?; + Ok(Rc::from(RefCell::from(console))) + } +} + +/// SDL host loop that must run on the desired UI thread. +pub struct Host { + host: host::SdlHost, +} + +impl Host { + /// Runs the SDL host loop on the current thread until shutdown. + pub fn run(self) { + self.host.run() + } +} + /// Converts a flat string error message to an `io::Error`. fn string_error_to_io_error(e: String) -> io::Error { io::Error::other(e) } +/// Parses the SDL console specification and prepares a split client/host pair. +pub fn prepare( + spec: &mut ConsoleSpec, + fonts: &Fonts, + signals_tx: Sender, +) -> io::Result<(ConsoleFactory, Host)> { + let resolution: Resolution = spec.take_keyed_flag("resolution")?.unwrap_or_else(|| { + let width = NonZeroU32::new(DEFAULT_RESOLUTION_PIXELS.0).unwrap(); + let height = NonZeroU32::new(DEFAULT_RESOLUTION_PIXELS.1).unwrap(); + Resolution::Windowed((width, height)) + }); + + let default_fg_color = spec.take_keyed_flag::("fg_color")?; + let default_bg_color = spec.take_keyed_flag::("bg_color")?; + + let font_name = spec.take_keyed_flag_str("font").unwrap_or(FONT_VT220.name); + let font = fonts.get(font_name)?; + + let (client, host) = SdlConsole::prepare(resolution, font.glyph_size, signals_tx); + let factory = ConsoleFactory { client, font, default_fg_color, default_bg_color }; + Ok((factory, Host { host })) +} + /// Creates the graphical console based on the given `spec`. pub fn setup( spec: &mut ConsoleSpec, diff --git a/std/src/gfx/lcd/fonts/mod.rs b/std/src/gfx/lcd/fonts/mod.rs index f10bd3cc..d20adf7a 100644 --- a/std/src/gfx/lcd/fonts/mod.rs +++ b/std/src/gfx/lcd/fonts/mod.rs @@ -84,8 +84,7 @@ impl Fonts { Err(io::Error::new( io::ErrorKind::InvalidInput, format!("Unknown font: {}; valid names are: {}", name, valid.join(", ")), - ) - .into()) + )) } } }