From 453dc5e570acf13c3326132d10a5fdc9cf3abfb5 Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Fri, 17 Jul 2026 19:38:51 +0200 Subject: [PATCH] feat(admin): typed Radio/Routing field editors; pre-release audit fixes Tools > Admin: split the Radio tab's single "f,bw,sf,cr" comma-string field (free-text keyboard) into 4 independent, type-appropriate rows -- Frequency (same digit-cursor DigitEditor Settings/Repeater use locally), Bandwidth/Spreading factor/Coding rate (discrete-set LEFT/RIGHT stepping), plus TX power and the 3 Routing numeric fields as number steppers and Repeat as an ON/OFF toggle. Edits happen locally (zero mesh traffic per keystroke); only Enter sends one combined `set`, Cancel sends nothing. Full pre-release audit of all 16 commits since v1.22 (5 parallel focus areas: display/font, keyboard/messages, Nodes/Admin, Bot/channels, UITask+NodePrefs schema) turned up and fixed: - Admin: fetched FK_NUMBER values weren't clamped to the field's range, so a value already out-of-range could get stuck unreachable; fixed commit-time float format noise (%.6f -> %.3f); Cancel/failed-login always returned to the Nodes picker even when Admin was opened directly from a node's Hold-Enter action -- now returns to wherever it was actually opened from (AdminScreen::_from_picker). - Keyboard: one-shot Shift was consumed after the *first* T9 multi-tap, so cycling to the 2nd/3rd candidate always came out lowercase -- fixed by caching the cycle's caps state (t9_caps). - Messages: history is numbered newest-first, so a message arriving while scrolled up to an older one silently relabeled the view onto a different message -- selection now shifts with the insert. - Channels: onChannelRemoved() didn't clear ch_notif_override/ ch_notif_muted/ch_fav_bitmask despite its own contract comment requiring it (now far more reachable via the on-device Delete); an all-zero hex secret silently self-deleted the channel it was just saved into (collides with the empty-slot sentinel) -- rejected. - Room login: isRoomLoggedIn() indexed the login-tracking ring directly instead of via its head offset, silently wrong once the ring wraps (8+ rooms/session) -- the new on-device Logout depends on this being right. - Font/display: removed the now fully-inert Settings > Display > Font toggle and the dead LemonFont.h (retired by the earlier misc-fixed font unification, zero remaining includes); fixed a copy-pasted "5x7" comment (font is 6x9), two meaningless dead ternaries, and an OLED/e-ink inconsistency in the undefined-glyph fallback box offset. - AdminField's `kind`/bounds fields no longer rely on default member initializers inside aggregate-init: this toolchain's actual nRF52 build (unlike env:native) has no explicit -std= override, so it predates C++14's aggregate-with-default-member-initializer rule. Given an explicit constructor instead -- portable regardless of standard, all existing field-table literals unchanged. Docs updated to match: tools_screen.md (Diagnostics as a 3-tab carousel, was documented as one flat screen), message_screen.md (chat bubbles, newest-at-bottom, cursor mode, secret validation), settings_screen.md (dropped the dead Font row), solo_ui_framework.md (header menu_hint signatures, icon priority-drop, KeyboardWidget's T9/alphabets/cursor-mode). release-notes.md gains the v1.23 section covering all of the above plus the other 15 commits since v1.22. Build-verified on WioTrackerL1_companion_solo_dual. Co-Authored-By: Claude Sonnet 5 --- docs/design/solo_ui_framework.md | 34 +- .../message_screen/message_screen.md | 8 +- .../settings_screen/settings_screen.md | 3 +- .../tools_screen/tools_screen.md | 19 +- examples/companion_radio/MyMesh.h | 2 +- examples/companion_radio/NodePrefs.h | 10 +- examples/companion_radio/ui-new/AdminScreen.h | 267 ++- .../companion_radio/ui-new/ChannelsView.h | 10 +- .../companion_radio/ui-new/KeyboardWidget.h | 14 +- .../companion_radio/ui-new/MessagesScreen.h | 22 +- .../companion_radio/ui-new/NearbyScreen.h | 4 +- .../companion_radio/ui-new/SettingsScreen.h | 13 - examples/companion_radio/ui-new/UITask.cpp | 16 +- examples/companion_radio/ui-new/UITask.h | 2 +- release-notes.md | 43 +- src/helpers/ui/GxEPDDisplay.h | 2 +- src/helpers/ui/LemonFont.h | 1523 ----------------- src/helpers/ui/SH1106Display.cpp | 2 +- src/helpers/ui/SH1106Display.h | 6 +- tools/bdf2gfx.py | 1 - 20 files changed, 396 insertions(+), 1605 deletions(-) delete mode 100644 src/helpers/ui/LemonFont.h diff --git a/docs/design/solo_ui_framework.md b/docs/design/solo_ui_framework.md index 9dd09df1..c739fe6c 100644 --- a/docs/design/solo_ui_framework.md +++ b/docs/design/solo_ui_framework.md @@ -95,8 +95,14 @@ pixel sizes** — derive everything from these: Drawing helpers (all clip/measure for you): -- `drawCenteredHeader(title)` — plain centered title + separator. -- `drawInvertedHeader(label)` — filled title bar (used by detail views). +- `drawCenteredHeader(title, menu_hint=false, menu_open=false)` — plain centered + title + separator. +- `drawInvertedHeader(label, menu_hint=false, menu_open=false)` — filled title + bar (used by detail views). +- Both take an optional `menu_hint`: pass `true` on a screen with a Hold-Enter + context menu to reserve a `≡` glyph (`menuHintWidth()`/`drawContextMenuHint()`) + in the header, so the menu is discoverable without already knowing the + shortcut; `menu_open` highlights it while the menu is actually up. - `drawSelectionRow(x, y, w, h, sel)` — the highlight bar behind a list row. - `drawTextEllipsized(x, y, max_w, str)` — truncates with `…`; **use this for any user string** (names, labels) so long/UTF-8 text can't overrun. @@ -167,6 +173,19 @@ sensor tokens) via `addPlaceholder()` / `clearPlaceholders()`; the shared `kbAddSensorPlaceholders()` (`ui-new/SensorPlaceholders.h`) adds only the tokens the board's sensors actually provide. Expand them with `expandMsg()` at send time. +Two layouts share every grid: **ABC** (one key per letter) and **T9** +(phone-keypad multi-tap — repeated Enter within `KB_T9_TIMEOUT_MS` cycles a +cell's letter group, then its digit). `NodePrefs::keyboard_alt_alphabet` adds a +non-Latin alphabet's own page to the cycle (Latin → alphabet → Symbols → +Latin); each alphabet defines both its ABC grid (`KB_*_CHARS`) and T9 group +table (`KB_T9_GROUPS_*`) so the two layouts always offer the same letters. +Shift is one-shot by default (capitalises the next letter, including whichever +candidate a T9 multi-tap cycle settles on) or Hold-Enter to toggle caps-lock; +Hold-Clear erases the whole field. Hold-Enter elsewhere in the field enters +**cursor mode** (LEFT/RIGHT move the insertion point, UP/DOWN jump to +start/end) so edits/inserts can target any point in the typed text, not just +the end. + `FullscreenMsgView::wrapLines()` is a standalone pixel-accurate word-wrapper (O(n), variable-width-font aware) reusable by any multi-line layout; it writes into the shared `s_wrap_trans` / `s_wrap_lines` scratch (single-threaded render, @@ -223,6 +242,17 @@ with a `blinkOn()` cadence for "leave it on and forget" broadcasts (auto-advert, Live Share, trail, repeater) — follow that pattern when adding an indicator: always shown on e-ink, blinking on OLED. +Icons are drawn from a fixed priority-ordered table (`HomeScreen::renderBatteryIndicator()`, +`UITask.cpp`); once the row runs out of horizontal space the loop just stops, +so the lowest-priority icons silently drop first rather than the whole bar +crushing the node name. A blinking icon still reserves its width on the +off-phase of its blink, so the row's layout can't visibly shift width as icons +blink in and out. + +Screens with a Hold-Enter context menu (Nodes, Bot, Admin, Diagnostics, …) pass +`menu_hint=true` to their header call (see §2) so a `≡` glyph advertises the +menu; `KEY_CONTEXT_MENU` (Hold-Enter) opens it. + --- ## 7. Input diff --git a/docs/solo_features/message_screen/message_screen.md b/docs/solo_features/message_screen/message_screen.md index cd65a370..f5eff567 100644 --- a/docs/solo_features/message_screen/message_screen.md +++ b/docs/solo_features/message_screen/message_screen.md @@ -18,11 +18,13 @@ The Messages screen is split into three modes — **DMs**, **Channels**, and **R | :-----------------------: | :-----------------------: | | ![](./compose_oled.png) | ![](./compose_eink.png) | -Press **Enter** on a contact or channel to open its history, then press **Enter** again (or select an empty send row) to compose a message. Choose between: +Press **Enter** on a contact or channel to open its history, then press **Enter** again (or select the **[+ send]** button, anchored at the right edge of the history) to compose a message. Choose between: - **Custom message** — opens the on-screen keyboard - **Q1–Q10** — quick reply templates editable in Settings › Messages +While typing, **Hold Enter** enters cursor mode (LEFT/RIGHT move the insertion point, UP/DOWN jump to start/end, Enter/Cancel exit) so you can edit or insert in the middle of what you've typed instead of only at the end — see the on-screen keyboard section of the [UI framework guide](../../design/solo_ui_framework.md) for the full key set (Shift, T9 multi-tap, alternate alphabets). + The keyboard supports placeholders that insert live data at send time: | Placeholder | Value | Availability | @@ -60,7 +62,7 @@ Posting to a **room server** requires a login handshake first, so the device can | :-----------------------: | :-----------------------: | | ![](./history_oled.png) | ![](./history_eink.png) | -Each entry in the history list shows the sender name and a compact age indicator (`3m`, `2h`, `>1d`) in the top-right corner. +Messages are drawn as chat bubbles sized to fit their content, anchored **right** for your own outgoing messages and **left** for incoming ones (like a typical messenger), with the sender name and a compact age indicator (`3m`, `2h`, `>1d`) in the top-right corner of each bubble. The list runs **newest at the bottom** — opening a history starts you at the latest message, and scrolling **up** goes further into the past. **Short Enter** on a message opens it in fullscreen. **Hold Enter** — on a history row or in fullscreen — opens the same options menu: Reply, plus **Navigate** / **Save waypoint** when the message contains a location (see Fullscreen message view). You don't need to open the message first. @@ -144,7 +146,7 @@ Joining a new community channel, or creating one to share with others, no longer | Secret | **LEFT/RIGHT** toggles between two entry modes; **Enter** opens the keyboard for whichever is selected | - **Passphrase** (default) — type any text; the device hashes it down to the channel's 16-byte secret. Easiest to agree on verbally, the same idea as a room password — two people who type the same passphrase end up on the same channel. -- **Hex key** — type the exact 32-hex-character secret (the format used by channel QR codes, see [QR Codes](../../qr_codes.md)), for joining a channel whose precise secret you were given rather than agreeing on a new passphrase. +- **Hex key** — type the exact 32-hex-character secret (the format used by channel QR codes, see [QR Codes](../../qr_codes.md)), for joining a channel whose precise secret you were given rather than agreeing on a new passphrase. An all-zero secret (`00…0`) is rejected ("Invalid secret") — that value is reserved internally to mark an empty channel slot. Select **[Save]** to commit. The secret can't be redisplayed once saved (only the derived key is kept) — editing it later means typing a new passphrase or hex key, the same as re-logging into a room with a new password. diff --git a/docs/solo_features/settings_screen/settings_screen.md b/docs/solo_features/settings_screen/settings_screen.md index cd8b1db8..7dea20f1 100644 --- a/docs/solo_features/settings_screen/settings_screen.md +++ b/docs/solo_features/settings_screen/settings_screen.md @@ -24,7 +24,6 @@ Press **Cancel/Back** to save and return to the home screen. | Battery | icon / % / V | Display mode for the top-bar battery indicator | | Clock seconds | show / hide | Hiding reduces OLED refresh from 1 s to 60 s | | Clock format | 24 h / 12 h | 12 h appends AM/PM | -| Font | Default / Lemon | Default: 5×7 Adafruit with ASCII transliteration; Lemon: native Unicode with pixel-accurate line wrap | | Display rotation _(e-ink only)_ | 0° / 90° / 180° / 270° | Applied immediately | | Joystick rotation _(e-ink only)_ | 0° / 90° / 180° / 270° | Rotates input mapping independently of display rotation; useful for custom enclosures | | Full refresh interval _(e-ink only)_ | off / 5 / 10 / 20 / 30 | Partial refreshes between full clears; reduces ghosting on long sessions | @@ -101,7 +100,7 @@ The **repeater** mode and its flood filters live on their own screen — see **T | Setting | Options | Notes | | -------- | ---------- | -------------------------------------------------------------------------------------------------- | | Layout | ABC / T9 | On-screen keyboard style. **ABC**: an a-b-c…z grid, one key per letter (the original layout). **T9**: phone-keypad multi-tap — each key is labelled with its **digit** and a letter group (e.g. `2abc`); repeated **Enter** presses cycle through the letters and then the digit itself. Applies to whichever alphabet page is active (see Alphabet below), not just Latin. | -| Alphabet | Latin / Cyrillic / Greek / Polish / Czech / Slovak / German / French / Spanish / Portuguese / Nordic | Which extra (non-Latin) alphabet, if any, joins the keyboard's page cycle. **Latin** (default): only the Latin letters and Symbols pages, as before. Any other choice adds that alphabet's own page to the same **#@/abc** key's cycle (Latin → alphabet → Symbols → Latin) — no separate key to switch scripts. Each Latin-diacritic language (Polish, Czech, Slovak, German, French, Spanish, Portuguese, Nordic) is its own separate page with that language's full, correct set of non-ASCII letters — e.g. Czech gets `á č ď é ě í ň ó ř š ť ú ů ý ž`, German just `ä ö ü ß`. Nordic covers `å ä æ ö ø`, shared across Danish/Norwegian/Swedish since their alphabets only differ in which of those five each one uses. **Greek** covers the 24-letter alphabet plus final sigma (`ς`) but not the tonos stress accents used in proper Modern Greek spelling. Typing in the chosen alphabet needs the **Lemon** font to actually display (Settings › Display › Font) — the keyboard forces it on temporarily while that page is open even if Font is set to Default, but received messages and everything else still follow your own Font choice. | +| Alphabet | Latin / Cyrillic / Greek / Polish / Czech / Slovak / German / French / Spanish / Portuguese / Nordic | Which extra (non-Latin) alphabet, if any, joins the keyboard's page cycle. **Latin** (default): only the Latin letters and Symbols pages, as before. Any other choice adds that alphabet's own page to the same **#@/abc** key's cycle (Latin → alphabet → Symbols → Latin) — no separate key to switch scripts. Each Latin-diacritic language (Polish, Czech, Slovak, German, French, Spanish, Portuguese, Nordic) is its own separate page with that language's full, correct set of non-ASCII letters — e.g. Czech gets `á č ď é ě í ň ó ř š ť ú ů ý ž`, German just `ä ö ü ß`. Nordic covers `å ä æ ö ø`, shared across Danish/Norwegian/Swedish since their alphabets only differ in which of those five each one uses. **Greek** covers the 24-letter alphabet plus final sigma (`ς`) but not the tonos stress accents used in proper Modern Greek spelling. Every alphabet's letters render natively — the display font (a single unified Unicode font used everywhere on-screen) covers all of them, no separate toggle needed. | Applies to every on-screen text field (messages, waypoint labels, room passwords, preset names). Earlier releases labelled the grid *QWERTY*; the layout has always been alphabetical, so it is now named **ABC**. diff --git a/docs/solo_features/tools_screen/tools_screen.md b/docs/solo_features/tools_screen/tools_screen.md index f7fead1c..3e370969 100644 --- a/docs/solo_features/tools_screen/tools_screen.md +++ b/docs/solo_features/tools_screen/tools_screen.md @@ -453,7 +453,15 @@ Each target's Commands toggle is independent — e.g. answer `!ping` in DMs but -A single read-only screen of live device and mesh stats, refreshed once a second. On a small OLED the rows scroll with **UP/DOWN**; on a larger e-ink display they all fit at once. +A circular tab carousel of live device and mesh stats, refreshed once a second (same tab idiom as Auto-Reply Bot / Nodes). **LEFT/RIGHT** switches tab; **UP/DOWN** scrolls within it on a small OLED — on a larger e-ink display a tab's rows all fit at once. + +| Tab | Shows | +| --- | ----- | +| **Live** | Live counters — see table below. | +| **System** | Static device identity: firmware version + build date, device model, node name, and the active radio parameters. | +| **Font** | A rendering test card — one sample line per script the on-device font claims to cover (Latin, diacritics, Greek, Cyrillic, digits, symbols), so its coverage can be eyeballed directly. | + +**Live** tab rows: | Row | Shows | | ------------ | -------------------------------------------------------------------------------------------------- | @@ -472,7 +480,7 @@ A single read-only screen of live device and mesh stats, refreshed once a second | Queue | Packets waiting in the outbound queue | | Errors | Radio error flags since boot/reset — `OK`, or tokens `F` (queue full), `C` (CAD timeout), `R` (RX-start timeout) | -The packet counters, **Forwarded** and **Errors** are cumulative since boot. **Hold Enter** opens a one-item *Reset counters* menu (Back dismisses it); the live readings (noise, RSSI/SNR, pool, queue, uptime) are not affected. **Cancel/Back** returns to the Tools list. +The packet counters, **Forwarded** and **Errors** are cumulative since boot. On the **Live** tab, **Hold Enter** opens a one-item *Reset counters* menu (Back dismisses it); the live readings (noise, RSSI/SNR, pool, queue, uptime) are not affected. **Cancel/Back** returns to the Tools list. The counters make the repeater behaviour observable: **Forwarded** confirms the node is actually relaying (not just configured to), and **Pool free** / **Queue** show whether forwarding is exhausting the packet pool. See **Tools › Repeater** for the relaying options. @@ -526,12 +534,13 @@ Send commands to a **repeater/room server you have admin permission on** — the | Tab | Rows | | --- | ---- | | **System** | Name, Owner info, Admin password | - | **Radio** | Radio (freq, bandwidth, spreading factor, coding rate), TX power | + | **Radio** | Frequency, Bandwidth, Spreading factor, Coding rate, TX power | | **Routing** | Repeat, Advert interval, Flood advert interval, Max hops | | **Actions** | Send advert, Send zero-hop advert, Sync clock, Reboot, **Custom command...** | - **Enter** on a row does one of three things, depending on the field: - - Most **System/Radio/Routing** rows first **fetch** the node's current value, then open the keyboard **pre-filled** with it to edit — submitting sends the change. If the fetch fails or times out, the keyboard still opens (blank), so the value can be set blind. + **Enter** on a row does one of four things, depending on the field: + - **Name / Owner info** first **fetch** the node's current value, then open the keyboard **pre-filled** with it to edit — submitting sends the change. If the fetch fails or times out, the keyboard still opens (blank), so the value can be set blind. + - **Radio and Routing rows** are typed, not free text: **Repeat** is an ON/OFF toggle; **Advert interval / Flood advert interval / Max hops / TX power** are number steppers (**LEFT/RIGHT** to adjust, within that field's valid range); **Frequency** uses the same digit-by-digit cursor editor as Settings' own Radio screen (**LEFT/RIGHT** moves between digits, **UP/DOWN** changes the selected one); **Bandwidth / Spreading factor / Coding rate** step through their valid discrete LoRa values with **LEFT/RIGHT**. All four Radio-tuple fields (Frequency/Bandwidth/SF/Coding rate) fetch and re-send the same underlying `radio` value together — editing any one of them still only overwrites that one, the other three round-trip unchanged. **Enter** sends the change; **Cancel** discards it and returns to the row list without sending anything. - **Admin password** has no fetch (there's no way to read a password back) — it opens straight to a blank keyboard. - **Actions** (Reboot, Send advert, …) send immediately, no editing step. - **Custom command...** (last row of Actions) opens the same free-text entry for anything not covered above — up to 160 characters, see the linked reference for the full grammar. The keyboard's **{}** key doubles as command completion here: it lists commands matching whatever's typed since the last space (narrowing as you type), and picking one completes that word instead of just inserting after it. diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 09f5b1d1..25f12bce 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -306,7 +306,7 @@ public: // To check if there is pending work bool hasPendingWork() const; - // Number of auto-replies sent since boot (DM + channel). Shown on BotScreen. + // Number of auto-replies sent since boot (DM + channel + room). Shown on BotScreen. uint16_t botReplyCount() const { return _bot_reply_count; } private: diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 3c0f0804..066232a3 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -77,8 +77,8 @@ struct NodePrefs { // persisted to file uint16_t low_batt_mv; // auto-shutdown threshold: 0=disabled, 3000-3500 mV uint8_t batt_display_mode; // 0=icon, 1=percent, 2=voltage char custom_msgs[10][140]; // user-defined quick messages (supports {loc}, {time}) - uint64_t ch_notif_override; // bitmask: bit i = channel i has explicit notification setting - uint64_t ch_notif_muted; // bitmask: bit i = channel i muted (only if override bit set) + uint64_t ch_notif_override; // bitmask: bit i = channel i has explicit notification setting [del→onChannelRemoved] + uint64_t ch_notif_muted; // bitmask: bit i = channel i muted (only if override bit set) [del→onChannelRemoved] uint8_t dm_show_all; // 0=favourites only (default), 1=all chat contacts uint8_t room_fav_only; // 0=all room servers (default), 1=favourites only uint8_t ringtone_bpm_idx; // index into {60,90,120,150,180} @@ -148,7 +148,7 @@ struct NodePrefs { // persisted to file uint8_t trail_min_delta_idx; // min-distance gate level (0=finest..3); metres or feet per units_imperial uint8_t trail_units_idx; // legacy: old combined speed/pace+unit index (km/h, mph, min/km, min/mi) - uint64_t ch_fav_bitmask; // bit i = channel i is marked as favourite + uint64_t ch_fav_bitmask; // bit i = channel i is marked as favourite [del→onChannelRemoved] uint8_t ch_fav_only; // 0=show all channels (default), 1=show favourites only // Global measurement system for every distance/speed shown in the UI @@ -329,8 +329,8 @@ struct NodePrefs { // persisted to file // key (Latin → alphabet → symbols → Latin), so composing in it needs no new // key, just Settings › Keyboard › Alphabet to pick which one is available. // See KeyboardWidget.h for the per-alphabet grids (KB_CYRILLIC_CHARS etc.) - // and Lemon font (src/helpers/ui/LemonFont.h) for on-screen rendering — - // every alphabet here must be in Lemon's U+0020-04FF range. + // and the misc-fixed font (src/helpers/ui/MiscFixedFont.h) for on-screen + // rendering — every alphabet here must be in its U+0020-04FF range. // // The Latin-diacritic entries (Polish..Nordic) replace what used to be a // single combined "Ext.Latin" curated subset — each is now its own full, diff --git a/examples/companion_radio/ui-new/AdminScreen.h b/examples/companion_radio/ui-new/AdminScreen.h index ead5e23c..0bc5cacf 100644 --- a/examples/companion_radio/ui-new/AdminScreen.h +++ b/examples/companion_radio/ui-new/AdminScreen.h @@ -4,13 +4,16 @@ // see docs/cli_commands.md). This device's own settings live in Settings / // Tools, not here -- Admin is purely for remote nodes. // -// Entry is always a target-then-act flow: Tools > Admin (or a repeater/room's -// Hold-Enter "Admin" action in Nodes) opens Tools > Nodes in a pick-mode -// (NearbyScreen::startPickAdminTarget(), the same borrow-another-screen's-list -// idiom the channel/bot pickers use), and picking a node calls the single -// canonical UITask::openAdminFor(ContactInfo) -> startFor(). Backing out of a -// command screen returns to that picker; backing out of the picker returns to -// Tools. +// Entry is a target-then-act flow, reached two ways: Tools > Admin opens +// Tools > Nodes in a pick-mode (NearbyScreen::startPickAdminTarget(), the same +// borrow-another-screen's-list idiom the channel/bot pickers use) and picking +// a node from it enters Admin; or a repeater/room's own Hold-Enter "Admin" +// action in Nodes enters Admin directly while browsing normally. Both call the +// single canonical UITask::openAdminFor(ContactInfo, from_picker) -> startFor(), +// which remembers which door was used: Cancel/login-failure from a command +// screen returns to the picker if that's how the user arrived, or straight +// back to Nodes if they came in directly (see returnToOrigin()); backing out +// of the picker itself returns to Tools. // // After login (session password, self-heals like room logins) a category tab // carousel of AdminField{label, get_cmd, set_prefix} rows unlocks: @@ -22,6 +25,7 @@ #include "FullscreenMsgView.h" #include "TabBar.h" +#include "RadioParamsEditor.h" // DigitEditor + stepSF/stepBW/stepCR -- same widgets Settings/Repeater use locally #include // PERM_ACL_ADMIN / PERM_ACL_ROLE_MASK class AdminScreen : public UIScreen { @@ -32,6 +36,18 @@ class AdminScreen : public UIScreen { ContactInfo _target; + // True when this visit was reached via the "Remote node..." picker + // (UITask::pickAdminTarget() -> NearbyScreen's pick-mode); false when reached + // directly from a node's own Hold-Enter "Admin" action while browsing Nodes + // normally. Determines where Cancel/failure paths send the user back to -- + // see returnToOrigin(). Set fresh by startFor() on every entry. + bool _from_picker = true; + + // Cancel/failure exit: back to the target picker if that's how we got here, + // otherwise straight back to Nodes (we were opened directly from its + // "Admin" context-menu action, so a bare pick-list would be a detour). + void returnToOrigin() { if (_from_picker) _task->pickAdminTarget(); else _task->gotoNearbyScreen(); } + // LOGIN char _login_pw[16] = ""; // True while waiting for a login result with no keyboard on screen -- either a @@ -50,7 +66,33 @@ class AdminScreen : public UIScreen { enum AdminTab : uint8_t { ATAB_SYSTEM, ATAB_RADIO, ATAB_ROUTING, ATAB_ACTIONS, ATAB_COUNT }; static const char* TAB_LABELS[ATAB_COUNT]; - struct AdminField { const char* label; const char* get_cmd; const char* set_prefix; }; + // Most fields are still free text (get reply pre-fills the keyboard, user + // retypes, "set " on submit) -- fine for names/passwords, but + // painful for values that are really a number or one of a handful of options. + // `kind` (default FK_TEXT, so every existing 3-field {label,get,set} literal + // below still compiles unchanged) opts a field into a typed, in-place editor + // instead: FK_ONOFF toggles on/off; FK_NUMBER steps an int within + // [min_val,max_val] by `step`; FK_RADIO_* are four views onto the same + // "get radio"/"set radio f,bw,sf,cr" tuple (freq via the same digit-cursor + // DigitEditor Settings/Repeater use, bw/sf/cr via RadioParamsEditor's + // discrete-set steppers) -- editing any one re-sends all four, the other + // three carried over unchanged from the fetch. See activateField()/ + // beginValueEdit()/the `_value_editing` block in handleInput(). + enum FieldKind : uint8_t { FK_TEXT, FK_ONOFF, FK_NUMBER, FK_RADIO_FREQ, FK_RADIO_BW, FK_RADIO_SF, FK_RADIO_CR }; + struct AdminField { + const char* label; const char* get_cmd; const char* set_prefix; + FieldKind kind; + float min_val, max_val, step; // FK_NUMBER only + // Explicit ctor (not default member initializers) so every existing + // 3-field {label,get,set} literal below still compiles as a converting + // constructor call -- this toolchain's actual C++ standard (the Adafruit + // nRF52 core's default; only `env:native` sets -std=c++17) predates + // C++14's aggregate-with-default-member-initializer rule, so plain + // aggregate init here fails to convert. + AdminField(const char* l, const char* g, const char* s, FieldKind k = FK_TEXT, + float mn = 0, float mx = 0, float st = 1) + : label(l), get_cmd(g), set_prefix(s), kind(k), min_val(mn), max_val(mx), step(st) {} + }; static const AdminField SYSTEM_FIELDS[]; static const AdminField RADIO_FIELDS[]; static const AdminField ROUTING_FIELDS[]; @@ -76,6 +118,15 @@ class AdminScreen : public UIScreen { bool _fetch_for_edit = false; // true while waiting on a "get" whose reply opens an editor const char* _pending_set_prefix = nullptr; // non-null => edited keyboard text gets this prefix on submit + // ── Typed value editors (FK_ONOFF/FK_NUMBER/FK_RADIO_*) ──────────────────── + bool _fetch_for_value = false; // true while waiting on a "get" whose reply feeds a typed editor + bool _value_editing = false; // true while the selected row's typed editor is live (Enter to send, Cancel to drop) + const AdminField* _edit_field = nullptr; // field currently open in _value_editing + float _edit_val = 0; // FK_ONOFF (0/1) / FK_NUMBER current value + float _radio_freq = 0, _radio_bw = 0; // last-fetched radio tuple -- kept for whichever of the + uint8_t _radio_sf = 0, _radio_cr = 0; // 4 radio sub-fields isn't the one actually being edited + DigitEditor _freq_ed; // FK_RADIO_FREQ's digit-cursor editor (same widget as Settings/Repeater) + // REPLY FullscreenMsgView _reply_view; char _reply_text[200] = ""; @@ -157,6 +208,14 @@ class AdminScreen : public UIScreen { void activateField(const AdminField& f) { _fetch_for_edit = false; _pending_set_prefix = nullptr; + if (f.kind != FK_TEXT) { // typed editor: on/off, number, or a radio sub-field + _edit_field = &f; + _fetch_for_value = true; + strncpy(_cmd_text, f.get_cmd, sizeof(_cmd_text) - 1); + _cmd_text[sizeof(_cmd_text) - 1] = '\0'; + sendCommand(); + return; + } if (f.get_cmd == nullptr && f.set_prefix == nullptr) { // Custom command... openValueKb(_cmd_text, true); } else if (f.set_prefix == nullptr) { // Action @@ -183,6 +242,73 @@ class AdminScreen : public UIScreen { openValueKb(""); } + // Splits a "get radio" reply's value ("freq,bw,sf,cr") into its 4 parts. + // Self-contained (no mesh::Utils dependency) since it only ever needs to + // parse this one, fixed 4-field shape. + static bool parseRadioReply(const char* val, float& freq, float& bw, uint8_t& sf, uint8_t& cr) { + char tmp[48]; + strncpy(tmp, val, sizeof(tmp) - 1); + tmp[sizeof(tmp) - 1] = '\0'; + char* p = tmp; + char* parts[4]; + for (int i = 0; i < 4; i++) { + parts[i] = p; + if (i < 3) { + char* c = strchr(p, ','); + if (!c) return false; + *c = '\0'; + p = c + 1; + } + } + freq = strtof(parts[0], nullptr); + bw = strtof(parts[1], nullptr); + sf = (uint8_t)atoi(parts[2]); + cr = (uint8_t)atoi(parts[3]); + return true; + } + + // Seeds the typed editor from a (already "> "-stripped) get-reply, per + // _edit_field->kind. Returns false on a malformed reply (caller falls back + // to an alert instead of entering an editor with garbage in it). + bool beginValueEdit(const char* val) { + switch (_edit_field->kind) { + case FK_ONOFF: + _edit_val = (memcmp(val, "on", 2) == 0) ? 1.0f : 0.0f; + return true; + case FK_NUMBER: + // Clamp in case the node's real value sits outside this row's declared + // range (e.g. set via a different firmware/build or the Custom command) -- + // otherwise every LEFT/RIGHT step lands out of range and gets silently + // rejected in both directions, stranding the field on an unreachable value. + _edit_val = constrain((float)atoi(val), _edit_field->min_val, _edit_field->max_val); + return true; + case FK_RADIO_FREQ: + case FK_RADIO_BW: + case FK_RADIO_SF: + case FK_RADIO_CR: + if (!parseRadioReply(val, _radio_freq, _radio_bw, _radio_sf, _radio_cr)) return false; + if (_edit_field->kind == FK_RADIO_FREQ) + _freq_ed.begin(_radio_freq, _edit_field->min_val, _edit_field->max_val, 4, 3); + return true; + default: + return false; + } + } + + // Right-column display text for the row currently in _value_editing (freq + // has its own DigitEditor::render call instead -- see render()). Matches + // RepeaterScreen's itemValue() formatting for the same quantities (plain + // %d for SF/CR, %.1f for BW) so a value reads the same wherever it's shown. + void formatEditValue(char* buf, size_t n) const { + switch (_edit_field->kind) { + case FK_ONOFF: snprintf(buf, n, "%s", _edit_val != 0 ? "ON" : "OFF"); break; + case FK_RADIO_BW: snprintf(buf, n, "%.1f", _radio_bw); break; + case FK_RADIO_SF: snprintf(buf, n, "%d", (int)_radio_sf); break; + case FK_RADIO_CR: snprintf(buf, n, "%d", (int)_radio_cr); break; + default: snprintf(buf, n, "%d", (int)_edit_val); break; // FK_NUMBER + } + } + public: explicit AdminScreen(UITask* task) : _task(task) {} @@ -196,6 +322,8 @@ public: _waiting = false; _fetch_for_edit = false; _pending_set_prefix = nullptr; + _fetch_for_value = false; + _value_editing = false; _login_waiting = false; _admin_ok = false; } @@ -204,8 +332,9 @@ public: // itself reached from NearbyScreen's "Admin" context-menu action or its // Admin-target pick-mode. Skips straight past login if this contact is already // admin-ok this visit. - void startFor(const ContactInfo& ci) { + void startFor(const ContactInfo& ci, bool from_picker) { _target = ci; + _from_picker = from_picker; if (isAdminOk(ci.id.pub_key)) { _tab = ATAB_SYSTEM; _row_sel = _row_scroll = 0; _phase = COMMAND; @@ -235,13 +364,13 @@ public: // Correct password, just insufficient permission -- leave any saved // password alone, retyping the same one won't change the outcome. _task->showAlert("Not admin on this node", 1600); - _task->pickAdminTarget(); + returnToOrigin(); } else { // Wrong/stale password -- forget it so the next attempt prompts fresh, // same self-healing behaviour as a room login. the_mesh.forgetRoomPassword(pub_key); _task->showAlert("Login failed", 1400); - _task->pickAdminTarget(); + returnToOrigin(); } } @@ -249,10 +378,25 @@ public: void onAdminReply(const uint8_t* pub_key, const char* text) { if (!_waiting || memcmp(_target.id.pub_key, pub_key, 4) != 0) return; _waiting = false; + // Every "get ..." reply comes back as "> value" (see CommonCLI::handleGetCmd) -- + // strip that CLI-decoration prefix before parsing/pre-filling from it. The + // final free-form REPLY view still shows `text` raw: action confirmations + // like "OK" never have the prefix, and a user-typed Custom "get ..." is + // meant to echo the raw wire reply verbatim, prefix included. + const char* val = (text[0] == '>' && text[1] == ' ') ? text + 2 : text; + if (_fetch_for_value) { + _fetch_for_value = false; + if (beginValueEdit(val)) { + _value_editing = true; + } else { + _task->showAlert("Fetch failed", 1400); + } + return; + } if (_fetch_for_edit) { _fetch_for_edit = false; char trimmed[161]; - strncpy(trimmed, text, sizeof(trimmed) - 1); + strncpy(trimmed, val, sizeof(trimmed) - 1); trimmed[sizeof(trimmed) - 1] = '\0'; size_t n = strlen(trimmed); while (n > 0 && (trimmed[n-1] == '\n' || trimmed[n-1] == '\r' || trimmed[n-1] == ' ')) trimmed[--n] = '\0'; @@ -268,8 +412,9 @@ public: void poll() override { if (_phase == COMMAND && _waiting && (int32_t)(millis() - _cmd_deadline_ms) >= 0) { _waiting = false; - if (_fetch_for_edit) { fallBackToBlankEdit(); _task->showAlert("Fetch failed - enter value", 1400); } - else { _task->showAlert("No response (timeout)", 1600); } + if (_fetch_for_edit) { fallBackToBlankEdit(); _task->showAlert("Fetch failed - enter value", 1400); } + else if (_fetch_for_value) { _fetch_for_value = false; _task->showAlert("Fetch failed - try again", 1400); } + else { _task->showAlert("No response (timeout)", 1600); } } } @@ -294,16 +439,26 @@ public: snprintf(title, sizeof(title), "%.23s", _target.name); display.drawCenteredHeader(title); display.setCursor(2, display.listStart()); - display.print(_fetch_for_edit ? "Fetching..." : "Waiting for reply..."); + display.print((_fetch_for_edit || _fetch_for_value) ? "Fetching..." : "Waiting for reply..."); return 500; } tabbar::draw(display, TAB_LABELS, ATAB_COUNT, _tab); int n = ROWS_PER_TAB[_tab]; drawList(display, n, _row_sel, _row_scroll, [&](int i, int y, bool sel, int reserve) { drawRowSelection(display, y, sel, reserve); - display.drawTextEllipsized(2, y, display.width() - 4 - reserve, fieldAt(_tab, i).label); + const AdminField& f = fieldAt(_tab, i); + display.drawTextEllipsized(2, y, display.width() - 4 - reserve, f.label); + if (_value_editing && sel) { // the row whose typed editor is currently open + if (f.kind == FK_RADIO_FREQ) { + _freq_ed.render(display, display.valCol(), y); + } else { + char val[16]; + formatEditValue(val, sizeof(val)); + display.drawTextRightAlign(display.width() - reserve - 2, y, val); + } + } }); - return 2000; + return _value_editing ? 50 : 2000; } // REPLY @@ -313,18 +468,18 @@ public: bool handleInput(char c) override { if (_phase == LOGIN) { if (_login_waiting) { - if (c == KEY_CANCEL) { _login_waiting = false; _task->pickAdminTarget(); } + if (c == KEY_CANCEL) { _login_waiting = false; returnToOrigin(); } return true; } auto r = kb().handleInput(c); if (r == KeyboardWidget::CANCELLED) { - _task->pickAdminTarget(); + returnToOrigin(); } else if (r == KeyboardWidget::DONE) { strncpy(_login_pw, kb().buf, sizeof(_login_pw) - 1); _login_pw[sizeof(_login_pw) - 1] = '\0'; bool sent = the_mesh.sendRoomLogin(_target, _login_pw); _task->showAlert(sent ? "Logging in..." : "Login failed", sent ? 1000 : 1500); - if (sent) _login_waiting = true; else _task->pickAdminTarget(); + if (sent) _login_waiting = true; else returnToOrigin(); // else: stay in LOGIN until onRoomLoginResult() fires above. } return true; @@ -353,14 +508,58 @@ public: } return true; } - if (_waiting) { - if (c == KEY_CANCEL) { - _waiting = false; - if (_fetch_for_edit) fallBackToBlankEdit(); + if (_value_editing) { + bool commit = false; + if (_edit_field->kind == FK_RADIO_FREQ) { + auto r = _freq_ed.handleInput(c); + if (r == DigitEditor::DONE) { _radio_freq = _freq_ed.value; commit = true; } + else if (r == DigitEditor::CANCELLED) { _value_editing = false; } + } else if (keyIsPrev(c) || keyIsNext(c)) { + int dir = keyIsNext(c) ? 1 : -1; + switch (_edit_field->kind) { + case FK_ONOFF: _edit_val = (_edit_val != 0) ? 0.0f : 1.0f; break; + case FK_NUMBER: { + float nv = _edit_val + dir * _edit_field->step; + if (nv >= _edit_field->min_val && nv <= _edit_field->max_val) _edit_val = nv; + break; + } + case FK_RADIO_BW: RadioParamsEditor::stepBW(_radio_bw, dir); break; + case FK_RADIO_SF: RadioParamsEditor::stepSF(_radio_sf, dir); break; + case FK_RADIO_CR: RadioParamsEditor::stepCR(_radio_cr, dir); break; + default: break; + } + } else if (c == KEY_ENTER) { + commit = true; + } else if (c == KEY_CANCEL) { + _value_editing = false; + } + if (commit) { + _value_editing = false; + switch (_edit_field->kind) { + case FK_RADIO_FREQ: case FK_RADIO_BW: case FK_RADIO_SF: case FK_RADIO_CR: + snprintf(_cmd_text, sizeof(_cmd_text), "%s %.3f,%.3f,%d,%d", + _edit_field->set_prefix, _radio_freq, _radio_bw, (int)_radio_sf, (int)_radio_cr); + break; + case FK_ONOFF: + snprintf(_cmd_text, sizeof(_cmd_text), "%s %s", _edit_field->set_prefix, _edit_val != 0 ? "on" : "off"); + break; + default: // FK_NUMBER + snprintf(_cmd_text, sizeof(_cmd_text), "%s %d", _edit_field->set_prefix, (int)_edit_val); + break; + } + sendCommand(); } return true; } - if (c == KEY_CANCEL) { _task->pickAdminTarget(); return true; } + if (_waiting) { + if (c == KEY_CANCEL) { + _waiting = false; + if (_fetch_for_edit) fallBackToBlankEdit(); + else if (_fetch_for_value) _fetch_for_value = false; + } + return true; + } + if (c == KEY_CANCEL) { returnToOrigin(); return true; } if (keyIsPrev(c)) { _tab = (_tab + ATAB_COUNT - 1) % ATAB_COUNT; _row_sel = _row_scroll = 0; return true; } if (keyIsNext(c)) { _tab = (_tab + 1) % ATAB_COUNT; _row_sel = _row_scroll = 0; return true; } int n = ROWS_PER_TAB[_tab]; @@ -384,15 +583,21 @@ const AdminScreen::AdminField AdminScreen::SYSTEM_FIELDS[] = { { "Owner info", "get owner.info", "set owner.info" }, { "Admin password", nullptr, "password" }, }; +// The 4 radio rows all share get_cmd/set_prefix ("get radio" / "set radio") -- +// each fetches and re-sends the whole f,bw,sf,cr tuple, only its own value +// actually editable (see beginValueEdit()/the commit switch in handleInput()). const AdminScreen::AdminField AdminScreen::RADIO_FIELDS[] = { - { "Radio (f,bw,sf,cr)", "get radio", "set radio" }, - { "TX power", "get tx", "set tx" }, + { "Frequency (MHz)", "get radio", "set radio", AdminScreen::FK_RADIO_FREQ, 150.0f, 2500.0f }, + { "Bandwidth (kHz)", "get radio", "set radio", AdminScreen::FK_RADIO_BW }, + { "Spreading factor", "get radio", "set radio", AdminScreen::FK_RADIO_SF }, + { "Coding rate", "get radio", "set radio", AdminScreen::FK_RADIO_CR }, + { "TX power (dBm)", "get tx", "set tx", AdminScreen::FK_NUMBER, -9, 30, 1 }, }; const AdminScreen::AdminField AdminScreen::ROUTING_FIELDS[] = { - { "Repeat", "get repeat", "set repeat" }, - { "Advert interval (min)", "get advert.interval", "set advert.interval" }, - { "Flood advert interval (h)", "get flood.advert.interval", "set flood.advert.interval" }, - { "Max hops", "get flood.max", "set flood.max" }, + { "Repeat", "get repeat", "set repeat", AdminScreen::FK_ONOFF }, + { "Advert interval (min)", "get advert.interval", "set advert.interval", AdminScreen::FK_NUMBER, 0, 240, 2 }, + { "Flood advert interval (h)", "get flood.advert.interval", "set flood.advert.interval", AdminScreen::FK_NUMBER, 0, 168, 1 }, + { "Max hops", "get flood.max", "set flood.max", AdminScreen::FK_NUMBER, 0, 64, 1 }, }; const AdminScreen::AdminField AdminScreen::ACTION_FIELDS[] = { { "Send advert", "advert", nullptr }, diff --git a/examples/companion_radio/ui-new/ChannelsView.h b/examples/companion_radio/ui-new/ChannelsView.h index fd6876ab..c92ef5d3 100644 --- a/examples/companion_radio/ui-new/ChannelsView.h +++ b/examples/companion_radio/ui-new/ChannelsView.h @@ -52,6 +52,14 @@ class ChannelsView { if (*end != 0) return false; tmp[i] = (uint8_t)v; } + // An all-zero 16-byte secret is the sentinel setChannelLocal() reads as + // "slot deleted" (see isAllZero() in MyMesh.cpp) -- saving one here would + // pass, then immediately trigger onChannelRemoved() on this very slot, + // silently discarding the channel and its bot/notif/favourite state. + // Reject it up front rather than let that self-delete happen invisibly. + bool all_zero = true; + for (int i = 0; i < 16 && all_zero; i++) if (tmp[i]) all_zero = false; + if (all_zero) return false; memset(out, 0, 32); memcpy(out, tmp, 16); return true; @@ -68,7 +76,7 @@ class ChannelsView { if (_name[0] == '\0') { _task->showAlert("Name required", 1200); return; } uint8_t secret[32]; if (!deriveSecret(secret)) { - _task->showAlert(_hex_mode ? "Need 32 hex chars" : "Secret required", 1400); + _task->showAlert(_hex_mode ? "Invalid secret" : "Secret required", 1400); return; } ChannelDetails ch; diff --git a/examples/companion_radio/ui-new/KeyboardWidget.h b/examples/companion_radio/ui-new/KeyboardWidget.h index 330ef3ad..50128541 100644 --- a/examples/companion_radio/ui-new/KeyboardWidget.h +++ b/examples/companion_radio/ui-new/KeyboardWidget.h @@ -45,8 +45,9 @@ static const char* const KB_T9_GROUPS[KB_PAGES][9] = { // Additional (non-Latin) keyboard alphabets — NodePrefs::keyboard_alt_alphabet // picks which one (if any) joins the Latin/symbols page cycle. Every alphabet -// here must fall inside Lemon's U+0020-04FF range (src/helpers/ui/LemonFont.h) -// since that's what actually draws these glyphs on-screen. +// here must fall inside the misc-fixed font's U+0020-04FF range +// (src/helpers/ui/MiscFixedFont.h) since that's what actually draws these +// glyphs on-screen. // // Unlike KB_CHARS (single ASCII byte per cell), these hold UTF-8 strings — // Cyrillic is 2 bytes/codepoint — so cells are `const char*`, not `char`. @@ -363,6 +364,12 @@ struct KeyboardWidget { int t9_cell = -1; int t9_cycle = 0; uint32_t t9_last_ms = 0; + // Caps state the *first* tap of the current T9 cycle applied -- reused by every + // later cycling tap on the same cell, since one-shot Shift is consumed (see + // below) right after that first tap, before the user has settled on a letter. + // Without this, cycling to the 2nd/3rd/... candidate would always render + // lowercase regardless of Shift. + bool t9_caps = false; int gridRows() const { return isT9() ? KB_T9_ROWS : KB_ROWS_CHAR; } int gridCols() const { return isT9() ? KB_T9_COLS : KB_COLS_CHAR; } @@ -765,7 +772,7 @@ struct KeyboardWidget { if (t9_cycle < glen) kbUtf8CharAt(group, t9_cycle, one); else { one[0] = (char)('1' + cell); one[1] = '\0'; } char shown[5]; - kbApplyCapsUtf8(one, caps, shown, sizeof(shown)); + kbApplyCapsUtf8(one, t9_caps, shown, sizeof(shown)); // Replace the codepoint just before the cursor (what the previous // tap inserted), preserving anything after the cursor too. int old_n = kbUtf8LastCharBytes(buf, cursor_pos); @@ -793,6 +800,7 @@ struct KeyboardWidget { buf[len] = '\0'; t9_cell = cell; t9_cycle = 0; + t9_caps = caps; // remember it for every later cycling tap on this cell if (caps && !caps_lock) caps = false; // one-shot: only this first tap gets capitalised } } diff --git a/examples/companion_radio/ui-new/MessagesScreen.h b/examples/companion_radio/ui-new/MessagesScreen.h index 7788368c..3158bd90 100644 --- a/examples/companion_radio/ui-new/MessagesScreen.h +++ b/examples/companion_radio/ui-new/MessagesScreen.h @@ -629,12 +629,22 @@ public: // the outgoing path can attach a relay seq to that exact entry. int addChannelMsg(uint8_t ch_idx, const char* text, uint32_t timestamp = 0) { bool viewing = (_phase == CHANNEL_HIST && _sel_channel_idx == (int)ch_idx); - return _history.addChannelMsg(ch_idx, text, viewing, timestamp); + int pos = _history.addChannelMsg(ch_idx, text, viewing, timestamp); + // Ring entries are numbered newest-first (0 == newest), so a new insert + // shifts every older message's index up by one. If the user has scrolled + // up to an older message (_hist_sel > 0), re-point the selection at that + // same message instead of silently relabeling a different one in under + // them. At _hist_sel <= 0 (already at newest, or -1 == compose button + // focused) there's nothing to preserve. + if (viewing && _hist_sel > 0) { _hist_sel++; _hist_scroll++; } + return pos; } void markChannelRelayed(uint32_t seq) { _history.markChannelRelayed(seq); } void addDMMsg(const uint8_t* pub_key, bool outgoing, const char* text, uint32_t sender_timestamp = 0) { + bool viewing = (_phase == DM_HIST && memcmp(_sel_contact.id.pub_key, pub_key, 4) == 0); _history.addDMMsg(pub_key, outgoing, text, sender_timestamp); + if (viewing && _dm_hist_sel > 0) { _dm_hist_sel++; _dm_hist_scroll++; } // see addChannelMsg } void markDmDelivered(uint32_t ack_crc) { _history.markDmDelivered(ack_crc); } @@ -648,8 +658,14 @@ public: int _room_login_head = 0, _room_login_count = 0; bool isRoomLoggedIn(const uint8_t* pub_key) const { - for (int i = 0; i < _room_login_count; i++) - if (memcmp(_room_login_prefix[i], pub_key, 4) == 0) return true; + // Indices are relative to _room_login_head, same as forgetRoomLoggedIn() -- + // direct 0.._room_login_count indexing only happens to work before the + // ring has wrapped once (head==0); after that it silently checks the wrong + // slots. + for (int i = 0; i < _room_login_count; i++) { + int pos = (_room_login_head + i) % ROOM_LOGIN_TABLE_SIZE; + if (memcmp(_room_login_prefix[pos], pub_key, 4) == 0) return true; + } return false; } diff --git a/examples/companion_radio/ui-new/NearbyScreen.h b/examples/companion_radio/ui-new/NearbyScreen.h index e8e4dd46..a7ff5479 100644 --- a/examples/companion_radio/ui-new/NearbyScreen.h +++ b/examples/companion_radio/ui-new/NearbyScreen.h @@ -621,7 +621,7 @@ class NearbyScreen : public UIScreen { const Entry* e = selected(); ContactInfo ci; if (e && e->contact_idx >= 0 && the_mesh.getContactByIdx(e->contact_idx, ci)) - _task->openAdminFor(ci); + _task->openAdminFor(ci, false); // direct from Nodes -- Cancel should return here, not to a pick-list break; } case ACT_SORT: break; // adjusted in-place via LEFT/RIGHT, not ENTER @@ -950,7 +950,7 @@ public: if (e && e->contact_idx >= 0 && (e->type == ADV_TYPE_REPEATER || e->type == ADV_TYPE_ROOM) && the_mesh.getContactByIdx(e->contact_idx, ci)) { _pick_admin_target = false; - _task->openAdminFor(ci); + _task->openAdminFor(ci, true); // via the picker -- Cancel should return here } // else: row isn't an eligible admin target -- ignore, stay on the picker. return true; diff --git a/examples/companion_radio/ui-new/SettingsScreen.h b/examples/companion_radio/ui-new/SettingsScreen.h index 8c99ecfe..ad739233 100644 --- a/examples/companion_radio/ui-new/SettingsScreen.h +++ b/examples/companion_radio/ui-new/SettingsScreen.h @@ -26,7 +26,6 @@ class SettingsScreen : public UIScreen { CLOCK_SECONDS, #endif CLOCK_FORMAT, - FONT, #if FEAT_DISPLAY_ROTATION_SETTING ROTATION, #endif @@ -585,10 +584,6 @@ class SettingsScreen : public UIScreen { display.print("Format"); display.setCursor(valCol(display), y); display.print((p && p->clock_12h) ? "12h" : "24h"); - } else if (item == FONT) { - display.print("Font"); - display.setCursor(valCol(display), y); - display.print((p && p->use_lemon_font) ? "Lemon" : "Default"); #if FEAT_DISPLAY_ROTATION_SETTING } else if (item == ROTATION) { display.print("Rotation"); @@ -961,14 +956,6 @@ public: _dirty = true; return true; } - if (_selected == FONT && p && (left || right || enter)) { - // Normalise (not ^=1): a stale value >1 from an older build with extra - // font modes would otherwise toggle 2<->3 and stay stuck on Lemon. - p->use_lemon_font = p->use_lemon_font ? 0 : 1; - _task->applyFont(); - _dirty = true; - return true; - } #if FEAT_DISPLAY_ROTATION_SETTING if (_selected == ROTATION && p && (left || right || enter)) { p->display_rotation = (p->display_rotation + (left ? 3 : 1)) & 3; diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index d519bf0c..e7993c85 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -1481,9 +1481,9 @@ void UITask::pickAdminTarget() { ((NearbyScreen*)nearby_screen)->startPickAdminTarget(); } -void UITask::openAdminFor(const ContactInfo& ci) { +void UITask::openAdminFor(const ContactInfo& ci, bool from_picker) { setCurrScreen(admin_screen); // runs AdminScreen::onShow()'s reset first - ((AdminScreen*)admin_screen)->startFor(ci); + ((AdminScreen*)admin_screen)->startFor(ci, from_picker); } void UITask::gotoDashboardConfig() { setCurrScreen(dashboard_config); } void UITask::gotoTrailScreen() { setCurrScreen(trail_screen); } @@ -2624,7 +2624,8 @@ void UITask::onContactRemoved(const uint8_t* pub_key) { // CONTRACT: every NodePrefs field that keys on a channel index is cleared here, // so a channel re-added at a freed slot can't inherit the old one's settings. // If you add such a field, add its cleanup below (and mark it in NodePrefs.h). -// Currently covered: bot_channel_idx, loc_share_channel_idx, ch_notif_melody_*. +// Currently covered: bot_channel_idx, loc_share_channel_idx, ch_notif_melody_*, +// ch_notif_override/ch_notif_muted, ch_fav_bitmask. void UITask::onChannelRemoved(uint8_t channel_idx) { if (!_node_prefs) return; bool changed = false; @@ -2644,6 +2645,15 @@ void UITask::onChannelRemoved(uint8_t channel_idx) { _node_prefs->ch_notif_melody_2 &= ~mask; changed = true; } + if (_node_prefs->ch_notif_override & mask) { + _node_prefs->ch_notif_override &= ~mask; + _node_prefs->ch_notif_muted &= ~mask; + changed = true; + } + if (_node_prefs->ch_fav_bitmask & mask) { + _node_prefs->ch_fav_bitmask &= ~mask; + changed = true; + } if (changed) the_mesh.savePrefs(); } diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index 2f4f882a..2b165dee 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -231,7 +231,7 @@ public: void gotoRingtoneEditor(int slot = 0); void gotoBotScreen(); void pickAdminTarget(); // Admin is remote-only: open Nodes to pick a repeater/room - void openAdminFor(const ContactInfo& ci); // canonical Admin entry for a specific target (Nodes' Hold-Enter menu or the picker above) + void openAdminFor(const ContactInfo& ci, bool from_picker); // canonical Admin entry for a specific target (Nodes' Hold-Enter menu or the picker above) void gotoNearbyScreen(); void gotoDashboardConfig(); void gotoAutoAdvertScreen(); diff --git a/release-notes.md b/release-notes.md index 59f84b33..cdaa184e 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,4 +1,45 @@ -## MeshCore Solo Companion Firmware v1.22 +## MeshCore Solo Companion Firmware v1.23 + +### What's new + +- **Nodes is now the single node hub.** "Nearby Nodes" is renamed **Nodes** and gains on-device contact management — no phone app needed: **Hold Enter** on a row opens **Add contact** (for a scanned-but-unknown node), **Favourite/Unfavourite** (pins to the first free Favourites Dial slot, shown as a star in the list), and **Delete contact** (confirm first, defaults to Cancel). The filter strip (**LEFT/RIGHT**) is now a visible, wraparound tab carousel instead of a hidden mode. The old read-only **Recent adverts** home page is retired — its passively-heard nodes now simply show up in the Nodes list (All filter) — so there's one list instead of two. +- **On-device channel management.** Messages › Channels gets a **"+ Add channel"** row and per-channel **Edit**/**Delete** — create or join a channel by typing either a passphrase (hashed to the channel secret, same idea as a room password) or the exact 32-hex-character key from a channel QR code. No phone app required. +- **Tools › Admin — administer a remote repeater/room from the device.** Log into a node you have admin rights on (same self-healing saved-password handshake as room logins) and browse its settings in category tabs — **System** (name, owner info), **Radio** (Frequency/Bandwidth/Spreading factor/Coding rate/TX power), **Routing** (Repeat/Advert interval/Flood advert interval/Max hops), **Actions** (reboot, send advert, sync clock, …) — plus a free-text **Custom command…** escape hatch (with **{}**-key command-name completion) for anything not covered. Radio and Routing fields use a type-appropriate editor instead of raw text — a digit-cursor for Frequency (the same widget Settings' own Radio screen uses locally), discrete-set stepping for Bandwidth/Spreading factor/Coding rate, plain number steppers for the rest, and an ON/OFF toggle for Repeat — adjusting a value costs no mesh traffic until you actually confirm it with **Enter** (**Cancel** sends nothing). Reachable either from **Tools › Admin** (opens the same Nodes list to pick a target) or directly via a repeater/room's own **Hold Enter › Admin** action. +- **Auto-Reply Bot gains a third target: room servers**, alongside DM and Channel, with its own trigger/reply/commands — the screen is redesigned as a circular tab carousel (**Direct/Channel/Room/Other**, **LEFT/RIGHT** to switch, **UP/DOWN** within a tab) instead of one long list. Each target's **Enable** and **Commands** toggle is now fully independent (they used to secretly share the DM tab's setting for Channel/Room too). New `{name}`/`{hops}` reply placeholders, a DM allow-list (all chat contacts or favourites only), and a dedicated stepper for Quiet Hours. +- **Keyboard: more languages, better editing.** Eight more per-language keyboards — Polish, Czech, Slovak, German, French, Spanish, Portuguese, Nordic — join Cyrillic and Greek under **Settings › Keyboard › Alphabet**, each with a linguistically complete letter set in both the ABC grid and T9 multi-tap groups. Shift is now **one-shot** by default (capitalises just the next letter; **Hold Enter** on Shift toggles the old sticky caps-lock back on), **Hold Enter** on Backspace clears the whole field, and a new **Hold Enter** cursor-positioning mode (**LEFT/RIGHT** move, **UP/DOWN** jump to start/end) lets you edit or insert anywhere in what you've typed, not just at the end. +- **Messages get a messenger-style history.** History bubbles are now sized to their content and anchored **right** (outgoing) / **left** (incoming); the list runs **newest at the bottom**, growing upward as you scroll into the past; the **[+ send]** compose button moved to the right edge to match. +- **Clock Tools: alarm repeat.** The Alarm screen gains a **Repeat** row (Off/Daily/Weekdays/Weekends) — Hour/Minute merge into one **Time** row edited with an HH:MM digit cursor, and **Repeat**/**Armed** now respond to **LEFT/RIGHT** like every other Settings field instead of Enter-only. +- **Diagnostics becomes a tab carousel** (Live/System/Font): **System** adds firmware version + build date, device model and the active radio parameters; **Font** is a rendering test card with one sample line per script the on-device font covers, so coverage can be eyeballed directly. +- **One unified display font.** OLED and e-ink now both draw a single misc-fixed 6×9 font (full Latin/Greek/Cyrillic coverage) generated by a new BDF-to-GFX converter (`tools/bdf2gfx.py`), retiring the old Default/Lemon switch and the keyboard's per-render font-swap workaround. +- **Discoverable context menus.** Every screen with a **Hold Enter** action menu now shows a small **≡** glyph in its header, which highlights while the menu is open — so the shortcut no longer has to be guessed. +- **Status bar and list polish** — background-mode icons (Bluetooth/GPS fix/alarm/mute/auto-advert/trail/live-share/repeater/battery) now drop by priority once space runs out, instead of crushing the node name down to a couple of characters; unread counts everywhere are drawn as a filled pill badge; DM/Channel history headers match every other screen's header style; the default home-page carousel is trimmed to Clock/Tools/Shutdown/Favourites/Map (Recent/Radio/BT/Advert/GPS/Sensors are still available, just opt-in under Settings › Home Pages — existing users' saved layouts are unaffected). +- **On-device room Logout** — Messages' room context menu gains a **Logout** entry (mirrors the app's own logout) alongside the existing **Login…**, forgetting the saved password so the next open prompts for one again. + +### Fixes + +- **Admin's typed Radio/Routing fields could get stuck on an unreachable value** — a field fetched from a node whose real value sat outside this screen's declared range (e.g. set by a different firmware/build) rejected every further adjustment in both directions. Fetched values are now clamped into range on entry. +- **Admin: the CLI's `"> "` reply-decoration prefix leaked into the pre-filled keyboard** when editing Name/Owner info (and the old single Radio-string field) — stripped once, consistently, before either the typed editors or the keyboard consume a reply. +- **Admin: Cancel / a failed login always dropped you onto the "pick a node" list**, even if you'd opened Admin directly from a node's own Hold Enter action while just browsing Nodes — it now returns to wherever you actually came from. +- **A message arriving while you'd scrolled up to an older one could silently swap in a different message at the same on-screen position** — history entries are numbered newest-first, so an insert used to shift every older message's index without updating your selection. The selection now shifts with it, so you keep looking at the same message. +- **One-shot Shift + T9 multi-tap didn't capitalise the letter you actually landed on** — Shift was consumed after the first tap of a cell, so cycling to the 2nd/3rd/etc. candidate always came out lowercase regardless of Shift. It now capitalises whichever candidate you settle on. +- **Deleting a channel could leave stale notification/favourite state behind** for whatever channel later got added into that freed slot — channel delete now also clears the per-channel notification override/mute and favourite flag, matching the existing cleanup for the bot/live-share/melody fields. +- **A hex channel secret of all zeros silently self-deleted the channel you'd just saved** — that value is reserved internally as the empty-slot marker. Now rejected up front ("Invalid secret") instead of failing invisibly after the save. +- **A room's logged-in status could be misreported after 8+ distinct rooms were logged into in one session** — the tracking ring's lookup didn't account for wraparound (only its insert/remove paths did), which the new on-device Logout now depends on for showing the right menu item. +- **Bot screen's tab order** now reliably starts on **Channel** even after the later room-target tabs were added (double-checked against the earlier "starts on Channel" fix, which the tab reshuffle could otherwise have silently undone). +- Reverted **corner-anchored context-menu popups** back to centred — looked bad on some screens; the header's **≡** discoverability hint stays. +- **Unread pill badge digits weren't centred** — the classic built-in OLED font always pads a measured string by one trailing column regardless of the glyph, throwing off centring math that assumed symmetric padding. +- **Status bar battery icon** stood 2px taller than its neighbours and its charge nub could drift off-centre at certain box heights — now sized and centred consistently with the rest of the row. +- Removed the now-inert **Settings › Display › Font** toggle, a leftover from the font unification above with no remaining effect. + +### Under the hood + +- **Shared `TabBar.h`** extracted from Nodes/Bot's independently duplicated tab-carousel rendering (Admin and Diagnostics are now the 3rd/4th consumers) — also fixes a tab that didn't quite fit vanishing outright instead of ellipsizing. +- **`MyMesh::setChannelLocal()`** factors out the setChannel/saveChannels/onChannelRemoved sequence previously duplicated across the BLE app's two `CMD_SET_CHANNEL` branches, now shared with the new on-device channel editor. +- **One canonical Admin entry point** — `UITask::openAdminFor()` / `AdminScreen::startFor()` — reached identically whether Admin is opened from Nodes' Hold-Enter action or the target picker. +- **`KeyboardWidget` reworked to UTF-8 codepoints** throughout (insert, backspace, T9 in-place cycling), so a multi-byte alt-alphabet character can never be split. +- **`NodePrefs` schema grew twice this cycle** — `alarm_repeat_mask`/`keyboard_alt_alphabet`, then the bot room-target/per-target-independence fields — each verified via a standalone host compile and `sizeof`/`offsetof` check, with the schema sentinel bumped both times; existing users' prefs upgrade in place with safe defaults. + + ### What's new diff --git a/src/helpers/ui/GxEPDDisplay.h b/src/helpers/ui/GxEPDDisplay.h index bbc6a8cb..a1560267 100644 --- a/src/helpers/ui/GxEPDDisplay.h +++ b/src/helpers/ui/GxEPDDisplay.h @@ -103,7 +103,7 @@ public: if (_text_sz == 4) return 6 * BIG_TEXT_SCALE; if (_text_sz == 3) return 17; if (_text_sz == 2) return 12 * sc; - return (_use_lemon ? 6 : 6) * sc; // misc-fixed 6x9 is 6px wide + return 6 * sc; // misc-fixed 6x9 is 6px wide } int getLineHeight() const override { int sc = scale(); diff --git a/src/helpers/ui/LemonFont.h b/src/helpers/ui/LemonFont.h deleted file mode 100644 index 3a81d691..00000000 --- a/src/helpers/ui/LemonFont.h +++ /dev/null @@ -1,1523 +0,0 @@ -// Lemon font, converted from lemon.bdf -// Range: U+0020–U+04FF (1074 BDF glyphs + 174 placeholders) -// Generated by bdf2lemon.py -#pragma once -#include - -static const uint8_t lemonBitmaps[] PROGMEM = { - 0xF4, 0xB6, 0x80, 0x56, 0xD5, 0xB5, 0x00, 0x27, 0x86, 0x1E, 0x20, 0xCD, 0x24, 0xB3, 0x45, 0x11, - 0x59, 0x34, 0xE0, 0x2A, 0x49, 0x11, 0x88, 0x92, 0x54, 0x96, 0xF6, 0x90, 0x21, 0x3E, 0x42, 0x00, - 0x58, 0xF0, 0xC0, 0x11, 0x22, 0x44, 0x88, 0x69, 0xDB, 0x96, 0x75, 0x50, 0xE1, 0x16, 0x8F, 0xE1, - 0x61, 0x1E, 0x99, 0x97, 0x11, 0xF8, 0xE1, 0x1E, 0x68, 0xE9, 0x96, 0xF1, 0x22, 0x44, 0x69, 0x69, - 0x96, 0x69, 0x97, 0x16, 0xD8, 0x51, 0x60, 0x2A, 0x22, 0xE3, 0x80, 0x88, 0xA8, 0xF4, 0xA0, 0x80, - 0x69, 0xBB, 0x87, 0x69, 0x9F, 0x99, 0xE9, 0xE9, 0x9E, 0x78, 0x88, 0x87, 0xE9, 0x99, 0x9E, 0xF8, - 0xE8, 0x8F, 0xF8, 0xE8, 0x88, 0x78, 0x8B, 0x97, 0x99, 0xF9, 0x99, 0xE9, 0x25, 0xC0, 0x71, 0x11, - 0x96, 0x9A, 0xCA, 0x99, 0x88, 0x88, 0x8F, 0x9F, 0xF9, 0x99, 0x9D, 0xDB, 0xB9, 0x69, 0x99, 0x96, - 0xE9, 0x9E, 0x88, 0x69, 0x99, 0x96, 0x30, 0xE9, 0x9E, 0x99, 0x78, 0x61, 0x1E, 0xF9, 0x08, 0x42, - 0x10, 0x99, 0x99, 0x97, 0x99, 0x96, 0x66, 0x99, 0x9F, 0xF9, 0x99, 0x69, 0x99, 0x8A, 0x88, 0x42, - 0x10, 0xF1, 0x24, 0x8F, 0xF2, 0x49, 0x27, 0x88, 0x44, 0x22, 0x11, 0xE4, 0x92, 0x4F, 0x22, 0xA2, - 0xF0, 0xA4, 0x79, 0x9B, 0x50, 0x88, 0xE9, 0x99, 0xE0, 0x78, 0x88, 0x70, 0x11, 0x79, 0x9B, 0x50, - 0x69, 0xF8, 0x70, 0x73, 0xC9, 0x20, 0x79, 0x9B, 0x51, 0x60, 0x88, 0xE9, 0x99, 0x90, 0x4D, 0x54, - 0x21, 0x92, 0x49, 0xC0, 0x88, 0x9A, 0xE9, 0x90, 0xD5, 0x54, 0x9F, 0xB9, 0x90, 0xE9, 0x99, 0x90, - 0x69, 0x99, 0x60, 0xE9, 0x99, 0xE8, 0x80, 0x79, 0x99, 0x71, 0x10, 0xBC, 0x88, 0x80, 0x78, 0x61, - 0xE0, 0x5D, 0x24, 0x40, 0x99, 0x99, 0x70, 0x99, 0x96, 0x60, 0x99, 0x9F, 0x90, 0x99, 0x69, 0x90, - 0x99, 0x99, 0x71, 0x60, 0xF2, 0x48, 0xF0, 0x29, 0x6C, 0x91, 0xFF, 0x89, 0x36, 0x94, 0x45, 0x6A, - 0x20, 0x6F, 0xF6, 0xBC, 0x17, 0xAA, 0xA7, 0x40, 0x32, 0x11, 0xC4, 0x27, 0xC0, 0x8B, 0x94, 0xA7, - 0x44, 0x8A, 0x88, 0xE2, 0x38, 0x80, 0xEE, 0x34, 0x69, 0x96, 0x2C, 0xA0, 0x7A, 0x1B, 0x69, 0xB6, - 0x17, 0x80, 0x79, 0x97, 0x5A, 0x50, 0xF8, 0x42, 0xE0, 0x7A, 0x1B, 0xED, 0xAE, 0x17, 0x80, 0xE0, - 0x55, 0x00, 0x27, 0x20, 0x70, 0xC5, 0x4E, 0xC7, 0x1C, 0x60, 0x99, 0x99, 0xE8, 0x7D, 0x6A, 0xD2, - 0x94, 0xF0, 0x47, 0x80, 0x75, 0x69, 0x96, 0xA2, 0xA8, 0x46, 0x10, 0xA3, 0x29, 0xE2, 0x46, 0x10, - 0xE0, 0x88, 0xE0, 0xC0, 0x84, 0x0A, 0xD8, 0xA3, 0xC2, 0x41, 0x4B, 0xC0, 0x42, 0x69, 0x9F, 0x99, - 0x24, 0x69, 0x9F, 0x99, 0x25, 0x06, 0x99, 0xF9, 0x90, 0x5A, 0x69, 0x9F, 0x99, 0x48, 0x0C, 0x94, - 0xBD, 0x29, 0x69, 0x69, 0x9F, 0x99, 0x7E, 0x49, 0xBC, 0x92, 0x70, 0x78, 0x88, 0x87, 0x24, 0x42, - 0xF8, 0xE8, 0x8F, 0x24, 0xF8, 0xE8, 0x8F, 0x4A, 0xF8, 0xE8, 0x8F, 0xA0, 0xF8, 0xE8, 0x8F, 0x88, - 0x74, 0x92, 0xE0, 0x28, 0x74, 0x92, 0xE0, 0x54, 0x74, 0x92, 0xE0, 0xA3, 0xA4, 0x97, 0x72, 0x7A, - 0x94, 0xB8, 0x2A, 0x80, 0xE4, 0xA5, 0x29, 0x84, 0x06, 0x99, 0x96, 0x24, 0x06, 0x99, 0x96, 0x4A, - 0x06, 0x99, 0x96, 0x5A, 0x06, 0x99, 0x96, 0x48, 0x0C, 0x94, 0xA4, 0xC0, 0xAA, 0x80, 0x35, 0x65, - 0x9A, 0x6A, 0xC0, 0x84, 0x99, 0x99, 0x97, 0x12, 0x99, 0x99, 0x97, 0x25, 0x09, 0x99, 0x99, 0x70, - 0x50, 0x99, 0x99, 0x97, 0x11, 0x22, 0xA2, 0x10, 0x84, 0x88, 0xE9, 0x9E, 0x80, 0x69, 0xA9, 0x9A, - 0x80, 0x84, 0x07, 0x99, 0xB5, 0x24, 0x07, 0x99, 0xB5, 0x4A, 0x07, 0x99, 0xB5, 0x5A, 0x07, 0x99, - 0xB5, 0x90, 0x79, 0x9B, 0x50, 0x20, 0x79, 0x9B, 0x50, 0x75, 0x6F, 0x45, 0x80, 0x78, 0x88, 0x72, - 0x40, 0x84, 0x06, 0x9F, 0x87, 0x24, 0x06, 0x9F, 0x87, 0x4A, 0x06, 0x9F, 0x87, 0xA0, 0x69, 0xF8, - 0x70, 0x93, 0x55, 0x28, 0x64, 0x92, 0x54, 0x64, 0x92, 0xA3, 0x24, 0x90, 0xA6, 0x97, 0x99, 0x96, - 0x5A, 0x0E, 0x99, 0x99, 0x41, 0x00, 0x64, 0xA5, 0x26, 0x12, 0x06, 0x99, 0x96, 0x4A, 0x06, 0x99, - 0x96, 0x5A, 0x06, 0x99, 0x96, 0x90, 0x69, 0x99, 0x60, 0x20, 0x3E, 0x02, 0x00, 0x35, 0x67, 0x9A, - 0xB0, 0x84, 0x09, 0x99, 0x97, 0x11, 0x00, 0x94, 0xA5, 0x27, 0x4A, 0x09, 0x99, 0x97, 0x50, 0x12, - 0x94, 0xA4, 0xE0, 0x24, 0x09, 0x99, 0x97, 0x16, 0x88, 0xE9, 0x99, 0xE8, 0x80, 0x50, 0x99, 0x99, - 0x71, 0x60, 0xF0, 0x69, 0x9F, 0x99, 0xF0, 0x79, 0x9B, 0x50, 0x96, 0x06, 0x99, 0xF9, 0x90, 0x96, - 0x07, 0x99, 0xB5, 0x64, 0xA5, 0xE9, 0x48, 0x20, 0x74, 0xA5, 0x65, 0x04, 0x24, 0x07, 0x88, 0x88, - 0x70, 0x24, 0x07, 0x88, 0x87, 0x25, 0x07, 0x88, 0x88, 0x70, 0x25, 0x07, 0x88, 0x87, 0x20, 0x78, - 0x88, 0x87, 0x20, 0x78, 0x88, 0x70, 0x52, 0x07, 0x88, 0x88, 0x70, 0x52, 0x07, 0x88, 0x87, 0xA4, - 0x0E, 0x99, 0x99, 0xE0, 0x04, 0x51, 0x9C, 0x92, 0x4B, 0x14, 0x72, 0x7A, 0x94, 0xB8, 0x38, 0x9D, - 0x29, 0x59, 0x40, 0x60, 0xF8, 0xE8, 0x8F, 0xF0, 0x69, 0xF8, 0x70, 0x96, 0x0F, 0x8E, 0x88, 0xF0, - 0x96, 0x06, 0x9F, 0x87, 0x40, 0xF8, 0xE8, 0x8F, 0x40, 0x69, 0xF8, 0x70, 0xF4, 0x39, 0x08, 0x78, - 0x20, 0x69, 0xF8, 0x61, 0xA4, 0x0F, 0x8E, 0x88, 0xF0, 0xA4, 0x06, 0x9F, 0x87, 0x25, 0x07, 0x88, - 0xB9, 0x70, 0x25, 0x07, 0x99, 0xB5, 0x16, 0x96, 0x07, 0x88, 0xB9, 0x70, 0x96, 0x07, 0x99, 0xB5, - 0x16, 0x20, 0x78, 0x8B, 0x97, 0x20, 0x79, 0x9B, 0x51, 0x60, 0x78, 0x8B, 0x97, 0x24, 0x24, 0x07, - 0x99, 0xB5, 0x16, 0x25, 0x09, 0x9F, 0x99, 0x90, 0x25, 0x88, 0xE9, 0x99, 0x90, 0xFD, 0x27, 0x92, - 0x49, 0x20, 0xF2, 0x1C, 0x94, 0xA5, 0x20, 0x5A, 0x07, 0x22, 0x22, 0x70, 0x5A, 0x06, 0x22, 0x22, - 0xE3, 0xA4, 0x97, 0xE3, 0x24, 0x90, 0xA8, 0x74, 0x92, 0xE0, 0xA8, 0x64, 0x92, 0xE9, 0x25, 0xE2, - 0x4D, 0x5E, 0x40, 0x43, 0xA4, 0x97, 0xD5, 0x40, 0xFD, 0x14, 0x51, 0x57, 0xA0, 0x48, 0x36, 0x94, - 0xA5, 0x26, 0x25, 0x07, 0x11, 0x19, 0x60, 0x25, 0x06, 0x22, 0x22, 0x2C, 0x9A, 0xCA, 0x99, 0x48, - 0x88, 0x9A, 0xE9, 0x94, 0x80, 0x9A, 0xE9, 0x90, 0x12, 0x88, 0x88, 0x8F, 0x12, 0xC4, 0x44, 0x44, - 0x40, 0x88, 0x88, 0x8F, 0x24, 0x64, 0x92, 0x4A, 0x80, 0x39, 0x88, 0x88, 0xF0, 0x31, 0xC4, 0x44, - 0x44, 0x42, 0x88, 0x8A, 0x8F, 0xC4, 0x44, 0x54, 0x40, 0x8A, 0xC8, 0x8F, 0xC9, 0x3C, 0x90, 0x24, - 0x09, 0xDD, 0xBB, 0x90, 0x24, 0x0E, 0x99, 0x99, 0x9D, 0xDB, 0xBD, 0x48, 0x24, 0x0E, 0x99, 0x9D, - 0x48, 0x52, 0x9D, 0xDB, 0xB9, 0x52, 0x0E, 0x99, 0x99, 0x88, 0x0E, 0x99, 0x99, 0x9D, 0xDB, 0xB9, - 0x16, 0x24, 0x0E, 0x99, 0x99, 0x16, 0xF0, 0x69, 0x99, 0x96, 0xF0, 0x69, 0x99, 0x60, 0x96, 0x06, - 0x99, 0x99, 0x60, 0x96, 0x06, 0x99, 0x96, 0x5A, 0x06, 0x99, 0x99, 0x60, 0x5A, 0x06, 0x99, 0x96, - 0x7E, 0x49, 0xA4, 0x91, 0xF0, 0x75, 0x6F, 0x47, 0x80, 0x24, 0xE9, 0x9E, 0x99, 0x24, 0x0B, 0xC8, - 0x88, 0xE9, 0x9E, 0x99, 0x48, 0xBC, 0x88, 0x84, 0x80, 0xA4, 0xE9, 0x9E, 0x99, 0xA4, 0x0B, 0xC8, - 0x88, 0x24, 0x07, 0x86, 0x11, 0xE0, 0x24, 0x07, 0x86, 0x1E, 0x25, 0x07, 0x86, 0x11, 0xE0, 0x25, - 0x07, 0x86, 0x1E, 0x78, 0x61, 0x1E, 0x22, 0x78, 0x61, 0xE2, 0x20, 0x52, 0x07, 0x86, 0x11, 0xE0, - 0x52, 0x07, 0x86, 0x1E, 0xF9, 0x08, 0x42, 0x11, 0x00, 0x5D, 0x24, 0x50, 0x51, 0x01, 0xF2, 0x10, - 0x84, 0x20, 0x31, 0x4E, 0x44, 0x42, 0xF9, 0x08, 0xE2, 0x10, 0x5D, 0x74, 0x40, 0x5A, 0x09, 0x99, - 0x99, 0x70, 0x5A, 0x09, 0x99, 0x97, 0xF0, 0x99, 0x99, 0x97, 0xF0, 0x99, 0x99, 0x70, 0x96, 0x09, - 0x99, 0x99, 0x70, 0x96, 0x09, 0x99, 0x97, 0x25, 0x29, 0x99, 0x99, 0x70, 0x25, 0x29, 0x99, 0x97, - 0x4C, 0x81, 0x29, 0x4A, 0x52, 0x70, 0x4C, 0x81, 0x29, 0x4A, 0x4E, 0x99, 0x99, 0x97, 0x42, 0x99, - 0x99, 0x74, 0x20, 0x25, 0x09, 0x99, 0x9F, 0x90, 0x25, 0x09, 0x99, 0xF9, 0x22, 0x81, 0x15, 0x10, - 0x84, 0x20, 0x25, 0x09, 0x99, 0x97, 0x16, 0x50, 0x22, 0xA2, 0x10, 0x84, 0x24, 0x0F, 0x12, 0x48, - 0xF0, 0x24, 0x0F, 0x24, 0x8F, 0x20, 0xF1, 0x24, 0x8F, 0x20, 0xF2, 0x48, 0xF0, 0x52, 0x0F, 0x12, - 0x48, 0xF0, 0x52, 0x0F, 0x24, 0x8F, 0x34, 0xC4, 0x44, 0x40, 0xE2, 0x1C, 0x94, 0xA5, 0xC0, 0xF2, - 0x5C, 0x94, 0xB8, 0xE5, 0x21, 0xE8, 0xC6, 0x3E, 0xEA, 0x8E, 0x99, 0x9E, 0x43, 0x07, 0x91, 0x45, - 0x17, 0x80, 0x46, 0x1C, 0x94, 0xA5, 0xC0, 0xE1, 0x11, 0x1E, 0x0B, 0xA1, 0x08, 0x41, 0xC0, 0x78, - 0x88, 0x70, 0x72, 0x7A, 0x94, 0xB8, 0x7A, 0x92, 0x49, 0x24, 0xE0, 0x39, 0x42, 0xF8, 0xC6, 0x6D, - 0x75, 0x17, 0x99, 0xB5, 0x7C, 0x4E, 0x10, 0xBC, 0xE1, 0xF9, 0x60, 0x69, 0x48, 0x96, 0x7A, 0x1C, - 0x84, 0x22, 0x00, 0x34, 0x74, 0x44, 0x48, 0x77, 0x16, 0x05, 0x89, 0x0E, 0x00, 0x8A, 0x94, 0x42, - 0x29, 0x44, 0x84, 0x33, 0x5A, 0xC8, 0x92, 0x49, 0x50, 0xE3, 0xA4, 0x97, 0x95, 0x71, 0x49, 0x48, - 0x68, 0x9A, 0xE9, 0x90, 0xC9, 0x74, 0x90, 0x4E, 0x66, 0x99, 0xAD, 0x6B, 0x57, 0x80, 0x4B, 0x5E, - 0xB4, 0xC0, 0xE9, 0x99, 0x10, 0x69, 0xF9, 0x60, 0x04, 0x16, 0xA4, 0x92, 0x49, 0x18, 0x04, 0x10, - 0x98, 0x92, 0x49, 0x18, 0x09, 0x99, 0x65, 0x96, 0x56, 0x40, 0x09, 0x99, 0x65, 0x95, 0x90, 0xFA, - 0x92, 0x4E, 0x20, 0x80, 0x0C, 0x5E, 0x24, 0x92, 0x4E, 0x20, 0x80, 0x87, 0x25, 0x2E, 0x52, 0x41, - 0xE1, 0x68, 0x87, 0xE1, 0x68, 0x70, 0xFA, 0x48, 0x88, 0xFC, 0x45, 0x18, 0x42, 0x10, 0x83, 0x5D, - 0x24, 0x50, 0xF9, 0x48, 0x42, 0x10, 0x2B, 0xA4, 0x88, 0xFD, 0x48, 0x42, 0x10, 0x0C, 0xE5, 0x29, - 0x49, 0xC0, 0x0C, 0xE5, 0x29, 0x38, 0xDA, 0xA3, 0x18, 0xC5, 0xC0, 0x94, 0x63, 0x18, 0xC5, 0xC0, - 0x06, 0x35, 0x08, 0x20, 0x82, 0x00, 0x0C, 0xE5, 0x29, 0x38, 0x4C, 0xF0, 0xBE, 0x88, 0x78, 0xF2, - 0xF8, 0xF0, 0xF1, 0x21, 0x96, 0xF8, 0x48, 0x96, 0xF8, 0x48, 0x96, 0x78, 0x21, 0x02, 0x4A, 0xC4, - 0x4E, 0x74, 0x43, 0xF2, 0x23, 0xE0, 0xFA, 0x1C, 0x10, 0xC5, 0xC0, 0xFA, 0x1C, 0x18, 0xB8, 0x23, - 0x88, 0x20, 0xC5, 0xC0, 0xAD, 0x9A, 0xC8, 0x80, 0x24, 0x92, 0x49, 0x24, 0xB6, 0xDB, 0x6D, 0xB4, - 0x21, 0x09, 0xF2, 0x13, 0xE4, 0x21, 0x00, 0x24, 0x92, 0x08, 0x14, 0x2C, 0x2F, 0xA6, 0xAA, 0xAC, - 0xDC, 0x17, 0x2A, 0x2F, 0xA6, 0xAB, 0x37, 0x20, 0xD2, 0x88, 0x7E, 0x9A, 0xAC, 0x7C, 0x8C, 0x63, - 0x18, 0xD7, 0x40, 0x8C, 0x27, 0x18, 0xC7, 0xA1, 0x30, 0xCA, 0x16, 0x94, 0xA5, 0xA1, 0x30, 0xAD, - 0x7B, 0xDE, 0xD6, 0xC0, 0xAD, 0x3F, 0xDE, 0xD6, 0xA1, 0x30, 0x08, 0x37, 0x5A, 0xD6, 0xA1, 0x30, - 0x96, 0x06, 0x9F, 0x99, 0x96, 0x07, 0x99, 0xB5, 0xA8, 0x74, 0x97, 0xA8, 0x64, 0x92, 0x96, 0x06, - 0x99, 0x96, 0x96, 0x06, 0x99, 0x60, 0x52, 0x99, 0x99, 0x97, 0x96, 0x09, 0x99, 0x70, 0xF2, 0x99, - 0x99, 0x97, 0xF6, 0x09, 0x99, 0x97, 0x24, 0xD9, 0x99, 0x97, 0x26, 0x09, 0x99, 0x97, 0x52, 0xD9, - 0x99, 0x97, 0x52, 0x50, 0x99, 0x97, 0x42, 0xB9, 0x99, 0x97, 0x46, 0x09, 0x99, 0x97, 0xE1, 0xF9, - 0x60, 0xFA, 0x06, 0x9F, 0x99, 0xFA, 0x07, 0x99, 0xB5, 0xF6, 0x06, 0x9F, 0x99, 0xF6, 0x07, 0x99, - 0xB5, 0x70, 0x1F, 0x4A, 0x7A, 0x94, 0xB8, 0x70, 0x1D, 0x5B, 0xD1, 0xE0, 0x78, 0x8B, 0xB7, 0x79, - 0x9B, 0x75, 0x60, 0x52, 0x07, 0x8B, 0x97, 0x52, 0x07, 0x99, 0xB5, 0x16, 0x9A, 0xCA, 0x99, 0x88, - 0x9A, 0xE9, 0x90, 0x69, 0x99, 0x96, 0x30, 0x69, 0x99, 0x63, 0x52, 0x69, 0x99, 0x96, 0x30, 0x52, - 0x06, 0x99, 0x96, 0x30, 0x52, 0xF1, 0x21, 0x96, 0x52, 0x0F, 0x12, 0x19, 0x60, 0x52, 0x06, 0x22, - 0x22, 0x2C, 0xDE, 0x9A, 0xAA, 0xAA, 0xCD, 0xC0, 0xC2, 0x8B, 0xE9, 0xAA, 0xCD, 0xC0, 0x20, 0x87, - 0xE9, 0xAA, 0xC7, 0xC0, 0x52, 0x78, 0x8B, 0x97, 0x52, 0x07, 0x99, 0xB5, 0x16, 0xA5, 0x2B, 0xDA, - 0xD6, 0x40, 0xB6, 0x63, 0x2A, 0x62, 0x00, 0x42, 0x9D, 0xDB, 0xB9, 0x42, 0x0E, 0x99, 0x99, 0x6C, - 0x46, 0x9F, 0x99, 0x6C, 0x47, 0x99, 0xB5, 0x10, 0x87, 0xE4, 0x9B, 0xC9, 0x27, 0x11, 0x1D, 0x5B, - 0xD1, 0x60, 0x08, 0x53, 0x56, 0x59, 0xA6, 0xAC, 0x08, 0x50, 0x0D, 0x59, 0xE6, 0xAC, 0xA5, 0x69, - 0x9F, 0x99, 0xA5, 0x07, 0x99, 0xB5, 0x69, 0x69, 0x9F, 0x99, 0x69, 0x07, 0x99, 0xB5, 0xA5, 0xF8, - 0xE8, 0x8F, 0xA5, 0x06, 0x9F, 0x87, 0x69, 0xF8, 0xE8, 0x8F, 0x69, 0x69, 0xF8, 0x70, 0xA5, 0xE4, - 0x44, 0x4E, 0xA5, 0x06, 0x22, 0x22, 0x74, 0x40, 0xE2, 0x10, 0x8E, 0x69, 0x00, 0x62, 0x22, 0xA2, - 0x80, 0x64, 0xA5, 0x29, 0x30, 0xA2, 0x80, 0x64, 0xA5, 0x26, 0x69, 0x06, 0x99, 0x99, 0x60, 0x69, - 0x06, 0x99, 0x96, 0xA5, 0xE9, 0x9E, 0x99, 0xA5, 0x0B, 0xC8, 0x88, 0x69, 0xE9, 0x9E, 0x99, 0x69, - 0x0B, 0xC8, 0x88, 0xA5, 0x89, 0x99, 0x97, 0xA5, 0x09, 0x99, 0x97, 0x69, 0x09, 0x99, 0x97, 0x69, - 0x00, 0x99, 0x97, 0x78, 0x61, 0x1E, 0x40, 0x78, 0x61, 0xE4, 0xF9, 0x08, 0x42, 0x11, 0x00, 0x5D, - 0x24, 0xE0, 0x69, 0x16, 0x91, 0x68, 0x61, 0x69, 0x2C, 0x52, 0x99, 0xF9, 0x99, 0x5A, 0x8E, 0x99, - 0x99, 0xB6, 0x63, 0x18, 0xC4, 0x21, 0x10, 0x47, 0x24, 0x92, 0x67, 0x4A, 0x30, 0x53, 0x17, 0x46, - 0x2E, 0x54, 0x62, 0xE8, 0xC5, 0xC0, 0xF0, 0x88, 0x88, 0x78, 0x20, 0xF1, 0x11, 0x0F, 0x04, 0x20, - 0x69, 0x9F, 0x99, 0x20, 0x79, 0x9B, 0x50, 0xF8, 0xE8, 0x8F, 0x40, 0x69, 0xF8, 0x78, 0xF0, 0x96, - 0x99, 0x96, 0xF0, 0x90, 0x69, 0x96, 0x75, 0xA6, 0x99, 0x99, 0x60, 0xE5, 0xA0, 0x69, 0x99, 0x60, - 0x20, 0x69, 0x99, 0x96, 0x20, 0x06, 0x99, 0x96, 0xF0, 0x26, 0x99, 0x96, 0xF0, 0x20, 0x69, 0x96, - 0x70, 0x22, 0xA2, 0x10, 0x84, 0x60, 0x99, 0x99, 0x71, 0x60, 0x61, 0x08, 0x43, 0x34, 0xC0, 0xB3, - 0x28, 0xA6, 0xB4, 0x20, 0x42, 0x3C, 0x85, 0x35, 0xC0, 0x64, 0x92, 0x70, 0x21, 0x1D, 0x5A, 0xD5, - 0xC0, 0x75, 0x6B, 0x57, 0x10, 0x80, 0x09, 0x96, 0xD7, 0xA7, 0x20, 0x09, 0xD4, 0xC6, 0x62, 0xE0, - 0x09, 0xD4, 0xC4, 0x5C, 0x41, 0x04, 0x3C, 0x41, 0x07, 0xC0, 0x0F, 0xCC, 0x66, 0x32, 0x94, 0x74, - 0x18, 0x2E, 0x24, 0xC0, 0xF2, 0x48, 0xF4, 0x30, 0x74, 0x42, 0x22, 0x10, 0x80, 0x74, 0x42, 0x22, - 0x10, 0x71, 0x37, 0x1A, 0xC9, 0xC0, 0x99, 0xF9, 0x96, 0x66, 0x69, 0x99, 0x0F, 0xE5, 0x2F, 0x53, - 0x1F, 0x84, 0x00, 0x08, 0x44, 0xEA, 0xFF, 0x0E, 0x84, 0x00, 0x08, 0x20, 0x87, 0x0A, 0x27, 0x00, - 0x10, 0x0C, 0x23, 0x88, 0x42, 0x60, 0x74, 0xA5, 0x29, 0x38, 0x41, 0x74, 0xA5, 0x27, 0x08, 0x20, - 0x72, 0x53, 0xE4, 0xA4, 0x5F, 0x10, 0x84, 0x00, 0x8B, 0x94, 0x42, 0x10, 0x9F, 0x99, 0x71, 0x60, - 0xAD, 0x99, 0xE0, 0x5B, 0x9B, 0x50, 0xAD, 0x9D, 0xA0, 0x88, 0xE9, 0x99, 0xE0, 0x69, 0x89, 0x60, - 0x69, 0x8B, 0x60, 0x10, 0x9D, 0x29, 0x59, 0x41, 0x08, 0x84, 0xE9, 0x4A, 0xCA, 0x69, 0xF1, 0xE0, - 0xE1, 0xF9, 0x60, 0xE8, 0xBD, 0x26, 0x00, 0x78, 0x68, 0x87, 0xE1, 0x61, 0x1E, 0xE8, 0x98, 0x21, - 0x70, 0x69, 0xA9, 0x96, 0x62, 0x36, 0x22, 0xC0, 0x0B, 0xA5, 0x2B, 0x28, 0x4C, 0x79, 0x9B, 0x51, - 0x60, 0x78, 0xB9, 0x70, 0x99, 0x96, 0x69, 0x60, 0x85, 0x24, 0x8C, 0x31, 0x23, 0x00, 0x99, 0x99, - 0x71, 0x10, 0x68, 0x8E, 0x99, 0x99, 0x68, 0x8E, 0x99, 0x99, 0x20, 0x43, 0x2E, 0x90, 0xC9, 0x24, - 0x40, 0xE9, 0x2E, 0x61, 0x08, 0xDB, 0x10, 0x80, 0x62, 0x6A, 0x72, 0x20, 0xC9, 0x24, 0x91, 0x87, - 0xE3, 0x2B, 0x45, 0x26, 0x99, 0x9F, 0x90, 0x99, 0x9F, 0x91, 0x9F, 0xB9, 0x92, 0x72, 0x52, 0x94, - 0xC0, 0xE4, 0xA5, 0x29, 0x04, 0x9D, 0xB9, 0x90, 0x69, 0xF9, 0x60, 0x7A, 0x59, 0xE4, 0x7C, 0x74, - 0x63, 0x5D, 0x80, 0x23, 0xAB, 0x5A, 0xB8, 0x80, 0x11, 0x13, 0xD0, 0x11, 0x11, 0x3D, 0x10, 0x84, - 0x6D, 0x04, 0xBC, 0x88, 0x88, 0xBC, 0x88, 0x88, 0x40, 0x69, 0x88, 0x80, 0x69, 0x11, 0x10, 0x79, - 0x97, 0x99, 0x99, 0xE9, 0x9E, 0x78, 0x61, 0xE8, 0x40, 0x19, 0x08, 0x42, 0x10, 0x98, 0x19, 0x08, - 0xE2, 0x10, 0x98, 0x89, 0x24, 0x88, 0x11, 0x08, 0x42, 0x32, 0xC9, 0xC2, 0x27, 0x22, 0x20, 0x9F, - 0x99, 0x70, 0x96, 0x99, 0x70, 0xA9, 0x96, 0x60, 0x66, 0x99, 0x90, 0x30, 0xC4, 0x92, 0x84, 0x9F, - 0xB9, 0x90, 0x68, 0xE9, 0x99, 0x90, 0x8A, 0x88, 0x40, 0xF2, 0x48, 0xF1, 0x20, 0xF2, 0x4A, 0xF2, - 0xF2, 0x42, 0x19, 0x60, 0xF2, 0x42, 0x5B, 0x60, 0x74, 0x42, 0x22, 0x10, 0x80, 0x74, 0x60, 0x82, - 0x10, 0x80, 0x21, 0x08, 0x20, 0xC5, 0xC0, 0x78, 0x88, 0x88, 0x70, 0x74, 0x6B, 0x17, 0x00, 0xE9, - 0xE9, 0xE0, 0x69, 0x79, 0x60, 0x0B, 0xA1, 0x69, 0x38, 0x99, 0xF9, 0x90, 0x20, 0x62, 0x22, 0x7A, - 0xC0, 0x99, 0x75, 0x91, 0x10, 0x88, 0x88, 0xF0, 0x0B, 0xA5, 0x29, 0x38, 0x42, 0x74, 0x42, 0x22, - 0x38, 0x80, 0x74, 0x60, 0x82, 0x38, 0x80, 0x20, 0x87, 0xE9, 0xAA, 0xC7, 0xC0, 0x20, 0x87, 0xE9, - 0xAA, 0x97, 0x42, 0x20, 0x87, 0xE9, 0xAA, 0xD7, 0xC4, 0x43, 0xF6, 0x16, 0x44, 0xE0, 0x0A, 0xBC, - 0xA5, 0x28, 0xC2, 0x20, 0x43, 0xB5, 0x16, 0x54, 0xF1, 0x00, 0x21, 0x04, 0x3E, 0x55, 0x55, 0x42, - 0x10, 0x84, 0x2F, 0x8B, 0x47, 0xC0, 0x84, 0x3F, 0x2A, 0x63, 0xE0, 0xAD, 0x55, 0x5A, 0xA8, 0xFC, - 0x41, 0xF8, 0x80, 0x8A, 0x52, 0x93, 0x84, 0x20, 0x89, 0x24, 0x92, 0x38, 0x20, 0x81, 0x2A, 0x22, - 0x88, 0xA8, 0x8A, 0x88, 0x22, 0xA2, 0x22, 0xA2, 0x8A, 0x88, 0xC0, 0xC0, 0x60, 0x90, 0xE8, 0x05, - 0xC0, 0xE8, 0x8B, 0x80, 0xF0, 0x55, 0x00, 0x64, 0x6D, 0x80, 0x55, 0xA0, 0x8A, 0x88, 0xA2, 0x00, - 0x72, 0x24, 0xDE, 0xD3, 0x20, 0x90, 0x60, 0x69, 0x6D, 0x80, 0xE0, 0xF8, 0x96, 0x80, 0xA0, 0xC5, - 0x00, 0x55, 0x00, 0x4C, 0x80, 0xA8, 0xC0, 0xB4, 0x24, 0x5C, 0x74, 0x40, 0x6C, 0xD8, 0xE4, 0xD8, - 0xF1, 0x10, 0x64, 0x5D, 0x00, 0xE0, 0xA0, 0x25, 0x20, 0xF4, 0xA8, 0x5A, 0x21, 0x20, 0xBC, 0x75, - 0x70, 0x55, 0x40, 0xF6, 0xE8, 0x8B, 0x80, 0x74, 0x40, 0x88, 0x8F, 0x88, 0x80, 0x82, 0x10, 0xF4, - 0x20, 0xFD, 0x48, 0x42, 0x10, 0xFD, 0x48, 0x42, 0x00, 0x78, 0x36, 0xC0, 0x9B, 0xBD, 0xD9, 0x95, - 0xB5, 0x29, 0x04, 0x10, 0xC0, 0x74, 0x43, 0x17, 0x00, 0x74, 0x69, 0x17, 0x00, 0x74, 0x4B, 0x17, - 0x00, 0x51, 0x60, 0xFF, 0xFF, 0xC0, 0xFF, 0xFE, 0xAA, 0xAA, 0x78, 0x12, 0xD2, 0x00, 0x24, 0x06, - 0x99, 0xF9, 0x90, 0xF0, 0x24, 0xF8, 0xE8, 0x8F, 0x22, 0x72, 0xF4, 0xA5, 0x20, 0x27, 0xA2, 0x22, - 0x70, 0x44, 0x0C, 0x94, 0xA5, 0x26, 0x45, 0x4A, 0x51, 0x08, 0x40, 0x44, 0x1D, 0x18, 0xC5, 0x5B, - 0x12, 0xD2, 0x00, 0x20, 0x82, 0x08, 0x20, 0x69, 0x9F, 0x99, 0xE9, 0xE9, 0x9E, 0xF8, 0x88, 0x80, - 0x21, 0x14, 0xA8, 0xFC, 0xF8, 0xE8, 0x8F, 0xF1, 0x24, 0x8F, 0x99, 0xF9, 0x99, 0x69, 0xBD, 0x96, - 0xE9, 0x25, 0xC0, 0x9A, 0xCA, 0x99, 0x21, 0x14, 0xA8, 0xC4, 0x9F, 0xB9, 0x99, 0x9D, 0xDB, 0xB9, - 0xF8, 0x1C, 0x0F, 0x80, 0x69, 0x99, 0x96, 0x7A, 0x52, 0x94, 0xA4, 0xE9, 0x9E, 0x88, 0xFA, 0x08, - 0x88, 0x7C, 0xF9, 0x08, 0x42, 0x10, 0x8C, 0x54, 0x42, 0x10, 0x23, 0xAB, 0x5A, 0xB8, 0x80, 0x99, - 0x69, 0x99, 0xAD, 0x6A, 0xE2, 0x10, 0x74, 0x63, 0x15, 0x6C, 0xA1, 0x24, 0x90, 0x50, 0x23, 0x15, - 0x10, 0x84, 0x24, 0x07, 0x99, 0xB5, 0x24, 0x07, 0x8E, 0x87, 0x24, 0x0E, 0x99, 0x99, 0x10, 0x28, - 0x64, 0x92, 0x12, 0xD2, 0x12, 0x49, 0x24, 0x8E, 0x79, 0x9B, 0x50, 0x69, 0xA9, 0x9A, 0x80, 0x99, - 0x56, 0x55, 0x20, 0xF8, 0x69, 0x99, 0x60, 0x78, 0xE8, 0x70, 0xF2, 0x48, 0x87, 0x20, 0xE9, 0x99, - 0x91, 0x69, 0xF9, 0x60, 0xD5, 0x40, 0x9A, 0xE9, 0x90, 0x88, 0x46, 0x69, 0x90, 0x99, 0x99, 0xE8, - 0x99, 0x96, 0x60, 0x69, 0x48, 0x86, 0x12, 0x69, 0x99, 0x60, 0xFA, 0x94, 0xA5, 0x00, 0x69, 0x99, - 0xE8, 0x88, 0x78, 0x88, 0x72, 0x40, 0x7C, 0xA5, 0x26, 0x00, 0xF4, 0x44, 0x20, 0xA9, 0x99, 0x60, - 0xB5, 0x6B, 0x57, 0x10, 0x80, 0x99, 0x69, 0x90, 0xAD, 0x6A, 0xE2, 0x10, 0x80, 0x54, 0x6B, 0x55, - 0x00, 0xA3, 0x24, 0x90, 0x50, 0x12, 0x94, 0xA4, 0xE0, 0x12, 0x06, 0x99, 0x96, 0x11, 0x00, 0x94, - 0xA5, 0x27, 0x22, 0x00, 0xA8, 0xD7, 0x71, 0x9A, 0xCA, 0x99, 0x20, 0x69, 0xE9, 0x96, 0x31, 0x25, - 0xFA, 0x48, 0xC0, 0x8A, 0x35, 0x08, 0x20, 0x80, 0x42, 0x28, 0xD4, 0x20, 0x82, 0x00, 0x50, 0x08, - 0xA3, 0x50, 0x82, 0x08, 0x21, 0x1D, 0x5A, 0xD5, 0xC4, 0x20, 0xFC, 0x6B, 0xB8, 0x80, 0x94, 0x99, - 0x29, 0x90, 0x74, 0x63, 0x18, 0xB8, 0x84, 0x74, 0x63, 0x17, 0x10, 0x80, 0x74, 0x21, 0x08, 0x38, - 0x26, 0x74, 0x21, 0x07, 0x04, 0xC0, 0xF8, 0xF9, 0x88, 0xF8, 0xEA, 0x88, 0x81, 0x25, 0x9A, 0x48, - 0x10, 0x2A, 0x72, 0xA0, 0x42, 0x69, 0x35, 0x10, 0x42, 0x69, 0x35, 0x10, 0xAD, 0x6B, 0x55, 0x87, - 0xC0, 0xAD, 0x6A, 0xB0, 0xF8, 0x59, 0x97, 0x11, 0x10, 0x59, 0x97, 0x11, 0x88, 0x8E, 0x99, 0x91, - 0xE0, 0x13, 0xAA, 0x90, 0xBA, 0x00, 0xE1, 0x68, 0x96, 0xE1, 0x68, 0x87, 0x55, 0x48, 0xA5, 0x47, - 0xE0, 0x55, 0x48, 0xA7, 0x00, 0x78, 0xE9, 0x99, 0x60, 0x68, 0xA9, 0x96, 0x23, 0xAA, 0x42, 0x10, - 0x31, 0x3F, 0x42, 0x30, 0x9A, 0x65, 0x90, 0x69, 0x99, 0xE8, 0x61, 0x78, 0x88, 0x70, 0x21, 0x92, - 0x49, 0xC0, 0x69, 0xBD, 0x96, 0x78, 0xE8, 0x70, 0xE1, 0x71, 0xE0, 0x8E, 0x99, 0xE8, 0x8E, 0x99, - 0xE8, 0x80, 0x78, 0x88, 0x87, 0x9F, 0xB9, 0x99, 0x9F, 0xB9, 0x88, 0x69, 0x99, 0xE8, 0xC8, 0x74, - 0x42, 0x18, 0xB8, 0x78, 0xA8, 0x87, 0xE1, 0x51, 0x1E, 0x42, 0xF8, 0xE8, 0x8F, 0xA0, 0xF8, 0xE8, - 0x8F, 0xE2, 0x1C, 0x94, 0xA5, 0x22, 0x24, 0xF8, 0x88, 0x88, 0x69, 0xC8, 0x96, 0x3A, 0x0C, 0x10, - 0xB8, 0x24, 0x92, 0x40, 0xA9, 0x24, 0x90, 0x38, 0x42, 0x14, 0x98, 0x31, 0x45, 0x95, 0x56, 0x60, - 0xA5, 0x3D, 0x5A, 0xD8, 0xF2, 0x1C, 0x94, 0xA5, 0x20, 0x13, 0x54, 0xC5, 0x25, 0x20, 0x42, 0x9B, - 0xBD, 0xD9, 0x52, 0x99, 0x97, 0x16, 0x8C, 0x63, 0x1F, 0x90, 0x80, 0x69, 0x9F, 0x99, 0x72, 0x1C, - 0x94, 0xA5, 0xC0, 0xE9, 0xE9, 0x9E, 0xF8, 0x88, 0x88, 0x32, 0x94, 0xA5, 0x7E, 0x20, 0xF8, 0xE8, - 0x8F, 0xAD, 0x5D, 0x5A, 0xD4, 0x69, 0x21, 0x96, 0x4A, 0xD6, 0xD6, 0xA4, 0x52, 0x9B, 0xBD, 0xD9, - 0x9A, 0xCA, 0x99, 0x75, 0x55, 0x59, 0x9F, 0xB9, 0x99, 0x99, 0xF9, 0x99, 0x69, 0x99, 0x96, 0x7A, - 0x52, 0x94, 0xA4, 0x72, 0x52, 0xE4, 0x20, 0x78, 0x88, 0x87, 0xE9, 0x24, 0x80, 0x99, 0x96, 0x2C, - 0x23, 0xAB, 0x5A, 0xD5, 0xC4, 0x99, 0x69, 0x99, 0xAA, 0xAA, 0xAF, 0x10, 0x99, 0x71, 0x11, 0xAD, - 0x6B, 0x5A, 0xF8, 0xAA, 0xAA, 0xAA, 0xA9, 0xF0, 0x40, 0xC4, 0x46, 0x56, 0x4A, 0x52, 0xD5, 0xB4, - 0x42, 0x10, 0xE4, 0xB8, 0x69, 0x31, 0x96, 0x95, 0x7B, 0x5A, 0xC8, 0x79, 0x97, 0x99, 0x79, 0x9B, - 0x50, 0x16, 0x8E, 0x99, 0xE0, 0xE9, 0xE9, 0xE0, 0xF8, 0x88, 0x80, 0x32, 0x94, 0xA5, 0x7E, 0x20, - 0x69, 0xF8, 0x70, 0xAD, 0x5D, 0x5A, 0x80, 0x69, 0x29, 0x60, 0x9B, 0xFD, 0x90, 0x52, 0x9B, 0xFD, - 0x90, 0x9A, 0xE9, 0x90, 0x75, 0x55, 0x90, 0x9F, 0xB9, 0x90, 0x99, 0xF9, 0x90, 0x69, 0x99, 0x60, - 0xF9, 0x99, 0x90, 0xE9, 0x99, 0xE8, 0x80, 0x78, 0x88, 0x70, 0xE9, 0x24, 0x99, 0x96, 0x2C, 0x23, - 0xAB, 0x5A, 0xB8, 0x80, 0x99, 0x69, 0x90, 0xAA, 0xAA, 0xF1, 0x99, 0x71, 0x10, 0xAD, 0x6B, 0x5F, - 0x00, 0xAA, 0xAA, 0xAA, 0x7C, 0x10, 0xC4, 0x65, 0x60, 0x99, 0xDB, 0xD0, 0x88, 0xE9, 0xE0, 0x69, - 0x39, 0x60, 0x95, 0x7B, 0x59, 0x00, 0x79, 0x79, 0x90, 0x42, 0x06, 0x9F, 0x87, 0xA0, 0x69, 0xF8, - 0x70, 0x47, 0x10, 0xE4, 0xA4, 0x40, 0x24, 0xF8, 0x88, 0x80, 0x69, 0xC9, 0x60, 0x78, 0x61, 0xE0, - 0x4D, 0x54, 0xA3, 0x24, 0x90, 0x21, 0x92, 0x49, 0xC0, 0x31, 0x45, 0x95, 0x98, 0xA5, 0x3D, 0x5B, - 0x00, 0x42, 0x3C, 0x87, 0x25, 0x20, 0x12, 0x09, 0xAE, 0x99, 0x42, 0x09, 0xBB, 0xD9, 0x96, 0x09, - 0x99, 0x97, 0x16, 0x8C, 0x63, 0x17, 0x10, 0x80, 0xAD, 0x6B, 0x5A, 0xD5, 0x40, 0xAD, 0x6B, 0x55, - 0x00, 0x47, 0x10, 0xE4, 0xA5, 0xC0, 0x47, 0x10, 0xE4, 0xB8, 0x95, 0x69, 0xEA, 0x56, 0x40, 0x9D, - 0x3D, 0x49, 0x80, 0x21, 0x14, 0xEA, 0xD4, 0x22, 0x9D, 0x5A, 0x80, 0xA5, 0x35, 0xEA, 0xD4, 0xA6, - 0xBD, 0x5A, 0x80, 0xFC, 0x54, 0xEA, 0xD6, 0xA0, 0x72, 0x88, 0xEA, 0x80, 0xF6, 0xA9, 0xEA, 0xD6, - 0xA0, 0xF6, 0xA9, 0xEA, 0x80, 0xA4, 0x0E, 0x16, 0x11, 0x68, 0x60, 0xA4, 0x0E, 0x16, 0x16, 0x84, - 0xAD, 0x6B, 0x57, 0x10, 0x80, 0xAD, 0x6A, 0xE2, 0x10, 0x69, 0x9F, 0x99, 0x60, 0x69, 0xF9, 0x60, - 0x94, 0xE4, 0xC6, 0x30, 0x94, 0xE4, 0xC6, 0x00, 0x50, 0xA4, 0x93, 0x48, 0xC3, 0x0C, 0xA2, 0xA5, - 0x39, 0x31, 0x80, 0x45, 0x29, 0x5A, 0xD5, 0x61, 0x30, 0x45, 0x6B, 0x55, 0x84, 0xC0, 0x23, 0xAB, - 0x18, 0xC6, 0xAE, 0x20, 0x23, 0xAB, 0x1A, 0xB8, 0x80, 0x64, 0x82, 0x48, 0xC6, 0x35, 0x50, 0x64, - 0x82, 0x48, 0xC6, 0xAA, 0xFD, 0x41, 0x5A, 0xD6, 0xB5, 0x50, 0xFD, 0x41, 0x5A, 0xD6, 0xAA, 0x78, - 0x88, 0x86, 0x27, 0x78, 0x88, 0x62, 0x70, 0x11, 0x63, 0xC6, 0x88, 0x16, 0x80, 0x1F, 0x06, 0x99, - 0x96, 0x2D, 0x06, 0x99, 0x96, 0x44, 0x06, 0x99, 0x96, 0x22, 0x06, 0x99, 0x96, 0x4B, 0x06, 0x99, - 0x96, 0x14, 0x82, 0xFD, 0x05, 0x40, 0x14, 0x82, 0x8D, 0x05, 0x40, 0x51, 0x25, 0x6B, 0x6B, 0x53, - 0x10, 0x51, 0x25, 0x6F, 0x6A, 0x62, 0x47, 0x10, 0xE4, 0xB8, 0xE4, 0xAD, 0xE8, 0xC0, 0xE4, 0xA5, - 0x6F, 0x46, 0x00, 0x1F, 0x88, 0x88, 0x80, 0x1F, 0x88, 0x88, 0x7A, 0x3C, 0x84, 0x20, 0x7A, 0x3C, - 0x84, 0x00, 0xE8, 0xE9, 0x99, 0x20, 0xC8, 0xE9, 0x92, 0xAA, 0xA7, 0x2A, 0xAA, 0xB0, 0x40, 0xAA, - 0xA7, 0x2A, 0xAC, 0x10, 0x69, 0x21, 0x96, 0x24, 0x69, 0x29, 0x62, 0x40, 0x95, 0x31, 0x49, 0x4C, - 0x20, 0x95, 0x39, 0x29, 0x84, 0x9A, 0xEF, 0x99, 0x9E, 0xED, 0x90, 0x4F, 0x98, 0xA4, 0xA4, 0x4F, - 0x9C, 0x94, 0x80, 0xCA, 0x98, 0xA4, 0xA4, 0xCA, 0x9C, 0x94, 0x80, 0x94, 0xBD, 0x29, 0x4C, 0x20, - 0x94, 0xBD, 0x29, 0x84, 0x9C, 0xBD, 0x29, 0x48, 0x9C, 0xBD, 0x29, 0x00, 0xF2, 0x49, 0x24, 0x9A, - 0x50, 0x42, 0xF2, 0x49, 0x26, 0x94, 0x10, 0x80, 0x44, 0xAB, 0x5A, 0xB8, 0x60, 0x44, 0xAB, 0x57, - 0x0C, 0x78, 0x88, 0x87, 0x26, 0x78, 0x88, 0x72, 0x60, 0xF9, 0x08, 0x42, 0x18, 0x40, 0xF9, 0x08, - 0x43, 0x08, 0x8A, 0x88, 0x42, 0x10, 0x8A, 0x88, 0x42, 0x10, 0x8A, 0x88, 0xE2, 0x10, 0x8A, 0x88, - 0xE2, 0x10, 0x94, 0x99, 0x29, 0x4C, 0x20, 0x94, 0x99, 0x29, 0x84, 0xE9, 0x24, 0x92, 0x49, 0xF0, - 0x40, 0xE9, 0x24, 0x92, 0x7C, 0x10, 0x94, 0x9C, 0x21, 0x1C, 0x20, 0x94, 0x9C, 0x23, 0x04, 0x99, - 0xF5, 0x11, 0x99, 0xF5, 0x10, 0x88, 0xAD, 0x99, 0x90, 0x88, 0xE9, 0x99, 0x90, 0x32, 0x72, 0xF4, - 0x1C, 0x36, 0x5E, 0x83, 0x80, 0x32, 0x72, 0xF4, 0x1C, 0x40, 0x36, 0x5E, 0x83, 0x88, 0x24, 0x92, - 0x40, 0x51, 0x23, 0x57, 0x56, 0xB5, 0x51, 0x01, 0x5A, 0xBA, 0xB5, 0x9A, 0xCA, 0x99, 0x20, 0x9A, - 0xE9, 0x92, 0x72, 0x94, 0xA5, 0x4C, 0x20, 0x72, 0x94, 0xA9, 0x84, 0x99, 0xF9, 0x99, 0x20, 0x99, - 0xF9, 0x92, 0x94, 0xBD, 0x29, 0x4C, 0x40, 0x94, 0xBD, 0x29, 0x88, 0x99, 0x71, 0x13, 0x20, 0x99, - 0x71, 0x32, 0x97, 0xAD, 0x29, 0x4C, 0x40, 0x97, 0xAD, 0x29, 0x88, 0x49, 0x74, 0x80, 0x96, 0x69, - 0x9F, 0x99, 0x96, 0x07, 0x99, 0xB5, 0x90, 0x69, 0x9F, 0x99, 0x90, 0x79, 0x9B, 0x50, 0x7E, 0x49, - 0xBC, 0x92, 0x70, 0x75, 0x6F, 0x47, 0x80, 0x96, 0xF8, 0xE8, 0x8F, 0x96, 0x06, 0x9F, 0x87, 0xE1, - 0xF9, 0x96, 0xE1, 0xF9, 0x60, 0x90, 0xE1, 0xF9, 0x96, 0x90, 0xE1, 0xF9, 0x60, 0x50, 0x2B, 0x57, - 0x56, 0xB5, 0x50, 0x2B, 0x57, 0x56, 0xA0, 0x90, 0x69, 0x21, 0x96, 0x90, 0x69, 0x29, 0x60, 0x90, - 0x69, 0x21, 0x96, 0x90, 0x69, 0x29, 0x60, 0x60, 0x9B, 0xBD, 0xD9, 0x60, 0x9B, 0xFD, 0x90, 0x90, - 0x9B, 0xBD, 0xD9, 0x90, 0x9B, 0xFD, 0x90, 0x90, 0x69, 0x99, 0x96, 0x90, 0x69, 0x99, 0x60, 0x69, - 0xF9, 0x96, 0x69, 0xF9, 0x60, 0x90, 0x69, 0xF9, 0x96, 0x69, 0xF9, 0x60, 0x90, 0x69, 0x31, 0x96, - 0x90, 0x69, 0x39, 0x60, 0x70, 0x22, 0xA2, 0x10, 0x84, 0x70, 0x22, 0xA2, 0x10, 0x84, 0x90, 0x99, - 0x96, 0x2C, 0x90, 0x99, 0x96, 0x2C, 0x5B, 0x09, 0x96, 0x2C, 0x5A, 0x09, 0x96, 0x2C, 0x90, 0x99, - 0xF5, 0x11, 0x90, 0x99, 0xF5, 0x10, 0xF8, 0x88, 0x8C, 0x40, 0x7A, 0x38, 0x86, 0x10, 0x90, 0x99, - 0x9D, 0xBD, 0x90, 0x99, 0xDB, 0xD0, 0x7A, 0x38, 0x84, 0x30, 0x42, 0x7A, 0x38, 0x86, 0x08, 0x80, - 0x94, 0x99, 0x29, 0x44, 0x40, 0x97, 0x99, 0x28, 0x88, 0x99, 0x6F, 0x99, 0x99, 0x6F, 0x90, -}; - -static const GFXglyph lemonGlyphs[] PROGMEM = { - { 0, 0, 0, 5, 0, 0 }, // U+0020 char32 - { 0, 1, 6, 2, 0, -6 }, // U+0021 uni0021 - { 1, 3, 3, 4, 0, -7 }, // U+0022 uni0022 - { 3, 5, 5, 6, 0, -5 }, // U+0023 uni0023 - { 7, 4, 7, 5, 0, -7 }, // U+0024 uni0053 - { 11, 4, 6, 5, 0, -6 }, // U+0025 uni0025 - { 14, 5, 6, 6, 0, -6 }, // U+0026 uni0026 - { 18, 1, 3, 2, 0, -7 }, // U+0027 uni0027 - { 19, 3, 8, 4, 0, -7 }, // U+0028 uni0028 - { 22, 3, 8, 4, 0, -7 }, // U+0029 uni0028 - { 25, 4, 5, 5, 0, -5 }, // U+002A uni002A - { 28, 5, 5, 6, 0, -6 }, // U+002B uni002B - { 32, 2, 3, 3, 0, -2 }, // U+002C uni002C - { 33, 4, 1, 5, 0, -4 }, // U+002D uni002D - { 34, 1, 2, 2, 0, -2 }, // U+002E uni002E - { 35, 4, 8, 5, 0, -7 }, // U+002F uni002F - { 39, 4, 6, 5, 0, -6 }, // U+0030 uni0030 - { 42, 2, 6, 3, 0, -6 }, // U+0031 uni0031 - { 44, 4, 6, 5, 0, -6 }, // U+0032 uni0032 - { 47, 4, 6, 5, 0, -6 }, // U+0033 uni0033 - { 50, 4, 6, 5, 0, -6 }, // U+0034 uni0034 - { 53, 4, 6, 5, 0, -6 }, // U+0035 uni0035 - { 56, 4, 6, 5, 0, -6 }, // U+0036 uni0036 - { 59, 4, 6, 5, 0, -6 }, // U+0037 uni0037 - { 62, 4, 6, 5, 0, -6 }, // U+0038 uni0038 - { 65, 4, 6, 5, 0, -6 }, // U+0039 uni0039 - { 68, 1, 5, 2, 0, -5 }, // U+003A uni003A - { 69, 2, 6, 3, 0, -5 }, // U+003B uni003B - { 71, 3, 5, 4, 0, -5 }, // U+003C uni003C - { 73, 3, 3, 4, 0, -5 }, // U+003D uni003D - { 75, 3, 5, 4, 0, -5 }, // U+003E uni003E - { 77, 3, 6, 4, 0, -6 }, // U+003F uni003F - { 80, 4, 6, 5, 0, -6 }, // U+0040 uni0040 - { 83, 4, 6, 5, 0, -6 }, // U+0041 uni0041 - { 86, 4, 6, 5, 0, -6 }, // U+0042 uni0042 - { 89, 4, 6, 5, 0, -6 }, // U+0043 uni0043 - { 92, 4, 6, 5, 0, -6 }, // U+0044 uni0044 - { 95, 4, 6, 5, 0, -6 }, // U+0045 uni0045 - { 98, 4, 6, 5, 0, -6 }, // U+0046 uni0046 - { 101, 4, 6, 5, 0, -6 }, // U+0047 uni0047 - { 104, 4, 6, 5, 0, -6 }, // U+0048 uni0048 - { 107, 3, 6, 4, 0, -6 }, // U+0049 uni0049 - { 110, 4, 6, 5, 0, -6 }, // U+004A uni004A - { 113, 4, 6, 5, 0, -6 }, // U+004B uni004B - { 116, 4, 6, 5, 0, -6 }, // U+004C uni004C - { 119, 4, 6, 5, 0, -6 }, // U+004D uni004D - { 122, 4, 6, 5, 0, -6 }, // U+004E uni004E - { 125, 4, 6, 5, 0, -6 }, // U+004F uni004F - { 128, 4, 6, 5, 0, -6 }, // U+0050 uni0050 - { 131, 4, 7, 5, 0, -6 }, // U+0051 uni0051 - { 135, 4, 6, 5, 0, -6 }, // U+0052 uni0052 - { 138, 4, 6, 5, 0, -6 }, // U+0053 uni0053 - { 141, 5, 6, 6, 0, -6 }, // U+0054 uni0054 - { 145, 4, 6, 5, 0, -6 }, // U+0055 uni0055 - { 148, 4, 6, 5, 0, -6 }, // U+0056 uni0056 - { 151, 4, 6, 5, 0, -6 }, // U+0057 uni0057 - { 154, 4, 6, 5, 0, -6 }, // U+0058 uni0058 - { 157, 5, 6, 6, 0, -6 }, // U+0059 uni0059 - { 161, 4, 6, 5, 0, -6 }, // U+005A uni005A - { 164, 3, 8, 4, 0, -7 }, // U+005B uni005B - { 167, 4, 8, 5, 0, -7 }, // U+005C uni005C - { 171, 3, 8, 4, 0, -7 }, // U+005D uni005D - { 174, 5, 3, 6, 0, -7 }, // U+005E uni005E - { 176, 4, 1, 5, 0, 0 }, // U+005F uni005F - { 177, 2, 3, 3, 0, -7 }, // U+0060 uni0060 - { 178, 4, 5, 5, 0, -5 }, // U+0061 uni0061 - { 181, 4, 7, 5, 0, -7 }, // U+0062 uni0062 - { 185, 4, 5, 5, 0, -5 }, // U+0063 uni0063 - { 188, 4, 7, 5, 0, -7 }, // U+0064 uni0064 - { 192, 4, 5, 5, 0, -5 }, // U+0065 uni0065 - { 195, 3, 7, 4, 0, -7 }, // U+0066 uni0066 - { 198, 4, 7, 5, 0, -5 }, // U+0067 uni0067 - { 202, 4, 7, 5, 0, -7 }, // U+0068 uni0068 - { 206, 2, 7, 3, 0, -7 }, // U+0069 uni0069 - { 208, 3, 9, 4, 0, -7 }, // U+006A uni006A - { 212, 4, 7, 5, 0, -7 }, // U+006B uni006B - { 216, 2, 7, 3, 0, -7 }, // U+006C uni006C - { 218, 4, 5, 5, 0, -5 }, // U+006D uni006D - { 221, 4, 5, 5, 0, -5 }, // U+006E uni006E - { 224, 4, 5, 5, 0, -5 }, // U+006F uni006F - { 227, 4, 7, 5, 0, -5 }, // U+0070 uni0070 - { 231, 4, 7, 5, 0, -5 }, // U+0071 uni0071 - { 235, 4, 5, 5, 0, -5 }, // U+0072 uni0072 - { 238, 4, 5, 5, 0, -5 }, // U+0073 uni0073 - { 241, 3, 6, 4, 0, -6 }, // U+0074 uni0074 - { 244, 4, 5, 5, 0, -5 }, // U+0075 uni0075 - { 247, 4, 5, 5, 0, -5 }, // U+0076 uni0076 - { 250, 4, 5, 5, 0, -5 }, // U+0077 uni0077 - { 253, 4, 5, 5, 0, -5 }, // U+0078 uni0078 - { 256, 4, 7, 5, 0, -5 }, // U+0079 uni0079 - { 260, 4, 5, 5, 0, -5 }, // U+007A uni007A - { 263, 3, 8, 4, 0, -7 }, // U+007B uni007B - { 266, 1, 8, 2, 0, -7 }, // U+007C uni007C - { 267, 3, 8, 4, 0, -7 }, // U+007D uni007D - { 270, 5, 4, 6, 0, -5 }, // U+007E uni007E - { 273, 0, 0, 5, 0, 0 }, // U+007F (placeholder) - { 273, 0, 0, 5, 0, 0 }, // U+0080 (placeholder) - { 273, 0, 0, 5, 0, 0 }, // U+0081 (placeholder) - { 273, 0, 0, 5, 0, 0 }, // U+0082 (placeholder) - { 273, 0, 0, 5, 0, 0 }, // U+0083 (placeholder) - { 273, 0, 0, 5, 0, 0 }, // U+0084 (placeholder) - { 273, 0, 0, 5, 0, 0 }, // U+0085 (placeholder) - { 273, 0, 0, 5, 0, 0 }, // U+0086 (placeholder) - { 273, 0, 0, 5, 0, 0 }, // U+0087 (placeholder) - { 273, 0, 0, 5, 0, 0 }, // U+0088 (placeholder) - { 273, 0, 0, 5, 0, 0 }, // U+0089 (placeholder) - { 273, 0, 0, 5, 0, 0 }, // U+008A (placeholder) - { 273, 0, 0, 5, 0, 0 }, // U+008B (placeholder) - { 273, 0, 0, 5, 0, 0 }, // U+008C (placeholder) - { 273, 0, 0, 5, 0, 0 }, // U+008D (placeholder) - { 273, 0, 0, 5, 0, 0 }, // U+008E (placeholder) - { 273, 0, 0, 5, 0, 0 }, // U+008F (placeholder) - { 273, 0, 0, 5, 0, 0 }, // U+0090 (placeholder) - { 273, 0, 0, 5, 0, 0 }, // U+0091 (placeholder) - { 273, 0, 0, 5, 0, 0 }, // U+0092 (placeholder) - { 273, 0, 0, 5, 0, 0 }, // U+0093 (placeholder) - { 273, 0, 0, 5, 0, 0 }, // U+0094 (placeholder) - { 273, 4, 4, 5, 0, -4 }, // U+0095 char149 - { 275, 0, 0, 5, 0, 0 }, // U+0096 (placeholder) - { 275, 0, 0, 5, 0, 0 }, // U+0097 (placeholder) - { 275, 0, 0, 5, 0, 0 }, // U+0098 (placeholder) - { 275, 0, 0, 5, 0, 0 }, // U+0099 (placeholder) - { 275, 0, 0, 5, 0, 0 }, // U+009A (placeholder) - { 275, 0, 0, 5, 0, 0 }, // U+009B (placeholder) - { 275, 0, 0, 5, 0, 0 }, // U+009C (placeholder) - { 275, 0, 0, 5, 0, 0 }, // U+009D (placeholder) - { 275, 0, 0, 5, 0, 0 }, // U+009E (placeholder) - { 275, 0, 0, 5, 0, 0 }, // U+009F (placeholder) - { 275, 0, 0, 5, 0, 0 }, // U+00A0 (placeholder) - { 275, 1, 6, 2, 0, -6 }, // U+00A1 char161 - { 276, 4, 7, 5, 0, -6 }, // U+00A2 char162 - { 280, 5, 7, 6, 0, -7 }, // U+00A3 char163 - { 285, 5, 6, 6, 0, -6 }, // U+00A4 char164 - { 289, 5, 7, 6, 0, -7 }, // U+00A5 char165 - { 294, 1, 7, 2, 0, -6 }, // U+00A6 char166 - { 295, 4, 8, 5, 0, -7 }, // U+00A7 char167 - { 299, 3, 1, 4, 0, -6 }, // U+00A8 char168 - { 300, 6, 7, 7, 0, -7 }, // U+00A9 char169 - { 306, 4, 4, 5, 0, -6 }, // U+00AA char170 - { 308, 4, 3, 5, 0, -5 }, // U+00AB char171 - { 310, 5, 3, 6, 0, -4 }, // U+00AC char172 - { 312, 3, 1, 4, 0, -3 }, // U+00AD SOFT HYPHEN - { 313, 6, 7, 7, 0, -7 }, // U+00AE char174 - { 319, 4, 1, 5, 0, -7 }, // U+00AF char175 - { 320, 3, 3, 4, 0, -6 }, // U+00B0 char176 - { 322, 4, 5, 5, 0, -5 }, // U+00B1 char177 - { 325, 3, 5, 4, 0, -7 }, // U+00B2 char178 - { 327, 3, 5, 4, 0, -7 }, // U+00B3 char179 - { 329, 2, 2, 3, 0, -8 }, // U+00B4 char180 - { 330, 4, 6, 5, 0, -5 }, // U+00B5 char181 - { 333, 5, 6, 6, 0, -6 }, // U+00B6 char182 - { 337, 2, 2, 3, 0, -4 }, // U+00B7 char183 - { 338, 3, 3, 4, 0, -4 }, // U+00B8 char184 - { 340, 2, 4, 3, 0, -6 }, // U+00B9 char185 - { 341, 4, 4, 5, 0, -8 }, // U+00BA char186 - { 343, 5, 3, 6, 0, -5 }, // U+00BB char187 - { 345, 5, 8, 6, 0, -8 }, // U+00BC char188 - { 350, 5, 7, 6, 0, -8 }, // U+00BD char189 - { 355, 6, 8, 7, 0, -8 }, // U+00BE char190 - { 361, 3, 6, 4, 0, -6 }, // U+00BF char191 - { 364, 4, 8, 5, 0, -8 }, // U+00C0 char192 - { 368, 4, 8, 5, 0, -8 }, // U+00C1 char193 - { 372, 4, 9, 5, 0, -9 }, // U+00C2 char194 - { 377, 4, 8, 5, 0, -8 }, // U+00C3 char195 - { 381, 5, 8, 6, 0, -8 }, // U+00C4 char196 - { 386, 4, 8, 5, 0, -8 }, // U+00C5 char197 - { 390, 6, 6, 7, 0, -6 }, // U+00C6 char198 - { 395, 4, 8, 5, 0, -6 }, // U+00C7 char199 - { 399, 4, 8, 5, 0, -8 }, // U+00C8 char200 - { 403, 4, 8, 5, 0, -8 }, // U+00C9 char201 - { 407, 4, 8, 5, 0, -8 }, // U+00CA char202 - { 411, 4, 8, 5, 0, -8 }, // U+00CB char203 - { 415, 3, 9, 4, 0, -9 }, // U+00CC char204 - { 419, 3, 9, 4, 0, -9 }, // U+00CD char205 - { 423, 3, 9, 4, 0, -9 }, // U+00CE char206 - { 427, 3, 8, 4, 0, -8 }, // U+00CF char207 - { 430, 5, 6, 6, 0, -6 }, // U+00D0 char208 - { 434, 5, 8, 6, 0, -8 }, // U+00D1 char209 - { 439, 4, 8, 5, 0, -8 }, // U+00D2 char210 - { 443, 4, 8, 5, 0, -8 }, // U+00D3 char211 - { 447, 4, 8, 5, 0, -8 }, // U+00D4 char212 - { 451, 4, 8, 5, 0, -8 }, // U+00D5 char213 - { 455, 5, 7, 6, 0, -7 }, // U+00D6 char214 - { 460, 3, 3, 4, 0, -5 }, // U+00D7 char215 - { 462, 6, 6, 7, 0, -6 }, // U+00D8 char216 - { 467, 4, 8, 5, 0, -8 }, // U+00D9 char217 - { 471, 4, 8, 5, 0, -8 }, // U+00DA char218 - { 475, 4, 9, 5, 0, -9 }, // U+00DB char219 - { 480, 4, 8, 5, 0, -8 }, // U+00DC char220 - { 484, 5, 8, 6, 0, -8 }, // U+00DD char221 - { 489, 4, 7, 5, 0, -6 }, // U+00DE char222 - { 493, 4, 7, 5, 0, -6 }, // U+00DF char223 - { 497, 4, 8, 5, 0, -8 }, // U+00E0 char224 - { 501, 4, 8, 5, 0, -8 }, // U+00E1 char225 - { 505, 4, 8, 5, 0, -8 }, // U+00E2 char226 - { 509, 4, 8, 5, 0, -8 }, // U+00E3 char227 - { 513, 4, 7, 5, 0, -7 }, // U+00E4 char228 - { 517, 4, 7, 5, 0, -7 }, // U+00E5 char229 - { 521, 5, 5, 6, 0, -5 }, // U+00E6 char230 - { 525, 4, 7, 5, 0, -5 }, // U+00E7 char231 - { 529, 4, 8, 5, 0, -8 }, // U+00E8 char232 - { 533, 4, 8, 5, 0, -8 }, // U+00E9 char233 - { 537, 4, 8, 5, 0, -8 }, // U+00EA char234 - { 541, 4, 7, 5, 0, -7 }, // U+00EB char235 - { 545, 2, 8, 3, 0, -8 }, // U+00EC char236 - { 547, 3, 8, 4, 0, -8 }, // U+00ED char237 - { 550, 3, 8, 4, 0, -8 }, // U+00EE char238 - { 553, 3, 7, 4, 0, -7 }, // U+00EF char239 - { 556, 4, 8, 5, 0, -8 }, // U+00F0 char240 - { 560, 4, 8, 5, 0, -8 }, // U+00F1 char241 - { 564, 5, 8, 6, 0, -8 }, // U+00F2 char242 - { 569, 4, 8, 5, 0, -8 }, // U+00F3 char243 - { 573, 4, 8, 5, 0, -8 }, // U+00F4 char244 - { 577, 4, 8, 5, 0, -8 }, // U+00F5 char245 - { 581, 4, 7, 5, 0, -7 }, // U+00F6 char246 - { 585, 5, 5, 6, 0, -6 }, // U+00F7 char247 - { 589, 6, 5, 7, 0, -5 }, // U+00F8 char248 - { 593, 4, 8, 5, 0, -8 }, // U+00F9 char249 - { 597, 5, 8, 6, 0, -8 }, // U+00FA char250 - { 602, 4, 8, 5, 0, -8 }, // U+00FB char251 - { 606, 5, 7, 6, 0, -7 }, // U+00FC char252 - { 611, 4, 10, 5, 0, -8 }, // U+00FD char253 - { 616, 4, 9, 5, 0, -7 }, // U+00FE char254 - { 621, 4, 9, 5, 0, -7 }, // U+00FF char255 - { 626, 4, 8, 5, 0, -8 }, // U+0100 uni0041 - { 630, 4, 7, 5, 0, -7 }, // U+0101 uni0061 - { 634, 4, 9, 5, 0, -9 }, // U+0102 uni0041 - { 639, 4, 8, 5, 0, -8 }, // U+0103 uni0061 - { 643, 5, 7, 6, 0, -6 }, // U+0104 uni0041 - { 648, 5, 6, 6, 0, -5 }, // U+0105 uni0061 - { 652, 4, 9, 5, 0, -9 }, // U+0106 uni0043 - { 657, 4, 8, 5, 0, -8 }, // U+0107 uni0063 - { 661, 4, 9, 5, 0, -9 }, // U+0108 uni0043 - { 666, 4, 8, 5, 0, -8 }, // U+0109 uni0063 - { 670, 4, 8, 5, 0, -8 }, // U+010A uni0043 - { 674, 4, 7, 5, 0, -7 }, // U+010B uni0063 - { 678, 4, 9, 5, 0, -9 }, // U+010C uni0043 - { 683, 4, 8, 5, 0, -8 }, // U+010D uni0063 - { 687, 4, 9, 5, 0, -9 }, // U+010E uni0044 - { 692, 6, 8, 7, 0, -8 }, // U+010F uni0064 - { 698, 5, 6, 6, 0, -6 }, // U+0110 uni0044 - { 702, 5, 7, 6, 0, -7 }, // U+0111 uni0064 - { 707, 4, 8, 5, 0, -8 }, // U+0112 uni0045 - { 711, 4, 7, 5, 0, -7 }, // U+0113 uni0065 - { 715, 4, 9, 5, 0, -9 }, // U+0114 uni0045 - { 720, 4, 8, 5, 0, -8 }, // U+0115 uni0065 - { 724, 4, 8, 5, 0, -8 }, // U+0116 uni0045 - { 728, 4, 7, 5, 0, -7 }, // U+0117 uni0065 - { 732, 5, 7, 6, 0, -6 }, // U+0118 uni0045 - { 737, 4, 6, 5, 0, -5 }, // U+0119 uni0065 - { 740, 4, 9, 5, 0, -9 }, // U+011A uni0045 - { 745, 4, 8, 5, 0, -8 }, // U+011B uni0065 - { 749, 4, 9, 5, 0, -9 }, // U+011C uni0047 - { 754, 4, 10, 5, 0, -8 }, // U+011D uni0067 - { 759, 4, 9, 5, 0, -9 }, // U+011E uni0047 - { 764, 4, 10, 5, 0, -8 }, // U+011F uni0067 - { 769, 4, 8, 5, 0, -8 }, // U+0120 uni0047 - { 773, 4, 9, 5, 0, -7 }, // U+0121 uni0067 - { 778, 4, 8, 5, 0, -6 }, // U+0122 uni0047 - { 782, 4, 10, 5, 0, -8 }, // U+0123 uni0067 - { 787, 4, 9, 5, 0, -9 }, // U+0124 uni0048 - { 792, 4, 9, 5, 0, -9 }, // U+0125 uni0068 - { 797, 6, 6, 7, 0, -6 }, // U+0126 uni0048 - { 802, 5, 7, 6, 0, -7 }, // U+0127 uni0068 - { 807, 4, 9, 5, 0, -9 }, // U+0128 uni0049 - { 812, 4, 8, 5, 0, -8 }, // U+0129 uni0069 - { 816, 3, 8, 4, 0, -8 }, // U+012A uni0049 - { 819, 3, 7, 4, 0, -7 }, // U+012B uni0069 - { 822, 3, 9, 4, 0, -9 }, // U+012C uni0049 - { 826, 3, 8, 4, 0, -8 }, // U+012D uni0069 - { 829, 3, 8, 4, 0, -6 }, // U+012E uni0049 - { 832, 2, 9, 3, 0, -7 }, // U+012F uni0069 - { 835, 3, 8, 4, 0, -8 }, // U+0130 uni0049 - { 838, 2, 5, 3, 0, -5 }, // U+0131 uni0069 - { 840, 6, 6, 7, 0, -6 }, // U+0132 uni0049 - { 845, 5, 8, 6, 0, -7 }, // U+0133 uni0069 - { 850, 4, 9, 5, 0, -9 }, // U+0134 uni004A - { 855, 4, 10, 5, 0, -8 }, // U+0135 uni006A - { 860, 4, 8, 5, 0, -6 }, // U+0136 uni004B - { 864, 4, 9, 5, 0, -7 }, // U+0137 uni006B - { 869, 4, 5, 5, 0, -5 }, // U+0138 uni006B - { 872, 4, 8, 5, 0, -8 }, // U+0139 uni004C - { 876, 4, 9, 5, 0, -9 }, // U+013A uni006C - { 881, 4, 8, 5, 0, -6 }, // U+013B uni004C - { 885, 3, 9, 4, 0, -7 }, // U+013C uni006C - { 889, 4, 7, 5, 0, -7 }, // U+013D uni004C - { 893, 4, 10, 5, 0, -9 }, // U+013E uni006C - { 898, 4, 6, 5, 0, -6 }, // U+013F uni004C - { 901, 4, 7, 5, 0, -7 }, // U+0140 uni006C - { 905, 4, 6, 5, 0, -6 }, // U+0141 uni004C - { 908, 3, 7, 4, 0, -7 }, // U+0142 uni006C - { 911, 4, 9, 5, 0, -9 }, // U+0143 uni004E - { 916, 4, 8, 5, 0, -8 }, // U+0144 uni006E - { 920, 4, 8, 5, 0, -6 }, // U+0145 uni004E - { 924, 4, 10, 5, 0, -8 }, // U+0146 uni006E - { 929, 4, 8, 5, 0, -8 }, // U+0147 uni004E - { 933, 4, 8, 5, 0, -8 }, // U+0148 uni006E - { 937, 4, 8, 5, 0, -8 }, // U+0149 uni006E - { 941, 4, 8, 5, 0, -6 }, // U+014A uni004E - { 945, 4, 10, 5, 0, -8 }, // U+014B uni006E - { 950, 4, 8, 5, 0, -8 }, // U+014C uni004F - { 954, 4, 7, 5, 0, -7 }, // U+014D uni006F - { 958, 4, 9, 5, 0, -9 }, // U+014E uni004F - { 963, 4, 8, 5, 0, -8 }, // U+014F uni006F - { 967, 4, 9, 5, 0, -9 }, // U+0150 uni004F - { 972, 4, 8, 5, 0, -8 }, // U+0151 uni006F - { 976, 6, 6, 7, 0, -6 }, // U+0152 char198 - { 981, 5, 5, 6, 0, -5 }, // U+0153 char230 - { 985, 4, 8, 5, 0, -8 }, // U+0154 uni0052 - { 989, 4, 8, 5, 0, -8 }, // U+0155 uni0072 - { 993, 4, 8, 5, 0, -6 }, // U+0156 uni0052 - { 997, 4, 7, 5, 0, -5 }, // U+0157 uni0072 - { 1001, 4, 8, 5, 0, -8 }, // U+0158 uni0052 - { 1005, 4, 8, 5, 0, -8 }, // U+0159 uni0072 - { 1009, 4, 9, 5, 0, -9 }, // U+015A uni0053 - { 1014, 4, 8, 5, 0, -8 }, // U+015B uni0073 - { 1018, 4, 9, 5, 0, -9 }, // U+015C uni0053 - { 1023, 4, 8, 5, 0, -8 }, // U+015D uni0073 - { 1027, 4, 8, 5, 0, -6 }, // U+015E uni0053 - { 1031, 4, 7, 5, 0, -5 }, // U+015F uni0073 - { 1035, 4, 9, 5, 0, -9 }, // U+0160 uni0053 - { 1040, 4, 8, 5, 0, -8 }, // U+0161 uni0073 - { 1044, 5, 7, 6, 0, -6 }, // U+0162 uni0054 - { 1049, 3, 7, 4, 0, -6 }, // U+0163 uni0074 - { 1052, 5, 9, 6, 0, -9 }, // U+0164 uni0054 - { 1058, 4, 8, 5, 0, -8 }, // U+0165 uni0074 - { 1062, 5, 6, 6, 0, -6 }, // U+0166 uni0054 - { 1066, 3, 6, 4, 0, -6 }, // U+0167 uni0074 - { 1069, 4, 9, 5, 0, -9 }, // U+0168 uni0055 - { 1074, 4, 8, 5, 0, -8 }, // U+0169 uni0075 - { 1078, 4, 8, 5, 0, -8 }, // U+016A uni0055 - { 1082, 4, 7, 5, 0, -7 }, // U+016B uni0075 - { 1086, 4, 9, 5, 0, -9 }, // U+016C uni0055 - { 1091, 4, 8, 5, 0, -8 }, // U+016D uni0075 - { 1095, 4, 9, 5, 0, -9 }, // U+016E uni0055 - { 1100, 4, 8, 5, 0, -8 }, // U+016F uni0075 - { 1104, 5, 9, 6, 0, -9 }, // U+0170 uni0055 - { 1110, 5, 8, 6, 0, -8 }, // U+0171 uni0075 - { 1115, 4, 8, 5, 0, -6 }, // U+0172 uni0055 - { 1119, 4, 7, 5, 0, -5 }, // U+0173 uni0075 - { 1123, 4, 9, 5, 0, -9 }, // U+0174 uni0077 - { 1128, 4, 8, 5, 0, -8 }, // U+0175 uni0077 - { 1132, 5, 9, 6, 0, -9 }, // U+0176 uni0059 - { 1138, 4, 10, 5, 0, -8 }, // U+0177 uni0079 - { 1143, 5, 8, 6, 0, -8 }, // U+0178 uni0059 - { 1148, 4, 9, 5, 0, -9 }, // U+0179 uni005A - { 1153, 4, 8, 5, 0, -8 }, // U+017A uni007A - { 1157, 4, 8, 5, 0, -8 }, // U+017B uni005A - { 1161, 4, 7, 5, 0, -7 }, // U+017C uni007A - { 1165, 4, 9, 5, 0, -9 }, // U+017D uni005A - { 1170, 4, 8, 5, 0, -8 }, // U+017E uni007A - { 1174, 4, 7, 5, 0, -7 }, // U+017F LATIN SMALL LETTER LONG S - { 1178, 5, 7, 6, 0, -7 }, // U+0180 uni0062 - { 1183, 5, 6, 6, 0, -6 }, // U+0181 uni0042 - { 1187, 5, 8, 6, 0, -8 }, // U+0182 uni0042 - { 1192, 4, 8, 5, 0, -8 }, // U+0183 uni0042 - { 1196, 6, 7, 7, 0, -7 }, // U+0184 uni0062 - { 1202, 5, 7, 6, 0, -7 }, // U+0185 uni0062 - { 1207, 4, 6, 5, 0, -6 }, // U+0186 uni0043 - { 1210, 5, 7, 6, 0, -7 }, // U+0187 uni0043 - { 1215, 4, 5, 5, 0, -5 }, // U+0188 uni0063 - { 1218, 5, 6, 6, 0, -6 }, // U+0189 uni0044 - { 1222, 6, 6, 7, 0, -6 }, // U+018A uni0044 - { 1227, 5, 8, 6, 0, -8 }, // U+018B uni0042 - { 1232, 4, 8, 5, 0, -8 }, // U+018C uni0042 - { 1236, 0, 0, 5, 0, 0 }, // U+018D (placeholder) - { 1236, 5, 6, 6, 0, -6 }, // U+018E uni0045 - { 1240, 4, 5, 5, 0, -5 }, // U+018F uni0065 - { 1243, 4, 6, 5, 0, -6 }, // U+0190 char1028 - { 1246, 5, 7, 6, 0, -6 }, // U+0191 uni0046 - { 1251, 4, 8, 5, 0, -7 }, // U+0192 uni0066 - { 1255, 7, 6, 8, 0, -6 }, // U+0193 uni0047 - { 1261, 5, 8, 6, 0, -6 }, // U+0194 LATIN CAPITAL LETTER GAMMA - { 1266, 5, 6, 6, 0, -6 }, // U+0195 LATIN SMALL LETTER HV - { 1270, 3, 7, 4, 0, -7 }, // U+0196 LATIN CAPITAL LETTER IOTA - { 1273, 3, 8, 4, 0, -8 }, // U+0197 uni0049 - { 1276, 5, 6, 6, 0, -6 }, // U+0198 uni004B - { 1280, 4, 7, 5, 0, -7 }, // U+0199 uni006B - { 1284, 3, 7, 4, 0, -7 }, // U+019A uni0049 - { 1287, 4, 6, 5, 0, -6 }, // U+019B LATIN SMALL LETTER LAMBDA WITH STROKE - { 1290, 5, 5, 6, 0, -5 }, // U+019C LATIN CAPITAL LETTER TURNED M - { 1294, 5, 6, 6, 0, -5 }, // U+019D LATIN CAPITAL LETTER N WITH LEFT HOOK - { 1298, 4, 5, 5, 0, -4 }, // U+019E uni006E - { 1301, 4, 5, 5, 0, -5 }, // U+019F GREEK SMALL LETTER THETA - { 1304, 6, 8, 7, 0, -8 }, // U+01A0 uni004F - { 1310, 6, 8, 7, 0, -8 }, // U+01A1 uni006F - { 1316, 6, 7, 7, 0, -7 }, // U+01A2 uni004F - { 1322, 6, 6, 7, 0, -6 }, // U+01A3 uni006F - { 1327, 6, 6, 7, 0, -6 }, // U+01A4 uni0050 - { 1332, 6, 9, 7, 0, -7 }, // U+01A5 uni0070 - { 1339, 5, 8, 6, 0, -7 }, // U+01A6 LATIN LETTER YR - { 1344, 4, 6, 5, 0, -6 }, // U+01A7 uni0053 - { 1347, 4, 5, 5, 0, -5 }, // U+01A8 uni0073 - { 1350, 5, 6, 6, 0, -6 }, // U+01A9 GREEK CAPITAL LETTER SIGMA - { 1354, 5, 8, 6, 0, -7 }, // U+01AA LATIN LETTER REVERSED ESH LOOP - { 1359, 3, 7, 4, 0, -6 }, // U+01AB uni0074 - { 1362, 5, 6, 6, 0, -6 }, // U+01AC uni0054 - { 1366, 3, 7, 4, 0, -7 }, // U+01AD uni0074 - { 1369, 5, 6, 6, 0, -6 }, // U+01AE uni0054 - { 1373, 5, 7, 6, 0, -7 }, // U+01AF uni0055 - { 1378, 5, 6, 6, 0, -6 }, // U+01B0 uni0075 - { 1382, 5, 7, 6, 0, -7 }, // U+01B1 LATIN CAPITAL LETTER UPSILON - { 1387, 5, 7, 6, 0, -7 }, // U+01B2 LATIN CAPITAL LETTER V WITH HOOK - { 1392, 6, 7, 7, 0, -7 }, // U+01B3 uni0059 - { 1398, 5, 8, 6, 0, -6 }, // U+01B4 uni0079 - { 1403, 5, 6, 6, 0, -6 }, // U+01B5 uni005A - { 1407, 4, 5, 5, 0, -5 }, // U+01B6 uni007A - { 1410, 4, 6, 5, 0, -6 }, // U+01B7 LATIN CAPITAL LETTER EZH - { 1413, 4, 6, 5, 0, -6 }, // U+01B8 LATIN CAPITAL LETTER EZH - { 1416, 4, 6, 5, 0, -5 }, // U+01B9 LATIN CAPITAL LETTER EZH - { 1419, 6, 8, 7, 0, -6 }, // U+01BA LATIN CAPITAL LETTER EZH - { 1425, 5, 7, 6, 0, -7 }, // U+01BB LATIN LETTER TWO WITH STROKE - { 1430, 5, 7, 6, 0, -7 }, // U+01BC LATIN CAPITAL LETTER TONE FIVE - { 1435, 5, 6, 6, 0, -6 }, // U+01BD LATIN SMALL LETTER TONE FIVE - { 1439, 5, 7, 6, 0, -8 }, // U+01BE LATIN LETTER INVERTED GLOTTAL STOP WITH STROKE - { 1444, 4, 7, 5, 0, -7 }, // U+01BF LATIN LETTER WYNN - { 1448, 3, 10, 4, 0, -8 }, // U+01C0 LATIN LETTER DENTAL CLICK - { 1452, 3, 10, 4, 0, -8 }, // U+01C1 LATIN LETTER LATERAL CLICK - { 1456, 5, 10, 6, 0, -8 }, // U+01C2 LATIN LETTER ALVEOLAR CLICK - { 1463, 3, 7, 4, 0, -7 }, // U+01C3 LATIN LETTER RETROFLEX CLICK - { 1466, 6, 9, 7, 0, -9 }, // U+01C4 LATIN CAPITAL LETTER DZ WITH CARON - { 1473, 6, 8, 7, 0, -8 }, // U+01C5 LATIN CAPITAL LETTER D WITH SMALL LETTER Z WITH CARON - { 1479, 6, 9, 7, 0, -9 }, // U+01C6 LATIN SMALL LETTER DZ WITH CARON - { 1486, 5, 7, 6, 0, -7 }, // U+01C7 LATIN CAPITAL LETTER LJ - { 1491, 5, 9, 6, 0, -7 }, // U+01C8 LATIN CAPITAL LETTER L WITH SMALL LETTER J - { 1497, 5, 9, 6, 0, -7 }, // U+01C9 LATIN SMALL LETTER LJ - { 1503, 5, 7, 6, 0, -7 }, // U+01CA LATIN CAPITAL LETTER NJ - { 1508, 5, 9, 6, 0, -7 }, // U+01CB LATIN CAPITAL LETTER N WITH SMALL LETTER J - { 1514, 5, 9, 6, 0, -7 }, // U+01CC LATIN SMALL LETTER NJ - { 1520, 4, 8, 5, 0, -8 }, // U+01CD uni0041 - { 1524, 4, 8, 5, 0, -8 }, // U+01CE uni0061 - { 1528, 3, 8, 4, 0, -8 }, // U+01CF uni0049 - { 1531, 3, 8, 4, 0, -8 }, // U+01D0 uni0069 - { 1534, 4, 8, 5, 0, -8 }, // U+01D1 uni004F - { 1538, 4, 7, 5, 0, -7 }, // U+01D2 uni004F - { 1542, 4, 8, 5, 0, -8 }, // U+01D3 uni0055 - { 1546, 4, 7, 5, 0, -7 }, // U+01D4 uni0075 - { 1550, 4, 8, 5, 0, -8 }, // U+01D5 uni0055 - { 1554, 4, 8, 5, 0, -8 }, // U+01D6 uni0075 - { 1558, 4, 8, 5, 0, -8 }, // U+01D7 uni0055 - { 1562, 4, 8, 5, 0, -8 }, // U+01D8 uni0075 - { 1566, 4, 8, 5, 0, -8 }, // U+01D9 uni0055 - { 1570, 4, 8, 5, 0, -8 }, // U+01DA uni0075 - { 1574, 4, 8, 5, 0, -8 }, // U+01DB uni0055 - { 1578, 4, 8, 5, 0, -8 }, // U+01DC uni0075 - { 1582, 4, 5, 5, 0, -5 }, // U+01DD uni0065 - { 1585, 4, 8, 5, 0, -8 }, // U+01DE uni0041 - { 1589, 4, 8, 5, 0, -8 }, // U+01DF uni0061 - { 1593, 4, 8, 5, 0, -8 }, // U+01E0 uni0041 - { 1597, 4, 8, 5, 0, -8 }, // U+01E1 uni0061 - { 1601, 5, 9, 6, 0, -9 }, // U+01E2 LATIN CAPITAL LETTER AE WITH MACRON - { 1607, 5, 7, 6, 0, -7 }, // U+01E3 LATIN SMALL LETTER AE WITH MACRON - { 1612, 4, 6, 5, 0, -6 }, // U+01E4 uni0047 - { 1615, 4, 7, 5, 0, -5 }, // U+01E5 uni0067 - { 1619, 4, 8, 5, 0, -8 }, // U+01E6 uni0047 - { 1623, 4, 10, 5, 0, -8 }, // U+01E7 uni0067 - { 1628, 4, 6, 5, 0, -6 }, // U+01E8 uni004B - { 1631, 4, 7, 5, 0, -7 }, // U+01E9 uni006B - { 1635, 4, 7, 5, 0, -6 }, // U+01EA uni0051 - { 1639, 4, 6, 5, 0, -5 }, // U+01EB uni0051 - { 1642, 4, 9, 5, 0, -8 }, // U+01EC uni0051 - { 1647, 4, 9, 5, 0, -8 }, // U+01ED uni0051 - { 1652, 4, 8, 5, 0, -8 }, // U+01EE LATIN CAPITAL LETTER EZH - { 1656, 4, 9, 5, 0, -8 }, // U+01EF LATIN CAPITAL LETTER EZH - { 1661, 4, 10, 5, 0, -8 }, // U+01F0 uni006A - { 1666, 6, 7, 7, 0, -7 }, // U+01F1 LATIN CAPITAL LETTER DZ - { 1672, 6, 7, 7, 0, -7 }, // U+01F2 LATIN CAPITAL LETTER D WITH SMALL LETTER Z - { 1678, 6, 7, 7, 0, -7 }, // U+01F3 LATIN SMALL LETTER DZ - { 1684, 4, 8, 5, 0, -8 }, // U+01F4 uni0047 - { 1688, 4, 10, 5, 0, -8 }, // U+01F5 uni0067 - { 1693, 5, 7, 6, 0, -7 }, // U+01F6 LATIN CAPITAL LETTER HWAIR - { 1698, 5, 7, 6, 0, -7 }, // U+01F7 LATIN CAPITAL LETTER WYNN - { 1703, 4, 8, 5, 0, -8 }, // U+01F8 uni004E - { 1707, 4, 8, 5, 0, -8 }, // U+01F9 uni006E - { 1711, 4, 8, 5, 0, -8 }, // U+01FA uni0041 - { 1715, 4, 8, 5, 0, -8 }, // U+01FB uni0061 - { 1719, 6, 8, 7, 0, -8 }, // U+01FC char198 - { 1725, 5, 7, 6, 0, -7 }, // U+01FD char230 - { 1730, 6, 8, 7, 0, -8 }, // U+01FE char216 - { 1736, 6, 8, 7, 0, -8 }, // U+01FF char248 - { 1742, 4, 8, 5, 0, -8 }, // U+0200 uni0041 - { 1746, 4, 8, 5, 0, -8 }, // U+0201 uni0061 - { 1750, 4, 8, 5, 0, -8 }, // U+0202 uni0041 - { 1754, 4, 8, 5, 0, -8 }, // U+0203 uni0061 - { 1758, 4, 8, 5, 0, -8 }, // U+0204 uni0045 - { 1762, 4, 8, 5, 0, -8 }, // U+0205 uni0065 - { 1766, 4, 8, 5, 0, -8 }, // U+0206 uni0045 - { 1770, 4, 7, 5, 0, -7 }, // U+0207 uni0065 - { 1774, 4, 8, 5, 0, -8 }, // U+0208 uni0049 - { 1778, 4, 8, 5, 0, -8 }, // U+0209 uni0069 - { 1782, 5, 8, 6, 0, -8 }, // U+020A uni0049 - { 1787, 4, 8, 5, 0, -8 }, // U+020B uni0069 - { 1791, 5, 9, 6, 0, -9 }, // U+020C LATIN CAPITAL LETTER O WITH DOUBLE GRAVE - { 1797, 5, 8, 6, 0, -8 }, // U+020D LATIN SMALL LETTER O WITH DOUBLE GRAVE - { 1802, 4, 9, 5, 0, -9 }, // U+020E LATIN CAPITAL LETTER O WITH INVERTED BREVE - { 1807, 4, 8, 5, 0, -8 }, // U+020F LATIN SMALL LETTER O WITH INVERTED BREVE - { 1811, 4, 8, 5, 0, -8 }, // U+0210 uni0052 - { 1815, 4, 8, 5, 0, -8 }, // U+0211 uni0072 - { 1819, 4, 8, 5, 0, -8 }, // U+0212 uni0052 - { 1823, 4, 8, 5, 0, -8 }, // U+0213 uni0072 - { 1827, 4, 8, 5, 0, -8 }, // U+0214 uni0055 - { 1831, 4, 8, 5, 0, -8 }, // U+0215 uni0075 - { 1835, 4, 8, 5, 0, -8 }, // U+0216 uni0055 - { 1839, 4, 8, 5, 0, -8 }, // U+0217 uni0075 - { 1843, 4, 7, 5, 0, -6 }, // U+0218 uni0053 - { 1847, 4, 6, 5, 0, -5 }, // U+0219 uni0073 - { 1850, 5, 7, 6, 0, -6 }, // U+021A uni0054 - { 1855, 3, 7, 4, 0, -6 }, // U+021B uni0074 - { 1858, 4, 8, 5, 0, -7 }, // U+021C LATIN CAPITAL LETTER YOGH - { 1862, 4, 6, 5, 0, -6 }, // U+021D LATIN SMALL LETTER YOGH - { 1865, 4, 8, 5, 0, -8 }, // U+021E uni0048 - { 1869, 4, 8, 5, 0, -8 }, // U+021F uni0068 - { 1873, 5, 8, 6, 0, -6 }, // U+0220 LATIN CAPITAL LETTER N WITH LONG RIGHT LEG - { 1878, 6, 8, 7, 0, -7 }, // U+0221 LATIN SMALL LETTER D WITH CURL - { 1884, 5, 8, 6, 0, -8 }, // U+0222 LATIN CAPITAL LETTER OU - { 1889, 5, 7, 6, 0, -7 }, // U+0223 LATIN SMALL LETTER OU - { 1894, 5, 7, 6, 0, -6 }, // U+0224 uni005A - { 1899, 5, 6, 6, 0, -5 }, // U+0225 uni007A - { 1903, 4, 8, 5, 0, -8 }, // U+0226 uni0041 - { 1907, 4, 7, 5, 0, -7 }, // U+0227 uni0061 - { 1911, 4, 7, 5, 0, -6 }, // U+0228 uni0045 - { 1915, 4, 6, 5, 0, -5 }, // U+0229 uni0065 - { 1918, 4, 8, 5, 0, -8 }, // U+022A LATIN CAPITAL LETTER O WITH INVERTED BREVE - { 1922, 4, 8, 5, 0, -8 }, // U+022B LATIN SMALL LETTER O WITH INVERTED BREVE - { 1926, 4, 9, 5, 0, -9 }, // U+022C LATIN CAPITAL LETTER O WITH INVERTED BREVE - { 1931, 4, 9, 5, 0, -9 }, // U+022D LATIN SMALL LETTER O WITH INVERTED BREVE - { 1936, 4, 8, 5, 0, -8 }, // U+022E LATIN CAPITAL LETTER O WITH INVERTED BREVE - { 1940, 4, 8, 5, 0, -8 }, // U+022F LATIN SMALL LETTER O WITH INVERTED BREVE - { 1944, 4, 8, 5, 0, -8 }, // U+0230 LATIN CAPITAL LETTER O WITH INVERTED BREVE - { 1948, 4, 8, 5, 0, -8 }, // U+0231 LATIN SMALL LETTER O WITH INVERTED BREVE - { 1952, 5, 8, 6, 0, -8 }, // U+0232 uni0059 - { 1957, 4, 9, 5, 0, -7 }, // U+0233 uni0079 - { 1962, 5, 7, 6, 0, -7 }, // U+0234 LATIN SMALL LETTER L WITH CURL - { 1967, 6, 6, 7, 0, -5 }, // U+0235 LATIN SMALL LETTER N WITH CURL - { 1972, 5, 7, 6, 0, -7 }, // U+0236 LATIN SMALL LETTER T WITH CURL - { 1977, 3, 7, 4, 0, -5 }, // U+0237 LATIN SMALL LETTER DOTLESS J - { 1980, 5, 7, 6, 0, -7 }, // U+0238 LATIN SMALL LETTER DB DIGRAPH - { 1985, 5, 7, 6, 0, -5 }, // U+0239 LATIN SMALL LETTER QP DIGRAPH - { 1990, 5, 7, 6, 0, -7 }, // U+023A uni0041 - { 1995, 5, 7, 6, 0, -7 }, // U+023B uni0043 - { 2000, 5, 6, 6, 0, -6 }, // U+023C uni0063 - { 2004, 6, 7, 7, 0, -7 }, // U+023D LATIN CAPITAL LETTER L WITH BAR - { 2010, 5, 8, 6, 0, -8 }, // U+023E LATIN CAPITAL LETTER T WITH DIAGONAL STROKE - { 2015, 5, 7, 6, 0, -5 }, // U+023F uni0073 - { 2020, 4, 7, 5, 0, -5 }, // U+0240 uni007A - { 2024, 5, 7, 6, 0, -8 }, // U+0241 LATIN CAPITAL LETTER GLOTTAL STOP - { 2029, 5, 6, 6, 0, -7 }, // U+0242 LATIN SMALL LETTER GLOTTAL STOP - { 2033, 6, 6, 7, 0, -6 }, // U+0243 uni0042 - { 2038, 4, 6, 5, 0, -6 }, // U+0244 uni0041 - { 2041, 4, 6, 5, 0, -6 }, // U+0245 uni0056 - { 2044, 5, 10, 6, 0, -8 }, // U+0246 LATIN CAPITAL LETTER E WITH STROKE - { 2051, 5, 10, 6, 0, -8 }, // U+0247 LATIN SMALL LETTER E WITH STROKE - { 2058, 6, 7, 7, 0, -7 }, // U+0248 LATIN CAPITAL LETTER J WITH STROKE - { 2064, 5, 9, 6, 0, -7 }, // U+0249 LATIN SMALL LETTER J WITH STROKE - { 2070, 5, 8, 6, 0, -6 }, // U+024A uni0071 - { 2075, 5, 7, 6, 0, -5 }, // U+024B uni0071 - { 2080, 5, 6, 6, 0, -6 }, // U+024C uni0052 - { 2084, 5, 5, 6, 0, -5 }, // U+024D uni0072 - { 2088, 5, 6, 6, 0, -6 }, // U+024E uni0059 - { 2092, 4, 7, 5, 0, -5 }, // U+024F uni0079 - { 2096, 4, 5, 5, 0, -5 }, // U+0250 uni0061 - { 2099, 4, 5, 5, 0, -5 }, // U+0251 uni0061 - { 2102, 4, 5, 5, 0, -5 }, // U+0252 uni0061 - { 2105, 4, 7, 5, 0, -7 }, // U+0253 uni0062 - { 2109, 4, 5, 5, 0, -5 }, // U+0254 uni0063 - { 2112, 4, 5, 5, 0, -5 }, // U+0255 uni0063 - { 2115, 5, 8, 6, 0, -7 }, // U+0256 uni0064 - { 2120, 5, 8, 6, 0, -8 }, // U+0257 uni0064 - { 2125, 4, 5, 5, 0, -5 }, // U+0258 uni0065 - { 2128, 4, 5, 5, 0, -5 }, // U+0259 uni0065 - { 2131, 5, 5, 6, 0, -5 }, // U+025A uni0065 - { 2135, 4, 6, 5, 0, -6 }, // U+025B uni0033 - { 2138, 4, 6, 5, 0, -6 }, // U+025C uni0033 - { 2141, 5, 6, 6, 0, -6 }, // U+025D uni0033 - { 2145, 4, 6, 5, 0, -6 }, // U+025E char223 - { 2148, 4, 7, 5, 0, -5 }, // U+025F uni006A - { 2152, 5, 8, 6, 0, -6 }, // U+0260 uni0067 - { 2157, 4, 7, 5, 0, -5 }, // U+0261 uni0067 - { 2161, 4, 5, 5, 0, -5 }, // U+0262 uni0047 - { 2164, 4, 7, 5, 0, -5 }, // U+0263 uni0076 - { 2168, 6, 7, 7, 0, -5 }, // U+0264 uni0076 - { 2174, 4, 7, 5, 0, -7 }, // U+0265 uni0068 - { 2178, 4, 8, 5, 0, -8 }, // U+0266 uni0068 - { 2182, 4, 9, 5, 0, -8 }, // U+0267 uni0068 - { 2187, 3, 7, 4, 0, -7 }, // U+0268 uni0069 - { 2190, 3, 6, 4, 0, -5 }, // U+0269 uni0069 - { 2193, 3, 5, 4, 0, -5 }, // U+026A uni0069 - { 2195, 5, 7, 6, 0, -7 }, // U+026B uni006C - { 2200, 4, 7, 5, 0, -7 }, // U+026C uni006C - { 2204, 3, 8, 4, 0, -7 }, // U+026D uni006C - { 2207, 5, 8, 6, 0, -7 }, // U+026E LATIN SMALL LETTER LEZH - { 2212, 4, 5, 5, 0, -5 }, // U+026F uni006D - { 2215, 4, 6, 5, 0, -5 }, // U+0270 uni006D - { 2218, 4, 6, 5, 0, -5 }, // U+0271 uni006D - { 2221, 5, 6, 6, 0, -5 }, // U+0272 uni006E - { 2225, 5, 6, 6, 0, -5 }, // U+0273 uni006E - { 2229, 4, 5, 5, 0, -5 }, // U+0274 GREEK SMALL LETTER PAMPHYLIAN DIGAMMA - { 2232, 4, 5, 5, 0, -5 }, // U+0275 LATIN SMALL LETTER BARRED O - { 2235, 6, 5, 7, 0, -5 }, // U+0276 char230 - { 2239, 5, 5, 6, 0, -5 }, // U+0277 GREEK SMALL LETTER OMEGA WITH TONOS - { 2243, 5, 7, 6, 0, -7 }, // U+0278 GREEK CAPITAL LETTER PHI - { 2248, 4, 5, 5, 0, -5 }, // U+0279 uni0072 - { 2251, 4, 6, 5, 0, -6 }, // U+027A uni0072 - { 2254, 5, 6, 6, 0, -5 }, // U+027B uni0072 - { 2258, 4, 6, 5, 0, -6 }, // U+027C uni0072 - { 2261, 4, 7, 5, 0, -6 }, // U+027D uni0072 - { 2265, 4, 5, 5, 0, -5 }, // U+027E uni0072 - { 2268, 4, 5, 5, 0, -5 }, // U+027F uni0072 - { 2271, 4, 6, 5, 0, -6 }, // U+0280 char1071 - { 2274, 4, 6, 5, 0, -6 }, // U+0281 char1071 - { 2277, 4, 7, 5, 0, -5 }, // U+0282 uni0073 - { 2281, 5, 8, 6, 0, -7 }, // U+0283 uni0066 - { 2286, 5, 8, 6, 0, -7 }, // U+0284 uni0066 - { 2291, 3, 7, 4, 0, -6 }, // U+0285 uni0066 - { 2294, 5, 8, 6, 0, -7 }, // U+0286 uni0066 - { 2299, 4, 7, 5, 0, -7 }, // U+0287 uni0066 - { 2303, 4, 5, 5, 0, -5 }, // U+0288 uni0075 - { 2306, 4, 5, 5, 0, -5 }, // U+0289 uni0075 - { 2309, 4, 5, 5, 0, -5 }, // U+028A uni0076 - { 2312, 4, 5, 5, 0, -5 }, // U+028B uni0076 - { 2315, 6, 5, 7, 0, -5 }, // U+028C uni0076 - { 2319, 4, 5, 5, 0, -5 }, // U+028D uni006D - { 2322, 4, 7, 5, 0, -5 }, // U+028E uni0079 - { 2326, 5, 4, 6, 0, -4 }, // U+028F uni0059 - { 2329, 4, 7, 5, 0, -5 }, // U+0290 uni007A - { 2333, 4, 6, 5, 0, -5 }, // U+0291 uni007A - { 2336, 4, 7, 5, 0, -5 }, // U+0292 uni007A - { 2340, 4, 7, 5, 0, -5 }, // U+0293 uni007A - { 2344, 5, 7, 6, 0, -7 }, // U+0294 LATIN LETTER GLOTTAL STOP - { 2349, 5, 7, 6, 0, -7 }, // U+0295 LATIN LETTER PHARYNGEAL VOICED FRICATIVE - { 2354, 5, 7, 6, 0, -7 }, // U+0296 LATIN LETTER INVERTED GLOTTAL STOP - { 2359, 4, 7, 5, 0, -6 }, // U+0297 uni0043 - { 2363, 5, 5, 6, 0, -5 }, // U+0298 LATIN LETTER BILABIAL CLICK - { 2367, 4, 5, 5, 0, -5 }, // U+0299 LATIN LETTER SMALL CAPITAL B - { 2370, 4, 5, 5, 0, -5 }, // U+029A LATIN SMALL LETTER CLOSED OPEN E - { 2373, 5, 6, 6, 0, -6 }, // U+029B uni0047 - { 2377, 4, 5, 5, 0, -5 }, // U+029C uni0048 - { 2380, 4, 9, 5, 0, -7 }, // U+029D uni006A - { 2385, 4, 7, 5, 0, -7 }, // U+029E uni006B - { 2389, 4, 5, 5, 0, -5 }, // U+029F LATIN LETTER SMALL CAPITAL L - { 2392, 5, 8, 6, 0, -6 }, // U+02A0 uni0071 - { 2397, 5, 7, 6, 0, -7 }, // U+02A1 LATIN LETTER GLOTTAL STOP WITH STROKE - { 2402, 5, 7, 6, 0, -7 }, // U+02A2 LATIN LETTER REVERSED GLOTTAL STOP WITH STROKE - { 2407, 6, 7, 7, 0, -7 }, // U+02A3 LATIN SMALL LETTER DZ DIGRAPH - { 2413, 6, 8, 7, 0, -7 }, // U+02A4 LATIN SMALL LETTER DEZH DIGRAPH - { 2419, 6, 8, 7, 0, -7 }, // U+02A5 LATIN SMALL LETTER DZ DIGRAPH WITH CURL - { 2425, 6, 6, 7, 0, -6 }, // U+02A6 LATIN SMALL LETTER TS DIGRAPH - { 2430, 5, 9, 6, 0, -7 }, // U+02A7 LATIN SMALL LETTER TESH DIGRAPH - { 2436, 6, 7, 7, 0, -6 }, // U+02A8 LATIN SMALL LETTER TC DIGRAPH WITH CURL - { 2442, 6, 9, 7, 0, -7 }, // U+02A9 LATIN SMALL LETTER FENG DIGRAPH - { 2449, 5, 7, 6, 0, -7 }, // U+02AA LATIN SMALL LETTER LS DIGRAPH - { 2454, 5, 7, 6, 0, -7 }, // U+02AB LATIN SMALL LETTER LZ DIGRAPH - { 2459, 5, 6, 6, 0, -6 }, // U+02AC LATIN LETTER BILABIAL PERCUSSIVE - { 2463, 5, 5, 6, 0, -5 }, // U+02AD LATIN LETTER BIDENTAL PERCUSSIVE - { 2467, 5, 7, 6, 0, -7 }, // U+02AE uni0068 - { 2472, 6, 8, 7, 0, -7 }, // U+02AF uni0068 - { 2478, 0, 0, 5, 0, 0 }, // U+02B0 (placeholder) - { 2478, 0, 0, 5, 0, 0 }, // U+02B1 (placeholder) - { 2478, 0, 0, 5, 0, 0 }, // U+02B2 (placeholder) - { 2478, 0, 0, 5, 0, 0 }, // U+02B3 (placeholder) - { 2478, 0, 0, 5, 0, 0 }, // U+02B4 (placeholder) - { 2478, 0, 0, 5, 0, 0 }, // U+02B5 (placeholder) - { 2478, 0, 0, 5, 0, 0 }, // U+02B6 (placeholder) - { 2478, 0, 0, 5, 0, 0 }, // U+02B7 (placeholder) - { 2478, 0, 0, 5, 0, 0 }, // U+02B8 (placeholder) - { 2478, 0, 0, 5, 0, 0 }, // U+02B9 (placeholder) - { 2478, 0, 0, 5, 0, 0 }, // U+02BA (placeholder) - { 2478, 0, 0, 5, 0, 0 }, // U+02BB (placeholder) - { 2478, 0, 0, 5, 0, 0 }, // U+02BC (placeholder) - { 2478, 0, 0, 5, 0, 0 }, // U+02BD (placeholder) - { 2478, 0, 0, 5, 0, 0 }, // U+02BE (placeholder) - { 2478, 0, 0, 5, 0, 0 }, // U+02BF (placeholder) - { 2478, 0, 0, 5, 0, 0 }, // U+02C0 (placeholder) - { 2478, 0, 0, 5, 0, 0 }, // U+02C1 (placeholder) - { 2478, 3, 5, 4, 0, -5 }, // U+02C2 uni003C - { 2480, 3, 5, 4, 0, -5 }, // U+02C3 uni003C - { 2482, 5, 3, 6, 0, -5 }, // U+02C4 uni005E - { 2484, 5, 3, 6, 0, -5 }, // U+02C5 uni005E - { 2486, 5, 3, 6, 0, -8 }, // U+02C6 MODIFIER LETTER CIRCUMFLEX ACCENT - { 2488, 5, 3, 6, 0, -9 }, // U+02C7 CARON - { 2490, 1, 2, 2, 0, -7 }, // U+02C8 MODIFIER LETTER VERTICAL LINE - { 2491, 2, 1, 3, 0, -7 }, // U+02C9 MODIFIER LETTER MACRON - { 2492, 2, 2, 3, 0, -7 }, // U+02CA MODIFIER LETTER ACUTE ACCENT - { 2493, 2, 2, 3, 0, -7 }, // U+02CB MODIFIER LETTER GRAVE ACCENT - { 2494, 0, 0, 5, 0, 0 }, // U+02CC (placeholder) - { 2494, 0, 0, 5, 0, 0 }, // U+02CD (placeholder) - { 2494, 0, 0, 5, 0, 0 }, // U+02CE (placeholder) - { 2494, 0, 0, 5, 0, 0 }, // U+02CF (placeholder) - { 2494, 3, 6, 4, 0, -6 }, // U+02D0 MODIFIER LETTER TRIANGULAR COLON - { 2497, 3, 2, 4, 0, -4 }, // U+02D1 MODIFIER LETTER HALF TRIANGULAR COLON - { 2498, 0, 0, 5, 0, 0 }, // U+02D2 (placeholder) - { 2498, 0, 0, 5, 0, 0 }, // U+02D3 (placeholder) - { 2498, 0, 0, 5, 0, 0 }, // U+02D4 (placeholder) - { 2498, 0, 0, 5, 0, 0 }, // U+02D5 (placeholder) - { 2498, 0, 0, 5, 0, 0 }, // U+02D6 (placeholder) - { 2498, 0, 0, 5, 0, 0 }, // U+02D7 (placeholder) - { 2498, 5, 2, 6, 0, -8 }, // U+02D8 BREVE - { 2500, 2, 2, 3, 0, -9 }, // U+02D9 DOT ABOVE - { 2501, 3, 3, 4, 0, -8 }, // U+02DA RING ABOVE - { 2503, 2, 3, 3, 0, -1 }, // U+02DB OGONEK - { 2504, 5, 2, 6, 0, -8 }, // U+02DC SMALL TILDE - { 2506, 4, 3, 5, 0, -9 }, // U+02DD DOUBLE ACUTE ACCENT - { 2508, 0, 0, 5, 0, 0 }, // U+02DE (placeholder) - { 2508, 0, 0, 5, 0, 0 }, // U+02DF (placeholder) - { 2508, 5, 5, 6, 0, -6 }, // U+02E0 MODIFIER LETTER SMALL GAMMA - { 2512, 0, 0, 5, 0, 0 }, // U+02E1 (placeholder) - { 2512, 0, 0, 5, 0, 0 }, // U+02E2 (placeholder) - { 2512, 0, 0, 5, 0, 0 }, // U+02E3 (placeholder) - { 2512, 3, 5, 4, 0, -8 }, // U+02E4 MODIFIER LETTER SMALL REVERSED GLOTTAL STOP - { 2514, 0, 0, 5, 0, 0 }, // U+02E5 (placeholder) - { 2514, 0, 0, 5, 0, 0 }, // U+02E6 (placeholder) - { 2514, 0, 0, 5, 0, 0 }, // U+02E7 (placeholder) - { 2514, 0, 0, 5, 0, 0 }, // U+02E8 (placeholder) - { 2514, 0, 0, 5, 0, 0 }, // U+02E9 (placeholder) - { 2514, 0, 0, 5, 0, 0 }, // U+02EA (placeholder) - { 2514, 0, 0, 5, 0, 0 }, // U+02EB (placeholder) - { 2514, 0, 0, 5, 0, 0 }, // U+02EC (placeholder) - { 2514, 0, 0, 5, 0, 0 }, // U+02ED (placeholder) - { 2514, 5, 4, 6, 0, -7 }, // U+02EE MODIFIER LETTER DOUBLE APOSTROPHE - { 2517, 0, 0, 5, 0, 0 }, // U+02EF (placeholder) - { 2517, 0, 0, 5, 0, 0 }, // U+02F0 (placeholder) - { 2517, 0, 0, 5, 0, 0 }, // U+02F1 (placeholder) - { 2517, 0, 0, 5, 0, 0 }, // U+02F2 (placeholder) - { 2517, 0, 0, 5, 0, 0 }, // U+02F3 (placeholder) - { 2517, 0, 0, 5, 0, 0 }, // U+02F4 (placeholder) - { 2517, 0, 0, 5, 0, 0 }, // U+02F5 (placeholder) - { 2517, 0, 0, 5, 0, 0 }, // U+02F6 (placeholder) - { 2517, 0, 0, 5, 0, 0 }, // U+02F7 (placeholder) - { 2517, 0, 0, 5, 0, 0 }, // U+02F8 (placeholder) - { 2517, 0, 0, 5, 0, 0 }, // U+02F9 (placeholder) - { 2517, 0, 0, 5, 0, 0 }, // U+02FA (placeholder) - { 2517, 0, 0, 5, 0, 0 }, // U+02FB (placeholder) - { 2517, 0, 0, 5, 0, 0 }, // U+02FC (placeholder) - { 2517, 0, 0, 5, 0, 0 }, // U+02FD (placeholder) - { 2517, 0, 0, 5, 0, 0 }, // U+02FE (placeholder) - { 2517, 0, 0, 5, 0, 0 }, // U+02FF (placeholder) - { 2517, 2, 2, 3, 0, -9 }, // U+0300 COMBINING GRAVE ACCENT - { 2518, 2, 2, 3, 0, -9 }, // U+0301 COMBINING ACUTE ACCENT - { 2519, 4, 2, 5, 0, -9 }, // U+0302 COMBINING CIRCUMFLEX ACCENT - { 2520, 5, 2, 6, 0, -9 }, // U+0303 COMBINING TILDE - { 2522, 3, 1, 4, 0, -9 }, // U+0304 COMBINING MACRON - { 2523, 5, 1, 6, 0, -9 }, // U+0305 COMBINING OVERLINE - { 2524, 4, 2, 5, 0, -9 }, // U+0306 COMBINING BREVE - { 2525, 1, 1, 2, 0, -9 }, // U+0307 COMBINING DOT ABOVE - { 2526, 3, 1, 4, 0, -9 }, // U+0308 COMBINING DIAERESIS - { 2527, 3, 3, 4, 0, -9 }, // U+0309 COMBINING HOOK ABOVE - { 2529, 3, 3, 4, 0, -9 }, // U+030A COMBINING RING ABOVE - { 2531, 5, 2, 6, 0, -9 }, // U+030B COMBINING DOUBLE ACUTE ACCENT - { 2533, 3, 2, 4, 0, -9 }, // U+030C COMBINING CARON - { 2534, 1, 2, 2, 0, -9 }, // U+030D COMBINING VERTICAL LINE ABOVE - { 2535, 3, 2, 4, 0, -9 }, // U+030E COMBINING DOUBLE VERTICAL LINE ABOVE - { 2536, 0, 0, 5, 0, 0 }, // U+030F (placeholder) - { 2536, 5, 3, 6, 0, -9 }, // U+0310 COMBINING CANDRABINDU - { 2538, 5, 2, 6, 0, -9 }, // U+0311 COMBINING INVERTED BREVE - { 2540, 2, 3, 3, 0, -9 }, // U+0312 COMBINING TURNED COMMA ABOVE - { 2541, 2, 3, 3, 0, -9 }, // U+0313 COMBINING COMMA ABOVE - { 2542, 2, 3, 3, 0, -9 }, // U+0314 COMBINING REVERSED COMMA ABOVE - { 2543, 2, 3, 3, 0, -9 }, // U+0315 COMBINING COMMA ABOVE RIGHT - { 2544, 0, 0, 5, 0, 0 }, // U+0316 (placeholder) - { 2544, 0, 0, 5, 0, 0 }, // U+0317 (placeholder) - { 2544, 0, 0, 5, 0, 0 }, // U+0318 (placeholder) - { 2544, 0, 0, 5, 0, 0 }, // U+0319 (placeholder) - { 2544, 4, 3, 5, 0, -9 }, // U+031A COMBINING LEFT ANGLE ABOVE - { 2546, 0, 0, 5, 0, 0 }, // U+031B (placeholder) - { 2546, 2, 3, 3, 0, 0 }, // U+031C COMBINING LEFT HALF RING BELOW - { 2547, 0, 0, 5, 0, 0 }, // U+031D (placeholder) - { 2547, 0, 0, 5, 0, 0 }, // U+031E (placeholder) - { 2547, 3, 3, 4, 0, 0 }, // U+031F COMBINING PLUS SIGN BELOW - { 2549, 3, 1, 4, 0, 1 }, // U+0320 COMBINING MINUS SIGN BELOW - { 2550, 0, 0, 5, 0, 0 }, // U+0321 (placeholder) - { 2550, 0, 0, 5, 0, 0 }, // U+0322 (placeholder) - { 2550, 0, 0, 5, 0, 0 }, // U+0323 (placeholder) - { 2550, 4, 1, 5, 0, 1 }, // U+0324 COMBINING DIAERESIS BELOW - { 2551, 4, 3, 5, 0, 0 }, // U+0325 COMBINING RING BELOW - { 2553, 0, 0, 5, 0, 0 }, // U+0326 (placeholder) - { 2553, 0, 0, 5, 0, 0 }, // U+0327 (placeholder) - { 2553, 0, 0, 5, 0, 0 }, // U+0328 (placeholder) - { 2553, 0, 0, 5, 0, 0 }, // U+0329 (placeholder) - { 2553, 3, 2, 4, 0, 0 }, // U+032A COMBINING BRIDGE BELOW - { 2554, 0, 0, 5, 0, 0 }, // U+032B (placeholder) - { 2554, 3, 2, 4, 0, 0 }, // U+032C COMBINING CARON BELOW - { 2555, 0, 0, 5, 0, 0 }, // U+032D (placeholder) - { 2555, 0, 0, 5, 0, 0 }, // U+032E (placeholder) - { 2555, 0, 0, 5, 0, 0 }, // U+032F (placeholder) - { 2555, 4, 2, 5, 0, 1 }, // U+0330 COMBINING TILDE BELOW - { 2556, 0, 0, 5, 0, 0 }, // U+0331 (placeholder) - { 2556, 0, 0, 5, 0, 0 }, // U+0332 (placeholder) - { 2556, 0, 0, 5, 0, 0 }, // U+0333 (placeholder) - { 2556, 0, 0, 5, 0, 0 }, // U+0334 (placeholder) - { 2556, 0, 0, 5, 0, 0 }, // U+0335 (placeholder) - { 2556, 0, 0, 5, 0, 0 }, // U+0336 (placeholder) - { 2556, 0, 0, 5, 0, 0 }, // U+0337 (placeholder) - { 2556, 0, 0, 5, 0, 0 }, // U+0338 (placeholder) - { 2556, 4, 3, 5, 0, 0 }, // U+0339 COMBINING RIGHT HALF RING BELOW - { 2558, 3, 2, 4, 0, 1 }, // U+033A COMBINING INVERTED BRIDGE BELOW - { 2559, 4, 3, 5, 0, 0 }, // U+033B COMBINING SQUARE BELOW - { 2561, 5, 2, 6, 0, 1 }, // U+033C COMBINING SEAGULL BELOW - { 2563, 0, 0, 5, 0, 0 }, // U+033D (placeholder) - { 2563, 0, 0, 5, 0, 0 }, // U+033E (placeholder) - { 2563, 0, 0, 5, 0, 0 }, // U+033F (placeholder) - { 2563, 0, 0, 5, 0, 0 }, // U+0340 (placeholder) - { 2563, 0, 0, 5, 0, 0 }, // U+0341 (placeholder) - { 2563, 0, 0, 5, 0, 0 }, // U+0342 (placeholder) - { 2563, 2, 4, 3, 0, -7 }, // U+0343 COMBINING GREEK KORONIS - { 2564, 3, 2, 4, 0, -4 }, // U+0344 COMBINING GREEK DIALYTIKA TONOS - { 2565, 0, 0, 5, 0, 0 }, // U+0345 (placeholder) - { 2565, 0, 0, 5, 0, 0 }, // U+0346 (placeholder) - { 2565, 0, 0, 5, 0, 0 }, // U+0347 (placeholder) - { 2565, 0, 0, 5, 0, 0 }, // U+0348 (placeholder) - { 2565, 0, 0, 5, 0, 0 }, // U+0349 (placeholder) - { 2565, 0, 0, 5, 0, 0 }, // U+034A (placeholder) - { 2565, 0, 0, 5, 0, 0 }, // U+034B (placeholder) - { 2565, 0, 0, 5, 0, 0 }, // U+034C (placeholder) - { 2565, 0, 0, 5, 0, 0 }, // U+034D (placeholder) - { 2565, 0, 0, 5, 0, 0 }, // U+034E (placeholder) - { 2565, 0, 0, 5, 0, 0 }, // U+034F (placeholder) - { 2565, 0, 0, 5, 0, 0 }, // U+0350 (placeholder) - { 2565, 0, 0, 5, 0, 0 }, // U+0351 (placeholder) - { 2565, 0, 0, 5, 0, 0 }, // U+0352 (placeholder) - { 2565, 0, 0, 5, 0, 0 }, // U+0353 (placeholder) - { 2565, 0, 0, 5, 0, 0 }, // U+0354 (placeholder) - { 2565, 0, 0, 5, 0, 0 }, // U+0355 (placeholder) - { 2565, 0, 0, 5, 0, 0 }, // U+0356 (placeholder) - { 2565, 0, 0, 5, 0, 0 }, // U+0357 (placeholder) - { 2565, 0, 0, 5, 0, 0 }, // U+0358 (placeholder) - { 2565, 0, 0, 5, 0, 0 }, // U+0359 (placeholder) - { 2565, 0, 0, 5, 0, 0 }, // U+035A (placeholder) - { 2565, 0, 0, 5, 0, 0 }, // U+035B (placeholder) - { 2565, 5, 2, 6, 0, -1 }, // U+035C COMBINING DOUBLE BREVE BELOW - { 2567, 0, 0, 5, 0, 0 }, // U+035D (placeholder) - { 2567, 0, 0, 5, 0, 0 }, // U+035E (placeholder) - { 2567, 0, 0, 5, 0, 0 }, // U+035F (placeholder) - { 2567, 0, 0, 5, 0, 0 }, // U+0360 (placeholder) - { 2567, 5, 2, 6, 0, -9 }, // U+0361 COMBINING DOUBLE INVERTED BREVE - { 2569, 0, 0, 5, 0, 0 }, // U+0362 (placeholder) - { 2569, 0, 0, 5, 0, 0 }, // U+0363 (placeholder) - { 2569, 0, 0, 5, 0, 0 }, // U+0364 (placeholder) - { 2569, 0, 0, 5, 0, 0 }, // U+0365 (placeholder) - { 2569, 0, 0, 5, 0, 0 }, // U+0366 (placeholder) - { 2569, 0, 0, 5, 0, 0 }, // U+0367 (placeholder) - { 2569, 0, 0, 5, 0, 0 }, // U+0368 (placeholder) - { 2569, 0, 0, 5, 0, 0 }, // U+0369 (placeholder) - { 2569, 0, 0, 5, 0, 0 }, // U+036A (placeholder) - { 2569, 0, 0, 5, 0, 0 }, // U+036B (placeholder) - { 2569, 0, 0, 5, 0, 0 }, // U+036C (placeholder) - { 2569, 0, 0, 5, 0, 0 }, // U+036D (placeholder) - { 2569, 0, 0, 5, 0, 0 }, // U+036E (placeholder) - { 2569, 0, 0, 5, 0, 0 }, // U+036F (placeholder) - { 2569, 4, 7, 5, 0, -7 }, // U+0370 GREEK CAPITAL LETTER HETA - { 2573, 5, 6, 6, 0, -6 }, // U+0371 GREEK SMALL LETTER HETA - { 2577, 5, 6, 6, 0, -6 }, // U+0372 GREEK CAPITAL LETTER ARCHAIC SAMPI - { 2581, 5, 5, 6, 0, -5 }, // U+0373 GREEK SMALL LETTER ARCHAIC SAMPI - { 2585, 3, 2, 4, 0, -8 }, // U+0374 GREEK NUMERAL SIGN - { 2586, 4, 3, 5, 0, -2 }, // U+0375 GREEK LOWER NUMERAL SIGN - { 2588, 4, 6, 5, 0, -6 }, // U+0376 uni004E - { 2591, 5, 6, 6, 0, -5 }, // U+0377 GREEK SMALL LETTER PAMPHYLIAN DIGAMMA - { 2595, 0, 0, 5, 0, 0 }, // U+0378 (placeholder) - { 2595, 0, 0, 5, 0, 0 }, // U+0379 (placeholder) - { 2595, 5, 2, 6, 0, 0 }, // U+037A GREEK YPOGEGRAMMENI - { 2597, 5, 5, 6, 0, -5 }, // U+037B GREEK SMALL REVERSED LUNATE SIGMA SYMBOL - { 2601, 5, 5, 6, 0, -5 }, // U+037C GREEK SMALL DOTTED LUNATE SIGMA SYMBOL - { 2605, 5, 5, 6, 0, -5 }, // U+037D GREEK SMALL REVERSED DOTTED LUNATE SIGMA SYMBOL - { 2609, 2, 6, 3, 0, -5 }, // U+037E uni003B - { 2611, 6, 3, 7, 0, -4 }, // U+037F uni037F - { 2614, 5, 3, 6, 0, -3 }, // U+0380 uni037F - { 2616, 5, 3, 6, 0, -3 }, // U+0381 uni0380 - { 2618, 0, 0, 5, 0, 0 }, // U+0382 (placeholder) - { 2618, 0, 0, 5, 0, 0 }, // U+0383 (placeholder) - { 2618, 2, 3, 3, 0, -8 }, // U+0384 GREEK TONOS - { 2619, 6, 3, 7, 0, -8 }, // U+0385 GREEK DIALYTIKA TONOS - { 2622, 4, 9, 5, 0, -9 }, // U+0386 GREEK CAPITAL LETTER ALPHA WITH TONOS - { 2627, 2, 2, 3, 0, -5 }, // U+0387 GREEK ANO TELEIA - { 2628, 4, 8, 5, 0, -8 }, // U+0388 char201 - { 2632, 5, 7, 6, 0, -7 }, // U+0389 GREEK CAPITAL LETTER ETA WITH TONOS - { 2637, 4, 7, 5, 0, -7 }, // U+038A GREEK CAPITAL LETTER IOTA WITH TONOS - { 2641, 0, 0, 5, 0, 0 }, // U+038B (placeholder) - { 2641, 5, 8, 6, 0, -8 }, // U+038C GREEK CAPITAL LETTER OMICRON WITH TONOS - { 2646, 0, 0, 5, 0, 0 }, // U+038D (placeholder) - { 2646, 5, 7, 6, 0, -7 }, // U+038E GREEK CAPITAL LETTER UPSILON WITH TONOS - { 2651, 5, 8, 6, 0, -8 }, // U+038F GREEK CAPITAL LETTER OMEGA WITH TONOS - { 2656, 6, 9, 7, 0, -9 }, // U+0390 GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS - { 2663, 4, 6, 5, 0, -6 }, // U+0391 GREEK CAPITAL LETTER ALPHA - { 2666, 4, 6, 5, 0, -6 }, // U+0392 uni0042 - { 2669, 4, 5, 5, 0, -5 }, // U+0393 GREEK CAPITAL LETTER GAMMA - { 2672, 5, 6, 6, 0, -6 }, // U+0394 GREEK CAPITAL LETTER DELTA - { 2676, 4, 6, 5, 0, -6 }, // U+0395 uni0045 - { 2679, 4, 6, 5, 0, -6 }, // U+0396 uni005A - { 2682, 4, 6, 5, 0, -6 }, // U+0397 uni0048 - { 2685, 4, 6, 5, 0, -6 }, // U+0398 GREEK CAPITAL LETTER THETA - { 2688, 3, 6, 4, 0, -6 }, // U+0399 GREEK CAPITAL LETTER IOTA - { 2691, 4, 6, 5, 0, -6 }, // U+039A uni004B - { 2694, 5, 6, 6, 0, -6 }, // U+039B GREEK CAPITAL LETTER LAMDA - { 2698, 4, 6, 5, 0, -6 }, // U+039C uni004D - { 2701, 4, 6, 5, 0, -6 }, // U+039D uni004E - { 2704, 5, 5, 6, 0, -5 }, // U+039E GREEK CAPITAL LETTER XI - { 2708, 4, 6, 5, 0, -6 }, // U+039F GREEK CAPITAL LETTER OMICRON - { 2711, 5, 6, 6, 0, -6 }, // U+03A0 char1055 - { 2715, 4, 6, 5, 0, -6 }, // U+03A1 uni0050 - { 2718, 0, 0, 5, 0, 0 }, // U+03A2 (placeholder) - { 2718, 5, 6, 6, 0, -6 }, // U+03A3 GREEK CAPITAL LETTER SIGMA - { 2722, 5, 6, 6, 0, -6 }, // U+03A4 GREEK CAPITAL LETTER TAU - { 2726, 5, 6, 6, 0, -6 }, // U+03A5 uni0059 - { 2730, 5, 7, 6, 0, -7 }, // U+03A6 GREEK CAPITAL LETTER PHI - { 2735, 4, 6, 5, 0, -6 }, // U+03A7 uni0058 - { 2738, 5, 6, 6, 0, -6 }, // U+03A8 GREEK CAPITAL LETTER PSI - { 2742, 5, 6, 6, 0, -6 }, // U+03A9 GREEK CAPITAL LETTER OMEGA - { 2746, 3, 7, 4, 0, -7 }, // U+03AA GREEK CAPITAL LETTER IOTA WITH DIALYTIKA - { 2749, 5, 8, 6, 0, -8 }, // U+03AB GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA - { 2754, 4, 8, 5, 0, -8 }, // U+03AC char225 - { 2758, 4, 8, 5, 0, -8 }, // U+03AD GREEK SMALL LETTER EPSILON WITH TONOS - { 2762, 4, 9, 5, 0, -8 }, // U+03AE uni006E - { 2767, 3, 8, 4, 0, -8 }, // U+03AF GREEK SMALL LETTER IOTA WITH TONOS - { 2770, 6, 8, 7, 0, -8 }, // U+03B0 GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS - { 2776, 4, 5, 5, 0, -5 }, // U+03B1 uni0061 - { 2779, 4, 7, 5, 0, -6 }, // U+03B2 char223 - { 2783, 4, 7, 5, 0, -5 }, // U+03B3 uni0076 - { 2787, 4, 7, 5, 0, -7 }, // U+03B4 GREEK SMALL LETTER DELTA - { 2791, 4, 5, 5, 0, -5 }, // U+03B5 GREEK SMALL LETTER EPSILON - { 2794, 4, 7, 5, 0, -6 }, // U+03B6 GREEK SMALL LETTER ZETA - { 2798, 4, 6, 5, 0, -5 }, // U+03B7 GREEK SMALL LETTER ETA - { 2801, 4, 5, 5, 0, -5 }, // U+03B8 GREEK SMALL LETTER THETA - { 2804, 2, 5, 3, 0, -5 }, // U+03B9 GREEK SMALL LETTER IOTA - { 2806, 4, 5, 5, 0, -5 }, // U+03BA uni006B - { 2809, 4, 7, 5, 0, -7 }, // U+03BB GREEK SMALL LETTER LAMDA - { 2813, 4, 6, 5, 0, -5 }, // U+03BC uni0075 - { 2816, 4, 5, 5, 0, -5 }, // U+03BD uni0076 - { 2819, 4, 8, 5, 0, -6 }, // U+03BE GREEK SMALL LETTER XI - { 2823, 4, 5, 5, 0, -5 }, // U+03BF GREEK SMALL LETTER OMICRON - { 2826, 5, 5, 6, 0, -5 }, // U+03C0 char960 - { 2830, 4, 8, 5, 0, -5 }, // U+03C1 uni0070 - { 2834, 4, 7, 5, 0, -5 }, // U+03C2 char231 - { 2838, 5, 5, 6, 0, -5 }, // U+03C3 char240 - { 2842, 4, 5, 5, 0, -5 }, // U+03C4 GREEK SMALL LETTER TAU - { 2845, 4, 5, 5, 0, -5 }, // U+03C5 uni0075 - { 2848, 5, 7, 6, 0, -5 }, // U+03C6 GREEK SMALL LETTER PHI - { 2853, 4, 5, 5, 0, -5 }, // U+03C7 uni0078 - { 2856, 5, 7, 6, 0, -5 }, // U+03C8 GREEK SMALL LETTER PSI - { 2861, 5, 5, 6, 0, -5 }, // U+03C9 GREEK SMALL LETTER OMEGA - { 2865, 3, 7, 4, 0, -7 }, // U+03CA GREEK SMALL LETTER IOTA WITH DIALYTIKA - { 2868, 5, 7, 6, 0, -7 }, // U+03CB char252 - { 2873, 4, 8, 5, 0, -8 }, // U+03CC char243 - { 2877, 5, 8, 6, 0, -8 }, // U+03CD char250 - { 2882, 5, 8, 6, 0, -8 }, // U+03CE GREEK SMALL LETTER OMEGA WITH TONOS - { 2887, 4, 7, 5, 0, -6 }, // U+03CF uni004B - { 2891, 4, 6, 5, 0, -6 }, // U+03D0 GREEK BETA SYMBOL - { 2894, 6, 6, 7, 0, -6 }, // U+03D1 GREEK THETA SYMBOL - { 2899, 6, 6, 7, 0, -6 }, // U+03D2 uni0059 - { 2904, 6, 7, 7, 0, -7 }, // U+03D3 uni0059 - { 2910, 6, 8, 7, 0, -8 }, // U+03D4 uni0059 - { 2916, 5, 9, 6, 0, -7 }, // U+03D5 GREEK PHI SYMBOL - { 2922, 5, 5, 6, 0, -5 }, // U+03D6 GREEK PI SYMBOL - { 2926, 5, 6, 6, 0, -5 }, // U+03D7 uni0078 - { 2930, 5, 8, 6, 0, -6 }, // U+03D8 GREEK LETTER ARCHAIC KOPPA - { 2935, 5, 7, 6, 0, -5 }, // U+03D9 GREEK SMALL LETTER ARCHAIC KOPPA - { 2940, 5, 8, 6, 0, -6 }, // U+03DA GREEK LETTER STIGMA - { 2945, 5, 7, 6, 0, -5 }, // U+03DB GREEK SMALL LETTER STIGMA - { 2950, 4, 6, 5, 0, -6 }, // U+03DC uni0046 - { 2953, 4, 6, 5, 0, -5 }, // U+03DD uni0046 - { 2956, 6, 6, 7, 0, -5 }, // U+03DE GREEK LETTER KOPPA - { 2961, 3, 7, 4, 0, -6 }, // U+03DF GREEK SMALL LETTER KOPPA - { 2964, 4, 7, 5, 0, -6 }, // U+03E0 GREEK LETTER SAMPI - { 2968, 4, 7, 5, 0, -6 }, // U+03E1 GREEK SMALL LETTER SAMPI - { 2972, 5, 7, 6, 0, -6 }, // U+03E2 COPTIC CAPITAL LETTER SHEI - { 2977, 5, 6, 6, 0, -4 }, // U+03E3 COPTIC SMALL LETTER SHEI - { 2981, 4, 7, 5, 0, -7 }, // U+03E4 COPTIC CAPITAL LETTER FEI - { 2985, 4, 6, 5, 0, -6 }, // U+03E5 COPTIC SMALL LETTER FEI - { 2988, 4, 9, 5, 0, -7 }, // U+03E6 COPTIC CAPITAL LETTER KHEI - { 2993, 5, 7, 6, 0, -6 }, // U+03E7 COPTIC SMALL LETTER KHEI - { 2998, 4, 6, 5, 0, -6 }, // U+03E8 uni0053 - { 3001, 4, 6, 5, 0, -6 }, // U+03E9 uni0053 - { 3004, 5, 7, 6, 0, -7 }, // U+03EA COPTIC CAPITAL LETTER GANGIA - { 3009, 5, 5, 6, 0, -5 }, // U+03EB COPTIC SMALL LETTER GANGIA - { 3013, 4, 7, 5, 0, -7 }, // U+03EC COPTIC CAPITAL LETTER SHIMA - { 3017, 4, 6, 5, 0, -6 }, // U+03ED COPTIC SMALL LETTER SHIMA - { 3020, 5, 6, 6, 0, -6 }, // U+03EE COPTIC CAPITAL LETTER DEI - { 3024, 5, 6, 6, 0, -6 }, // U+03EF COPTIC SMALL LETTER DEI - { 3028, 4, 5, 5, 0, -5 }, // U+03F0 uni0078 - { 3031, 4, 8, 5, 0, -5 }, // U+03F1 GREEK RHO SYMBOL - { 3035, 4, 5, 5, 0, -5 }, // U+03F2 uni0063 - { 3038, 3, 9, 4, 0, -7 }, // U+03F3 uni006A - { 3042, 4, 6, 5, 0, -6 }, // U+03F4 GREEK CAPITAL LETTER THETA - { 3045, 4, 5, 5, 0, -5 }, // U+03F5 GREEK LUNATE EPSILON SYMBOL - { 3048, 4, 5, 5, 0, -5 }, // U+03F6 GREEK REVERSED LUNATE EPSILON SYMBOL - { 3051, 4, 6, 5, 0, -6 }, // U+03F7 GREEK CAPITAL LETTER SHO - { 3054, 4, 7, 5, 0, -6 }, // U+03F8 GREEK SMALL LETTER SHO - { 3058, 4, 6, 5, 0, -6 }, // U+03F9 uni0043 - { 3061, 4, 6, 5, 0, -6 }, // U+03FA uni004D - { 3064, 4, 6, 5, 0, -4 }, // U+03FB GREEK SMALL LETTER SAN - { 3067, 4, 8, 5, 0, -5 }, // U+03FC uni0070 - { 3071, 5, 6, 6, 0, -6 }, // U+03FD uni0043 - { 3075, 4, 6, 5, 0, -6 }, // U+03FE uni0043 - { 3078, 4, 6, 5, 0, -6 }, // U+03FF uni0043 - { 3081, 4, 8, 5, 0, -8 }, // U+0400 char203 - { 3085, 4, 8, 5, 0, -8 }, // U+0401 char203 - { 3089, 5, 8, 6, 0, -7 }, // U+0402 char1026 - { 3094, 4, 8, 5, 0, -8 }, // U+0403 char1027 - { 3098, 4, 6, 5, 0, -6 }, // U+0404 char1028 - { 3101, 5, 6, 6, 0, -6 }, // U+0405 char1029 - { 3105, 3, 6, 4, 0, -6 }, // U+0406 char1030 - { 3108, 3, 7, 4, 0, -7 }, // U+0407 char1031 - { 3111, 5, 6, 6, 0, -6 }, // U+0408 char1032 - { 3115, 6, 6, 7, 0, -6 }, // U+0409 char1033 - { 3120, 5, 6, 6, 0, -6 }, // U+040A char1034 - { 3124, 5, 7, 6, 0, -7 }, // U+040B char1035 - { 3129, 5, 7, 6, 0, -7 }, // U+040C char1036 - { 3134, 4, 8, 5, 0, -8 }, // U+040D char1080 - { 3138, 4, 8, 5, 0, -8 }, // U+040E char1038 - { 3142, 5, 7, 6, 0, -6 }, // U+040F char1039 - { 3147, 4, 6, 5, 0, -6 }, // U+0410 char1040 - { 3150, 5, 7, 6, 0, -7 }, // U+0411 char1041 - { 3155, 4, 6, 5, 0, -6 }, // U+0412 char1042 - { 3158, 4, 6, 5, 0, -6 }, // U+0413 char1043 - { 3161, 5, 7, 6, 0, -6 }, // U+0414 char1044 - { 3166, 4, 6, 5, 0, -6 }, // U+0415 uni0045 - { 3169, 5, 6, 6, 0, -6 }, // U+0416 uni0058 - { 3173, 4, 6, 5, 0, -6 }, // U+0417 uni0033 - { 3176, 5, 6, 6, 0, -6 }, // U+0418 char1080 - { 3180, 4, 8, 5, 0, -8 }, // U+0419 char1080 - { 3184, 4, 6, 5, 0, -6 }, // U+041A uni004B - { 3187, 4, 6, 5, 0, -6 }, // U+041B char1051 - { 3190, 4, 6, 5, 0, -6 }, // U+041C uni004D - { 3193, 4, 6, 5, 0, -6 }, // U+041D uni0048 - { 3196, 4, 6, 5, 0, -6 }, // U+041E uni004F - { 3199, 5, 6, 6, 0, -6 }, // U+041F char1055 - { 3203, 5, 6, 6, 0, -6 }, // U+0420 char1104 - { 3207, 4, 6, 5, 0, -6 }, // U+0421 uni0043 - { 3210, 3, 6, 4, 0, -6 }, // U+0422 uni0054 - { 3213, 4, 6, 5, 0, -6 }, // U+0423 uni0079 - { 3216, 5, 8, 6, 0, -7 }, // U+0424 char1060 - { 3221, 4, 6, 5, 0, -6 }, // U+0425 uni0058 - { 3224, 4, 7, 5, 0, -6 }, // U+0426 char1062 - { 3228, 4, 6, 5, 0, -6 }, // U+0427 char1063 - { 3231, 5, 6, 6, 0, -6 }, // U+0428 char1064 - { 3235, 6, 7, 7, 0, -6 }, // U+0429 char1065 - { 3241, 4, 6, 5, 0, -6 }, // U+042A char1066 - { 3244, 5, 6, 6, 0, -6 }, // U+042B char1067 - { 3248, 5, 6, 6, 0, -6 }, // U+042C char1068 - { 3252, 4, 6, 5, 0, -6 }, // U+042D uni0033 - { 3255, 5, 6, 6, 0, -6 }, // U+042E char1070 - { 3259, 4, 6, 5, 0, -6 }, // U+042F char1071 - { 3262, 4, 5, 5, 0, -5 }, // U+0430 uni0061 - { 3265, 4, 7, 5, 0, -7 }, // U+0431 char1073 - { 3269, 4, 5, 5, 0, -5 }, // U+0432 char1074 - { 3272, 4, 5, 5, 0, -5 }, // U+0433 char1075 - { 3275, 5, 7, 6, 0, -6 }, // U+0434 char1044 - { 3280, 4, 5, 5, 0, -5 }, // U+0435 uni0065 - { 3283, 5, 5, 6, 0, -5 }, // U+0436 uni0058 - { 3287, 4, 5, 5, 0, -5 }, // U+0437 uni0033 - { 3290, 4, 5, 5, 0, -5 }, // U+0438 char1080 - { 3293, 4, 7, 5, 0, -7 }, // U+0439 char1080 - { 3297, 4, 5, 5, 0, -5 }, // U+043A uni006B - { 3300, 4, 5, 5, 0, -5 }, // U+043B char1051 - { 3303, 4, 5, 5, 0, -5 }, // U+043C uni006D - { 3306, 4, 5, 5, 0, -5 }, // U+043D uni006D - { 3309, 4, 5, 5, 0, -5 }, // U+043E uni006F - { 3312, 4, 5, 5, 0, -5 }, // U+043F uni006E - { 3315, 4, 7, 5, 0, -5 }, // U+0440 uni0070 - { 3319, 4, 5, 5, 0, -5 }, // U+0441 uni0063 - { 3322, 3, 5, 4, 0, -5 }, // U+0442 uni0074 - { 3324, 4, 6, 5, 0, -4 }, // U+0443 uni0079 - { 3327, 5, 7, 6, 0, -6 }, // U+0444 char1060 - { 3332, 4, 5, 5, 0, -5 }, // U+0445 uni0078 - { 3335, 4, 6, 5, 0, -5 }, // U+0446 char1062 - { 3338, 4, 5, 5, 0, -5 }, // U+0447 char1063 - { 3341, 5, 5, 6, 0, -5 }, // U+0448 char1064 - { 3345, 6, 6, 7, 0, -5 }, // U+0449 char1065 - { 3350, 4, 5, 5, 0, -5 }, // U+044A char1066 - { 3353, 4, 5, 5, 0, -5 }, // U+044B char1067 - { 3356, 4, 5, 5, 0, -5 }, // U+044C char1068 - { 3359, 4, 5, 5, 0, -5 }, // U+044D uni0033 - { 3362, 5, 5, 6, 0, -5 }, // U+044E char1070 - { 3366, 4, 5, 5, 0, -5 }, // U+044F char1071 - { 3369, 4, 8, 5, 0, -8 }, // U+0450 uni0065 - { 3373, 4, 7, 5, 0, -7 }, // U+0451 uni0065 - { 3377, 5, 7, 6, 0, -6 }, // U+0452 char1026 - { 3382, 4, 7, 5, 0, -7 }, // U+0453 char1027 - { 3386, 4, 5, 5, 0, -5 }, // U+0454 char1028 - { 3389, 4, 5, 5, 0, -5 }, // U+0455 uni0073 - { 3392, 2, 7, 3, 0, -7 }, // U+0456 uni0069 - { 3394, 3, 7, 4, 0, -7 }, // U+0457 uni0069 - { 3397, 3, 9, 4, 0, -7 }, // U+0458 uni006A - { 3401, 6, 5, 7, 0, -5 }, // U+0459 CYRILLIC SMALL LETTER LJE - { 3405, 5, 5, 6, 0, -5 }, // U+045A CYRILLIC SMALL LETTER NJE - { 3409, 5, 7, 6, 0, -7 }, // U+045B CYRILLIC SMALL LETTER TSHE - { 3414, 4, 8, 5, 0, -8 }, // U+045C CYRILLIC SMALL LETTER KJE - { 3418, 4, 8, 5, 0, -8 }, // U+045D CYRILLIC SMALL LETTER I WITH GRAVE - { 3422, 4, 10, 5, 0, -8 }, // U+045E uni0079 - { 3427, 5, 7, 6, 0, -5 }, // U+045F uni0079 - { 3432, 5, 7, 6, 0, -7 }, // U+0460 CYRILLIC CAPITAL LETTER OMEGA - { 3437, 5, 5, 6, 0, -5 }, // U+0461 CYRILLIC SMALL LETTER OMEGA - { 3441, 5, 7, 6, 0, -7 }, // U+0462 char1122 - { 3446, 5, 6, 6, 0, -6 }, // U+0463 char1123 - { 3450, 5, 7, 6, 0, -7 }, // U+0464 CYRILLIC CAPITAL LETTER IOTIFIED E - { 3455, 5, 5, 6, 0, -5 }, // U+0465 CYRILLIC SMALL LETTER IOTIFIED E - { 3459, 5, 6, 6, 0, -6 }, // U+0466 char1126 - { 3463, 5, 5, 6, 0, -5 }, // U+0467 char1127 - { 3467, 5, 6, 6, 0, -6 }, // U+0468 char1128 - { 3471, 5, 5, 6, 0, -5 }, // U+0469 char1129 - { 3475, 5, 7, 6, 0, -7 }, // U+046A CYRILLIC CAPITAL LETTER BIG YUS - { 3480, 5, 5, 6, 0, -5 }, // U+046B CYRILLIC SMALL LETTER BIG YUS - { 3484, 5, 7, 6, 0, -7 }, // U+046C CYRILLIC CAPITAL LETTER IOTIFIED BIG YUS - { 3489, 5, 5, 6, 0, -5 }, // U+046D CYRILLIC SMALL LETTER IOTIFIED BIG YUS - { 3493, 4, 11, 5, 0, -9 }, // U+046E CYRILLIC CAPITAL LETTER KSI - { 3499, 4, 10, 5, 0, -8 }, // U+046F CYRILLIC SMALL LETTER KSI - { 3504, 5, 7, 6, 0, -7 }, // U+0470 CYRILLIC CAPITAL LETTER PSI - { 3509, 5, 6, 6, 0, -6 }, // U+0471 CYRILLIC SMALL LETTER PSI - { 3513, 4, 7, 5, 0, -7 }, // U+0472 CYRILLIC CAPITAL LETTER FITA - { 3517, 4, 5, 5, 0, -5 }, // U+0473 CYRILLIC SMALL LETTER FITA - { 3520, 5, 6, 6, 0, -6 }, // U+0474 char1140 - { 3524, 5, 5, 6, 0, -5 }, // U+0475 char1141 - { 3528, 6, 8, 7, 0, -8 }, // U+0476 char1142 - { 3534, 5, 7, 6, 0, -7 }, // U+0477 char1143 - { 3539, 5, 9, 6, 0, -7 }, // U+0478 CYRILLIC CAPITAL LETTER UK - { 3545, 5, 7, 6, 0, -5 }, // U+0479 CYRILLIC SMALL LETTER UK - { 3550, 5, 9, 6, 0, -8 }, // U+047A CYRILLIC CAPITAL LETTER ROUND OMEGA - { 3556, 5, 7, 6, 0, -6 }, // U+047B CYRILLIC SMALL LETTER ROUND OMEGA - { 3561, 5, 9, 6, 0, -9 }, // U+047C CYRILLIC CAPITAL LETTER OMEGA WITH TITLO - { 3567, 5, 8, 6, 0, -8 }, // U+047D CYRILLIC SMALL LETTER OMEGA WITH TITLO - { 3572, 5, 9, 6, 0, -9 }, // U+047E CYRILLIC CAPITAL LETTER OT - { 3578, 5, 8, 6, 0, -8 }, // U+047F CYRILLIC SMALL LETTER OT - { 3583, 4, 8, 5, 0, -6 }, // U+0480 uni0043 - { 3587, 4, 7, 5, 0, -5 }, // U+0481 uni0063 - { 3591, 4, 8, 5, 0, -8 }, // U+0482 CYRILLIC THOUSANDS SIGN - { 3595, 4, 3, 5, 0, -7 }, // U+0483 char1155 - { 3597, 4, 8, 5, 0, -8 }, // U+0484 uni006F - { 3601, 4, 8, 5, 0, -8 }, // U+0485 uni006F - { 3605, 4, 8, 5, 0, -8 }, // U+0486 uni006F - { 3609, 4, 8, 5, 0, -8 }, // U+0487 uni006F - { 3613, 4, 8, 5, 0, -8 }, // U+0488 uni006F - { 3617, 7, 5, 8, 0, -5 }, // U+0489 char1161 - { 3622, 7, 5, 8, 0, -5 }, // U+048A char1161 - { 3627, 5, 9, 6, 0, -8 }, // U+048B char1080 - { 3633, 5, 8, 6, 0, -7 }, // U+048C char1080 - { 3638, 5, 6, 6, 0, -6 }, // U+048D char1068 - { 3642, 5, 6, 6, 0, -6 }, // U+048E uni0050 - { 3646, 5, 7, 6, 0, -5 }, // U+048F uni0070 - { 3651, 4, 7, 5, 0, -7 }, // U+0490 char1027 - { 3655, 4, 6, 5, 0, -6 }, // U+0491 char1075 - { 3658, 5, 6, 6, 0, -6 }, // U+0492 uni0046 - { 3662, 5, 5, 6, 0, -5 }, // U+0493 uni0046 - { 3666, 4, 7, 5, 0, -6 }, // U+0494 char1026 - { 3670, 4, 6, 5, 0, -5 }, // U+0495 char1026 - { 3673, 6, 7, 7, 0, -6 }, // U+0496 uni0058 - { 3679, 6, 6, 7, 0, -5 }, // U+0497 uni0058 - { 3684, 4, 8, 5, 0, -6 }, // U+0498 uni0033 - { 3688, 4, 7, 5, 0, -5 }, // U+0499 uni0033 - { 3692, 5, 7, 6, 0, -6 }, // U+049A uni004B - { 3697, 5, 6, 6, 0, -5 }, // U+049B uni006B - { 3701, 4, 6, 5, 0, -6 }, // U+049C uni004B - { 3704, 4, 5, 5, 0, -5 }, // U+049D uni006B - { 3707, 5, 6, 6, 0, -6 }, // U+049E uni004B - { 3711, 5, 5, 6, 0, -5 }, // U+049F uni006B - { 3715, 5, 6, 6, 0, -6 }, // U+04A0 uni004B - { 3719, 5, 5, 6, 0, -5 }, // U+04A1 uni006B - { 3723, 5, 7, 6, 0, -6 }, // U+04A2 uni0048 - { 3728, 5, 6, 6, 0, -5 }, // U+04A3 uni006D - { 3732, 5, 6, 6, 0, -6 }, // U+04A4 uni0048 - { 3736, 5, 5, 6, 0, -5 }, // U+04A5 uni006D - { 3740, 6, 8, 7, 0, -6 }, // U+04A6 char1055 - { 3746, 6, 7, 7, 0, -5 }, // U+04A7 uni006E - { 3752, 5, 7, 6, 0, -6 }, // U+04A8 char1060 - { 3757, 5, 6, 6, 0, -5 }, // U+04A9 char1060 - { 3761, 4, 8, 5, 0, -6 }, // U+04AA uni0043 - { 3765, 4, 7, 5, 0, -5 }, // U+04AB uni0063 - { 3769, 5, 7, 6, 0, -6 }, // U+04AC uni0054 - { 3774, 5, 6, 6, 0, -5 }, // U+04AD uni0054 - { 3778, 5, 6, 6, 0, -6 }, // U+04AE uni0059 - { 3782, 5, 6, 6, 0, -4 }, // U+04AF uni0059 - { 3786, 5, 6, 6, 0, -6 }, // U+04B0 uni0059 - { 3790, 5, 6, 6, 0, -4 }, // U+04B1 uni0059 - { 3794, 5, 7, 6, 0, -6 }, // U+04B2 uni0058 - { 3799, 5, 6, 6, 0, -5 }, // U+04B3 uni0078 - { 3803, 6, 7, 7, 0, -6 }, // U+04B4 uni0054 - { 3809, 6, 6, 7, 0, -5 }, // U+04B5 uni0054 - { 3814, 5, 7, 6, 0, -6 }, // U+04B6 char1063 - { 3819, 5, 6, 6, 0, -5 }, // U+04B7 char1063 - { 3823, 4, 6, 5, 0, -6 }, // U+04B8 char1063 - { 3826, 4, 5, 5, 0, -5 }, // U+04B9 char1063 - { 3829, 4, 7, 5, 0, -7 }, // U+04BA uni0068 - { 3833, 4, 7, 5, 0, -7 }, // U+04BB uni0068 - { 3837, 5, 6, 6, 0, -6 }, // U+04BC uni0065 - { 3841, 5, 5, 6, 0, -5 }, // U+04BD uni0065 - { 3845, 5, 7, 6, 0, -6 }, // U+04BE uni0065 - { 3850, 5, 6, 6, 0, -5 }, // U+04BF uni0065 - { 3854, 3, 6, 4, 0, -6 }, // U+04C0 char1030 - { 3857, 5, 8, 6, 0, -8 }, // U+04C1 uni0058 - { 3862, 5, 8, 6, 0, -8 }, // U+04C2 uni0058 - { 3867, 4, 7, 5, 0, -6 }, // U+04C3 uni004B - { 3871, 4, 6, 5, 0, -5 }, // U+04C4 uni006B - { 3874, 5, 7, 6, 0, -6 }, // U+04C5 char1051 - { 3879, 5, 6, 6, 0, -5 }, // U+04C6 char1051 - { 3883, 4, 7, 5, 0, -6 }, // U+04C7 uni0048 - { 3887, 4, 6, 5, 0, -5 }, // U+04C8 uni006D - { 3890, 5, 7, 6, 0, -6 }, // U+04C9 uni0048 - { 3895, 5, 6, 6, 0, -5 }, // U+04CA uni006D - { 3899, 4, 7, 5, 0, -6 }, // U+04CB char1063 - { 3903, 4, 6, 5, 0, -5 }, // U+04CC char1063 - { 3906, 5, 7, 6, 0, -6 }, // U+04CD uni004D - { 3911, 5, 6, 6, 0, -5 }, // U+04CE uni006D - { 3915, 3, 6, 4, 0, -6 }, // U+04CF char1030 - { 3918, 4, 8, 5, 0, -8 }, // U+04D0 char1040 - { 3922, 4, 8, 5, 0, -8 }, // U+04D1 uni0061 - { 3926, 4, 8, 5, 0, -8 }, // U+04D2 char1040 - { 3930, 4, 7, 5, 0, -7 }, // U+04D3 uni0061 - { 3934, 6, 6, 7, 0, -6 }, // U+04D4 char198 - { 3939, 5, 5, 6, 0, -5 }, // U+04D5 uni0061 - { 3943, 4, 8, 5, 0, -8 }, // U+04D6 uni0045 - { 3947, 4, 8, 5, 0, -8 }, // U+04D7 uni0065 - { 3951, 4, 6, 5, 0, -6 }, // U+04D8 uni0065 - { 3954, 4, 5, 5, 0, -5 }, // U+04D9 uni0065 - { 3957, 4, 8, 5, 0, -8 }, // U+04DA uni0065 - { 3961, 4, 7, 5, 0, -7 }, // U+04DB uni0065 - { 3965, 5, 8, 6, 0, -8 }, // U+04DC uni0058 - { 3970, 5, 7, 6, 0, -7 }, // U+04DD uni0058 - { 3975, 4, 8, 5, 0, -8 }, // U+04DE uni0033 - { 3979, 4, 7, 5, 0, -7 }, // U+04DF uni0033 - { 3983, 4, 8, 5, 0, -8 }, // U+04E0 uni0033 - { 3987, 4, 7, 5, 0, -7 }, // U+04E1 uni0033 - { 3991, 4, 8, 5, 0, -8 }, // U+04E2 char1080 - { 3995, 4, 7, 5, 0, -7 }, // U+04E3 char1080 - { 3999, 4, 8, 5, 0, -8 }, // U+04E4 char1080 - { 4003, 4, 7, 5, 0, -7 }, // U+04E5 char1080 - { 4007, 4, 8, 5, 0, -8 }, // U+04E6 uni004F - { 4011, 4, 7, 5, 0, -7 }, // U+04E7 uni006F - { 4015, 4, 6, 5, 0, -6 }, // U+04E8 uni004F - { 4018, 4, 5, 5, 0, -5 }, // U+04E9 uni006F - { 4021, 4, 8, 5, 0, -8 }, // U+04EA uni004F - { 4025, 4, 5, 5, 0, -5 }, // U+04EB uni006F - { 4028, 4, 8, 5, 0, -8 }, // U+04EC uni0033 - { 4032, 4, 7, 5, 0, -7 }, // U+04ED uni0033 - { 4036, 5, 8, 6, 0, -8 }, // U+04EE uni0059 - { 4041, 5, 8, 6, 0, -6 }, // U+04EF uni0059 - { 4046, 4, 8, 5, 0, -8 }, // U+04F0 uni0079 - { 4050, 4, 8, 5, 0, -6 }, // U+04F1 uni0079 - { 4054, 4, 8, 5, 0, -8 }, // U+04F2 uni0079 - { 4058, 4, 8, 5, 0, -6 }, // U+04F3 uni0079 - { 4062, 4, 8, 5, 0, -8 }, // U+04F4 char1063 - { 4066, 4, 7, 5, 0, -7 }, // U+04F5 char1063 - { 4070, 4, 7, 5, 0, -6 }, // U+04F6 char1043 - { 4074, 5, 6, 6, 0, -5 }, // U+04F7 char1075 - { 4078, 4, 8, 5, 0, -8 }, // U+04F8 char1067 - { 4082, 4, 7, 5, 0, -7 }, // U+04F9 char1067 - { 4086, 5, 8, 6, 0, -6 }, // U+04FA char1043 - { 4091, 5, 7, 6, 0, -5 }, // U+04FB char1075 - { 4096, 5, 7, 6, 0, -6 }, // U+04FC uni0058 - { 4101, 5, 6, 6, 0, -5 }, // U+04FD uni0078 - { 4105, 4, 6, 5, 0, -6 }, // U+04FE uni0058 - { 4108, 4, 5, 5, 0, -5 }, // U+04FF uni0078 -}; - -static const GFXfont Lemon PROGMEM = { - (uint8_t*)lemonBitmaps, - (GFXglyph*)lemonGlyphs, - 0x0020, 0x04FF, - 10 // yAdvance -}; diff --git a/src/helpers/ui/SH1106Display.cpp b/src/helpers/ui/SH1106Display.cpp index f8afb372..464201d9 100644 --- a/src/helpers/ui/SH1106Display.cpp +++ b/src/helpers/ui/SH1106Display.cpp @@ -93,7 +93,7 @@ int16_t SH1106Display::drawLemonChar(int16_t x, int16_t y, uint32_t cp) { // Font glyphs come from misc-fixed 6x9 (full Latin/Greek/Cyrillic, ascent 7) — // baseline +7. The custom UI icons above keep +6, so they sit 1px higher. if (cp < MiscFixed.first || cp > MiscFixed.last) { - if (cp >= 0x20) display.fillRect(x + sz, y - sz, 4*sz, 6*sz, _color); + if (cp >= 0x20) display.fillRect(x + sz, y - 7*sz, 4*sz, 6*sz, _color); return x + 6 * sz; } const GFXglyph* g = &MiscFixedGlyphs[cp - MiscFixed.first]; diff --git a/src/helpers/ui/SH1106Display.h b/src/helpers/ui/SH1106Display.h index 9d6eb102..15a5ef6b 100644 --- a/src/helpers/ui/SH1106Display.h +++ b/src/helpers/ui/SH1106Display.h @@ -21,7 +21,7 @@ class SH1106Display : public DisplayDriver uint8_t _color; uint8_t _contrast; uint8_t _precharge; - bool _use_lemon = true; // OLED is single-font (misc-fixed 5x7); the Lemon/default switch is retired here + bool _use_lemon = true; // OLED is single-font (misc-fixed 6x9); the Lemon/default switch is retired here int _text_sz; // Frame-skip: endFrame() hashes the GFX buffer (FNV-1a, no external dep — the // CRC32 lib is only wired into e-ink builds) and skips the I²C flush when it's @@ -57,13 +57,13 @@ public: if (_use_lemon) return lemonXAdvance(cp); return 6 * _text_sz; // built-in 5x7 font: 6 px advance per glyph } - int getCharWidth() const override { return (_use_lemon ? 6 : 6) * _text_sz; } // misc-fixed 6x9 is 6px wide + int getCharWidth() const override { return 6 * _text_sz; } // misc-fixed 6x9 is 6px wide int getLineHeight() const override { return (_use_lemon ? 9 : 8) * _text_sz; } // misc-fixed 6x9 box height // Only the built-in classic font pads every measured string by one trailing // advance column (see DisplayDriver::textWidthTrailingGap()); the lemon // font's width comes from its own glyph table (ink-tight, no padding). int textWidthTrailingGap() const override { return _use_lemon ? 0 : 1; } - void setLemonFont(bool) override { } // single-font: ignore toggles, stay misc-fixed 5x7 + void setLemonFont(bool) override { } // single-font: ignore toggles, stay misc-fixed 6x9 bool isLemonFont() const override { return _use_lemon; } void translateUTF8ToBlocks(char* dest, const char* src, size_t dest_size) override; void setBrightness(uint8_t level) override; diff --git a/tools/bdf2gfx.py b/tools/bdf2gfx.py index 76339a47..8ac8be22 100644 --- a/tools/bdf2gfx.py +++ b/tools/bdf2gfx.py @@ -48,7 +48,6 @@ def pack_glyph_bits(g): for row in range(h): rowval = g['bitmap'][row] if row < len(g['bitmap']) else 0 # BDF rows are left-justified, padded to a multiple of 8 bits. - rowbits = ((len(bin(rowval)) - 2 + 7)//8*8) if rowval else 8 rowbytes = max(1, (w + 7)//8) for col in range(w): byte_i = col // 8