mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-08-28 13:34:17 +00:00
Merge pull request #58 from torlando-tech/fix/nomadnet-unicode
fix(nomadnet): render bounded Unicode and preserve icons
This commit is contained in:
+1
-1
@@ -93,7 +93,7 @@
|
||||
#define LV_FONT_DEFAULT &lv_font_montserrat_14
|
||||
|
||||
#define LV_FONT_FMT_TXT_LARGE 0
|
||||
#define LV_USE_FONT_COMPRESSED 0
|
||||
#define LV_USE_FONT_COMPRESSED 1 /* Required by generated NomadNet font bitmaps */
|
||||
#define LV_USE_FONT_SUBPX 0
|
||||
|
||||
/*====================
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
DejaVu Fonts License
|
||||
|
||||
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved.
|
||||
Bitstream Vera is a trademark of Bitstream, Inc.
|
||||
DejaVu changes are in public domain.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of the fonts accompanying this license ("Fonts") and associated
|
||||
documentation files (the "Font Software"), to reproduce and distribute the
|
||||
Font Software, including without limitation the rights to use, copy, merge,
|
||||
publish, distribute, and/or sell copies of the Font Software, and to permit
|
||||
persons to whom the Font Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright and trademark notices and this permission notice shall
|
||||
be included in all copies of one or more of the Font Software typefaces.
|
||||
|
||||
The Font Software may be modified, altered, or added to, and in particular
|
||||
the designs of glyphs or characters in the Fonts may be modified and
|
||||
additional glyphs or characters may be added to the Fonts, only if the fonts
|
||||
are renamed to names not containing either the words "Bitstream" or the word
|
||||
"Vera".
|
||||
|
||||
This License becomes null and void to the extent applicable to Fonts or Font
|
||||
Software that has been modified and is distributed under the "Bitstream
|
||||
Vera" names.
|
||||
|
||||
The Font Software may be sold as part of a larger software package but no
|
||||
copy of one or more of the Font Software typefaces may be sold by itself.
|
||||
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
|
||||
TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
|
||||
FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING
|
||||
ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, CONSEQUENTIAL DAMAGES, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR
|
||||
INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT
|
||||
SOFTWARE.
|
||||
|
||||
Except as contained in this notice, the names of Gnome, the Gnome
|
||||
Foundation, and Bitstream Inc., shall not be used in advertising or
|
||||
otherwise to promote the sale, use or other dealings in this Font Software
|
||||
without prior written authorization from the Gnome Foundation or Bitstream
|
||||
Inc., respectively. For further information, contact: fonts at gnome dot
|
||||
org.
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// Exact codepoint contract shared by the generated LVGL font adapters and the
|
||||
// host-testable UTF-8 display sanitizer. Keeping this separate prevents LVGL's
|
||||
// inclusive range-length check from treating the first codepoint after a
|
||||
// contiguous cmap as a valid (and potentially out-of-bounds) glyph.
|
||||
static inline bool nomadnet_font_has_codepoint(uint32_t codepoint) {
|
||||
if (codepoint >= 0x20 && codepoint <= 0x7e) return true;
|
||||
if (codepoint >= 0x00a0 && codepoint <= 0x017f) return true;
|
||||
if (codepoint >= 0x2190 && codepoint <= 0x2199) return true;
|
||||
|
||||
switch (codepoint) {
|
||||
case 0x2007: case 0x2008: case 0x2009: case 0x200a: case 0x200b:
|
||||
case 0x2010: case 0x2012: case 0x2013: case 0x2014: case 0x2015:
|
||||
case 0x2018: case 0x2019: case 0x201a: case 0x201c: case 0x201d:
|
||||
case 0x201e: case 0x2020: case 0x2021: case 0x2022: case 0x2026:
|
||||
case 0x2030: case 0x2032: case 0x2033: case 0x2039: case 0x203a:
|
||||
case 0x2044: case 0x2052:
|
||||
case 0x20a1: case 0x20a3: case 0x20a4: case 0x20a6: case 0x20a7:
|
||||
case 0x20a9: case 0x20ab: case 0x20ac: case 0x20ad: case 0x20ae:
|
||||
case 0x20b1: case 0x20b2: case 0x20b4: case 0x20b5: case 0x20b8:
|
||||
case 0x20b9: case 0x20ba: case 0x20bc: case 0x20bd:
|
||||
case 0x2500: case 0x2550: case 0x2551: case 0x2554: case 0x2557:
|
||||
case 0x255a: case 0x255d: case 0x2588: case 0x2594: case 0x25a0:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,77 @@
|
||||
#include "NomadNetGlyphs.h"
|
||||
#include "../Fonts/NomadNetFontCoverage.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace UI::LXMF::NomadNet {
|
||||
namespace {
|
||||
|
||||
bool decode_utf8(const std::string& value, std::size_t offset,
|
||||
uint32_t& codepoint, std::size_t& length) {
|
||||
const auto lead = static_cast<uint8_t>(value[offset]);
|
||||
if (lead <= 0x7f) {
|
||||
codepoint = lead;
|
||||
length = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::size_t continuation = 0;
|
||||
if (lead >= 0xc2 && lead <= 0xdf) {
|
||||
codepoint = lead & 0x1f;
|
||||
continuation = 1;
|
||||
} else if (lead >= 0xe0 && lead <= 0xef) {
|
||||
codepoint = lead & 0x0f;
|
||||
continuation = 2;
|
||||
} else if (lead >= 0xf0 && lead <= 0xf4) {
|
||||
codepoint = lead & 0x07;
|
||||
continuation = 3;
|
||||
} else {
|
||||
length = 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
length = continuation + 1;
|
||||
if (offset + length > value.size()) {
|
||||
length = 1;
|
||||
return false;
|
||||
}
|
||||
for (std::size_t i = 1; i <= continuation; ++i) {
|
||||
const auto byte = static_cast<uint8_t>(value[offset + i]);
|
||||
if ((byte & 0xc0) != 0x80) {
|
||||
length = 1;
|
||||
return false;
|
||||
}
|
||||
codepoint = (codepoint << 6) | (byte & 0x3f);
|
||||
}
|
||||
if ((continuation == 1 && codepoint < 0x80) ||
|
||||
(continuation == 2 && codepoint < 0x800) ||
|
||||
(continuation == 3 && codepoint < 0x10000) ||
|
||||
(codepoint >= 0xd800 && codepoint <= 0xdfff) ||
|
||||
codepoint > 0x10ffff) {
|
||||
length = 1;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string display_text(const std::string& utf8) {
|
||||
std::string output;
|
||||
output.reserve(utf8.size());
|
||||
for (std::size_t offset = 0; offset < utf8.size();) {
|
||||
uint32_t codepoint = 0;
|
||||
std::size_t length = 1;
|
||||
if (!decode_utf8(utf8, offset, codepoint, length) ||
|
||||
!nomadnet_font_has_codepoint(codepoint)) {
|
||||
output.push_back('?');
|
||||
} else {
|
||||
output.append(utf8, offset, length);
|
||||
}
|
||||
offset += length;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
} // namespace UI::LXMF::NomadNet
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#ifdef ARDUINO
|
||||
#include <lvgl.h>
|
||||
LV_FONT_DECLARE(nomadnet_font_12)
|
||||
LV_FONT_DECLARE(nomadnet_font_16)
|
||||
#endif
|
||||
|
||||
namespace UI::LXMF::NomadNet {
|
||||
|
||||
// The bounded browser fonts contain printable ASCII, all glyphs in
|
||||
// U+00A0-U+017F, and selected available glyphs from U+2000-U+206F,
|
||||
// U+20A0-U+20CF, U+2190-U+21FF, and ten exact box/block glyphs observed on
|
||||
// live NomadNet pages. Other valid Unicode is replaced at the display boundary
|
||||
// so LVGL never draws its missing-glyph rectangle.
|
||||
std::string display_text(const std::string& utf8);
|
||||
|
||||
} // namespace UI::LXMF::NomadNet
|
||||
@@ -2,6 +2,7 @@
|
||||
#ifdef ARDUINO
|
||||
#include "Theme.h"
|
||||
#include "NomadNetDisplay.h"
|
||||
#include "NomadNetGlyphs.h"
|
||||
#include "../LVGL/LVGLInit.h"
|
||||
#include "../TextAreaHelper.h"
|
||||
#include <algorithm>
|
||||
@@ -33,13 +34,13 @@ NomadNetScreen::NomadNetScreen() {
|
||||
lv_obj_set_style_bg_color(_address_row,Theme::surface(),0);lv_obj_set_style_border_width(_address_row,0,0);lv_obj_set_style_pad_all(_address_row,3,0);
|
||||
_address=lv_textarea_create(_address_row);lv_obj_set_size(_address,270,30);lv_obj_align(_address,LV_ALIGN_LEFT_MID,0,0);
|
||||
lv_textarea_set_one_line(_address,true);lv_textarea_set_max_length(_address,511);lv_textarea_set_placeholder_text(_address,"destination:/page/path");
|
||||
lv_obj_set_style_text_font(_address,&lv_font_montserrat_12,0);lv_obj_set_style_bg_color(_address,Theme::surfaceInput(),0);TextAreaHelper::enable_paste(_address);
|
||||
lv_obj_set_style_text_font(_address,&nomadnet_font_12,0);lv_obj_set_style_bg_color(_address,Theme::surfaceInput(),0);TextAreaHelper::enable_paste(_address);
|
||||
_go_button=lv_btn_create(_address_row);lv_obj_set_size(_go_button,42,30);lv_obj_align(_go_button,LV_ALIGN_RIGHT_MID,0,0);
|
||||
lv_obj_set_style_bg_color(_go_button,Theme::primary(),0);lv_obj_set_style_radius(_go_button,8,0);
|
||||
lv_obj_t* gl=lv_label_create(_go_button);lv_label_set_text(gl,"Go");lv_obj_center(gl);
|
||||
|
||||
_address_summary=lv_label_create(_address_row);lv_obj_set_size(_address_summary,266,24);lv_obj_align(_address_summary,LV_ALIGN_LEFT_MID,4,0);
|
||||
lv_label_set_long_mode(_address_summary,LV_LABEL_LONG_DOT);lv_obj_set_style_text_font(_address_summary,&lv_font_montserrat_12,0);
|
||||
lv_label_set_long_mode(_address_summary,LV_LABEL_LONG_DOT);lv_obj_set_style_text_font(_address_summary,&nomadnet_font_12,0);
|
||||
lv_obj_set_style_text_color(_address_summary,Theme::textSecondary(),0);
|
||||
_edit_button=lv_btn_create(_address_row);lv_obj_set_size(_edit_button,36,26);lv_obj_align(_edit_button,LV_ALIGN_RIGHT_MID,0,0);
|
||||
lv_obj_set_style_bg_color(_edit_button,Theme::surfaceContainer(),0);lv_obj_set_style_bg_color(_edit_button,Theme::primaryPressed(),LV_STATE_FOCUSED);
|
||||
@@ -65,7 +66,7 @@ NomadNetScreen::NomadNetScreen() {
|
||||
NomadNetScreen::~NomadNetScreen(){if(_screen)lv_obj_del(_screen);}
|
||||
void NomadNetScreen::set_address(const std::string& value){
|
||||
lv_textarea_set_text(_address,value.c_str());
|
||||
const auto summary=NomadNet::compact_address(value);
|
||||
const auto summary=NomadNet::display_text(NomadNet::compact_address(value));
|
||||
lv_label_set_text(_address_summary,summary.c_str());
|
||||
}
|
||||
std::string NomadNetScreen::address()const{return lv_textarea_get_text(_address);}
|
||||
@@ -117,15 +118,24 @@ void NomadNetScreen::render_directory(View view){
|
||||
lv_obj_clear_flag(_directory,LV_OBJ_FLAG_HIDDEN);
|
||||
for(auto* object:{_address_row,_status,_content,_reload_button,_save_button})lv_obj_add_flag(object,LV_OBJ_FLAG_HIDDEN);
|
||||
|
||||
auto add_row=[&](const std::string& title,const std::string& detail,std::size_t code){
|
||||
auto add_row=[&](const std::string& title,const std::string& detail,std::size_t code,const char* symbol=nullptr){
|
||||
lv_obj_t* button=lv_btn_create(_directory);lv_obj_set_size(button,306,35);lv_obj_set_flex_grow(button,0);
|
||||
lv_obj_set_style_bg_color(button,Theme::surfaceContainer(),0);lv_obj_set_style_bg_color(button,Theme::primaryPressed(),LV_STATE_FOCUSED);
|
||||
lv_obj_set_style_border_width(button,0,0);lv_obj_set_style_radius(button,8,0);lv_obj_set_style_pad_all(button,4,0);
|
||||
lv_obj_t* primary=lv_label_create(button);lv_label_set_text(primary,title.c_str());lv_label_set_long_mode(primary,LV_LABEL_LONG_DOT);
|
||||
lv_obj_set_width(primary,286);lv_obj_set_style_text_font(primary,&lv_font_montserrat_12,0);lv_obj_align(primary,LV_ALIGN_TOP_LEFT,2,0);
|
||||
lv_coord_t title_x=2;
|
||||
lv_coord_t title_width=286;
|
||||
if(symbol){
|
||||
lv_obj_t* icon=lv_label_create(button);lv_label_set_text(icon,symbol);
|
||||
lv_obj_set_style_text_font(icon,&lv_font_montserrat_12,0);lv_obj_align(icon,LV_ALIGN_TOP_LEFT,2,0);
|
||||
title_x=20;title_width=268;
|
||||
}
|
||||
const auto rendered_title=NomadNet::display_text(title);
|
||||
lv_obj_t* primary=lv_label_create(button);lv_label_set_text(primary,rendered_title.c_str());lv_label_set_long_mode(primary,LV_LABEL_LONG_DOT);
|
||||
lv_obj_set_width(primary,title_width);lv_obj_set_style_text_font(primary,&nomadnet_font_12,0);lv_obj_align(primary,LV_ALIGN_TOP_LEFT,title_x,0);
|
||||
if(!detail.empty()){
|
||||
lv_obj_t* secondary=lv_label_create(button);lv_label_set_text(secondary,detail.c_str());lv_label_set_long_mode(secondary,LV_LABEL_LONG_DOT);
|
||||
lv_obj_set_width(secondary,286);lv_obj_set_style_text_font(secondary,&lv_font_montserrat_12,0);lv_obj_set_style_text_color(secondary,Theme::textTertiary(),0);
|
||||
const auto rendered_detail=NomadNet::display_text(detail);
|
||||
lv_obj_t* secondary=lv_label_create(button);lv_label_set_text(secondary,rendered_detail.c_str());lv_label_set_long_mode(secondary,LV_LABEL_LONG_DOT);
|
||||
lv_obj_set_width(secondary,286);lv_obj_set_style_text_font(secondary,&nomadnet_font_12,0);lv_obj_set_style_text_color(secondary,Theme::textTertiary(),0);
|
||||
lv_obj_align(secondary,LV_ALIGN_BOTTOM_LEFT,2,0);
|
||||
}
|
||||
lv_obj_set_user_data(button,reinterpret_cast<void*>(code));lv_obj_add_event_cb(button,clicked,LV_EVENT_CLICKED,this);
|
||||
@@ -133,11 +143,11 @@ void NomadNetScreen::render_directory(View view){
|
||||
};
|
||||
|
||||
if(view==View::START){
|
||||
add_row(LV_SYMBOL_DIRECTORY " Heard Nodes","Recently announced NomadNet nodes",1001);
|
||||
add_row(LV_SYMBOL_DIRECTORY " Saved Nodes","Bookmarked destinations",1002);
|
||||
add_row(LV_SYMBOL_SAVE " Saved Pages","Bookmarked destination and path",1003);
|
||||
add_row(LV_SYMBOL_LIST " Recent Pages","Bounded browsing history",1004);
|
||||
add_row(LV_SYMBOL_EDIT " Enter Address","Advanced destination/path entry",1005);
|
||||
add_row("Heard Nodes","Recently announced NomadNet nodes",1001,LV_SYMBOL_DIRECTORY);
|
||||
add_row("Saved Nodes","Bookmarked destinations",1002,LV_SYMBOL_DIRECTORY);
|
||||
add_row("Saved Pages","Bookmarked destination and path",1003,LV_SYMBOL_SAVE);
|
||||
add_row("Recent Pages","Bounded browsing history",1004,LV_SYMBOL_LIST);
|
||||
add_row("Enter Address","Advanced destination/path entry",1005,LV_SYMBOL_EDIT);
|
||||
}else if(view==View::HEARD||view==View::SAVED_NODES){
|
||||
for(const auto& node:_library.nodes()){
|
||||
if(view==View::SAVED_NODES&&!node.saved)continue;
|
||||
@@ -159,7 +169,7 @@ void NomadNetScreen::render_directory(View view){
|
||||
if(_directory_focusables.empty()){
|
||||
lv_obj_t* empty=lv_label_create(_directory);
|
||||
lv_label_set_text(empty,view==View::HEARD?"No NomadNet nodes heard yet":view==View::SAVED_NODES?"No saved nodes":view==View::SAVED_PAGES?"No saved pages":"No recent pages");
|
||||
lv_obj_set_style_text_color(empty,Theme::textTertiary(),0);lv_obj_set_style_text_font(empty,&lv_font_montserrat_12,0);
|
||||
lv_obj_set_style_text_color(empty,Theme::textTertiary(),0);lv_obj_set_style_text_font(empty,&nomadnet_font_12,0);
|
||||
}
|
||||
rebuild_focus();
|
||||
}
|
||||
@@ -265,7 +275,8 @@ void NomadNetScreen::set_page(const NomadNet::Document& document) {
|
||||
for (const auto& run : block.runs) {
|
||||
if (spans >= MAX_UI_SPANS) { render_truncated = true; break; }
|
||||
lv_span_t* span = lv_spangroup_new_span(text);
|
||||
lv_span_set_text(span, run.text.c_str());
|
||||
const auto rendered_text = NomadNet::display_text(run.text);
|
||||
lv_span_set_text(span, rendered_text.c_str());
|
||||
const bool is_link = run.link_index >= 0 &&
|
||||
static_cast<std::size_t>(run.link_index) < document.links.size();
|
||||
lv_style_set_text_color(&span->style, is_link ? Theme::primaryLight() :
|
||||
@@ -273,7 +284,7 @@ void NomadNetScreen::set_page(const NomadNet::Document& document) {
|
||||
document.has_foreground ? lv_color_hex(document.foreground) : Theme::textPrimary());
|
||||
lv_style_set_text_font(&span->style,
|
||||
(run.bold || block.type == NomadNet::BlockType::HEADING)
|
||||
? &lv_font_montserrat_16 : &lv_font_montserrat_12);
|
||||
? &nomadnet_font_16 : &nomadnet_font_12);
|
||||
lv_style_set_text_line_space(&span->style, 3);
|
||||
if (run.underline || is_link)
|
||||
lv_style_set_text_decor(&span->style, LV_TEXT_DECOR_UNDERLINE);
|
||||
@@ -292,8 +303,9 @@ void NomadNetScreen::set_page(const NomadNet::Document& document) {
|
||||
lv_obj_set_size(button, 304, 28);
|
||||
lv_obj_set_style_pad_all(button, 4, 0);
|
||||
lv_obj_t* label = lv_label_create(button);
|
||||
const std::string caption = "Open: " + run.text;
|
||||
const std::string caption = "Open: " + NomadNet::display_text(run.text);
|
||||
lv_label_set_text(label, caption.c_str());
|
||||
lv_obj_set_style_text_font(label, &nomadnet_font_12, 0);
|
||||
lv_label_set_long_mode(label, LV_LABEL_LONG_DOT);
|
||||
lv_obj_set_width(label, 294);
|
||||
lv_obj_center(label);
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"+<Hardware/TDeck/*.cpp>",
|
||||
"+<UI/*.cpp>",
|
||||
"+<UI/LVGL/*.cpp>",
|
||||
"+<UI/Fonts/*.c>",
|
||||
"+<UI/LXMF/*.cpp>"
|
||||
],
|
||||
"includeDir": "."
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "NomadNetDocument.h"
|
||||
#include "NomadNetDisplay.h"
|
||||
#include "NomadNetHistory.h"
|
||||
#include "NomadNetGlyphs.h"
|
||||
#include "NomadNetLibrary.h"
|
||||
#include "NomadNetActionMailbox.h"
|
||||
#include "NomadNetMailbox.h"
|
||||
@@ -24,6 +25,7 @@ using UI::LXMF::NomadNet::BlockType;
|
||||
using UI::LXMF::NomadNet::DocumentParser;
|
||||
using UI::LXMF::NomadNet::compact_address;
|
||||
using UI::LXMF::NomadNet::PageHistory;
|
||||
using UI::LXMF::NomadNet::display_text;
|
||||
using UI::LXMF::NomadNet::Library;
|
||||
using UI::LXMF::NomadNet::ActionMailbox;
|
||||
using UI::LXMF::NomadNet::UserAction;
|
||||
@@ -170,6 +172,17 @@ int main(int argc, char** argv) {
|
||||
auto utf8_doc = parser.parse(invalid_utf8);
|
||||
check("invalid UTF-8 is rejected before rendering", utf8_doc.malformed && utf8_doc.blocks.empty());
|
||||
|
||||
check("common NomadNet Unicode punctuation remains intact",
|
||||
display_text(u8"release · stable — open → details • done") ==
|
||||
u8"release · stable — open → details • done");
|
||||
check("Latin accents remain intact", display_text(u8"café Ångström") == u8"café Ångström");
|
||||
check("live NomadNet box and block drawing glyphs remain intact",
|
||||
display_text(u8"─═║╔╗╚╝█▔■") == u8"─═║╔╗╚╝█▔■");
|
||||
check("glyphs outside the bounded browser font degrade without rectangles",
|
||||
display_text(u8"status 😀 ok") == "status ? ok");
|
||||
check("contiguous cmap boundary codepoints degrade safely",
|
||||
display_text(std::string("\x7f") + u8"ƀ↚") == "???");
|
||||
|
||||
std::string huge(DocumentParser::MAX_DOCUMENT_BYTES, 'x');
|
||||
huge += "\n#!c=99\n";
|
||||
auto huge_doc = parser.parse(huge);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import shutil
|
||||
import subprocess
|
||||
import hashlib
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
@@ -36,6 +37,7 @@ def test_app_launcher_nomadnet_native(tmp_path):
|
||||
_cxx(), "-std=c++17", "-Wall", "-Wextra", "-Werror",
|
||||
f"-I{INCLUDE}", str(SOURCE),
|
||||
str(INCLUDE / "NomadNetDocument.cpp"),
|
||||
str(INCLUDE / "NomadNetGlyphs.cpp"),
|
||||
str(INCLUDE / "NomadNetLibrary.cpp"),
|
||||
str(INCLUDE / "NomadNetUrl.cpp"),
|
||||
"-o", str(binary),
|
||||
@@ -49,11 +51,13 @@ def test_app_launcher_nomadnet_native(tmp_path):
|
||||
|
||||
|
||||
def test_ui_wiring_contract():
|
||||
library_json = (ROOT / "lib" / "tdeck_ui" / "library.json").read_text()
|
||||
manager_h = (INCLUDE / "UIManager.h").read_text()
|
||||
manager_cpp = (INCLUDE / "UIManager.cpp").read_text()
|
||||
launcher_cpp = (INCLUDE / "HomeScreen.cpp").read_text()
|
||||
network_cpp = (INCLUDE / "NetworkScreen.cpp").read_text()
|
||||
browser_cpp = (INCLUDE / "NomadNetScreen.cpp").read_text()
|
||||
glyphs_h = (INCLUDE / "NomadNetGlyphs.h").read_text()
|
||||
|
||||
for tile in ("Messages", "NomadNet", "Network", "Settings"):
|
||||
assert tile in launcher_cpp
|
||||
@@ -92,6 +96,13 @@ def test_ui_wiring_contract():
|
||||
assert "lv_group_focus_obj(_focusables.front())" not in browser_cpp
|
||||
assert "LV_SYMBOL_HOME" in browser_cpp
|
||||
assert "lv_font_" in browser_cpp
|
||||
assert "nomadnet_font_12" in browser_cpp
|
||||
assert "nomadnet_font_16" in browser_cpp
|
||||
assert "display_text" in browser_cpp
|
||||
assert "U+00A0-U+017F" in glyphs_h
|
||||
assert "U+2000-U+206F" in glyphs_h
|
||||
assert "U+2190-U+21FF" in glyphs_h
|
||||
assert "+<UI/Fonts/*.c>" in library_json
|
||||
for detail in ("Inbox & calls", "Browse Micron", "Links & radio", "Device options"):
|
||||
assert detail in launcher_cpp
|
||||
for detail in ("Interfaces & storage", "Signal history", "Delivery relays"):
|
||||
@@ -106,6 +117,52 @@ def test_ui_wiring_contract():
|
||||
assert "set_save_callback" in manager_cpp
|
||||
|
||||
|
||||
def test_bounded_nomadnet_fonts_match_display_allowlist():
|
||||
expected = set(range(0x20, 0x7F)) | set(range(0xA0, 0x180))
|
||||
expected |= set(range(0x2190, 0x219A))
|
||||
expected |= {
|
||||
0x2007, 0x2008, 0x2009, 0x200A, 0x200B,
|
||||
0x2010, 0x2012, 0x2013, 0x2014, 0x2015,
|
||||
0x2018, 0x2019, 0x201A, 0x201C, 0x201D, 0x201E,
|
||||
0x2020, 0x2021, 0x2022, 0x2026, 0x2030, 0x2032,
|
||||
0x2033, 0x2039, 0x203A, 0x2044, 0x2052,
|
||||
0x20A1, 0x20A3, 0x20A4, 0x20A6, 0x20A7, 0x20A9,
|
||||
0x20AB, 0x20AC, 0x20AD, 0x20AE, 0x20B1, 0x20B2,
|
||||
0x20B4, 0x20B5, 0x20B8, 0x20B9, 0x20BA, 0x20BC, 0x20BD,
|
||||
0x2500, 0x2550, 0x2551, 0x2554, 0x2557,
|
||||
0x255A, 0x255D, 0x2588, 0x2594, 0x25A0,
|
||||
}
|
||||
for size in (12, 16):
|
||||
source = (ROOT / "lib" / "tdeck_ui" / "UI" / "Fonts" /
|
||||
f"nomadnet_font_{size}.c").read_text()
|
||||
encoded = {int(value, 16) for value in re.findall(r"/\* U\+([0-9A-F]+)", source)}
|
||||
assert encoded == expected
|
||||
assert f".get_glyph_dsc = nomadnet_font_{size}_get_glyph_dsc" in source
|
||||
assert f".get_glyph_bitmap = nomadnet_font_{size}_get_glyph_bitmap" in source
|
||||
assert "nomadnet_font_has_codepoint(letter)" in source
|
||||
|
||||
|
||||
def test_generated_font_compression_is_enabled_in_lvgl():
|
||||
config = (ROOT / "lib" / "lv_conf.h").read_text()
|
||||
for size in (12, 16):
|
||||
source = (ROOT / "lib" / "tdeck_ui" / "UI" / "Fonts" /
|
||||
f"nomadnet_font_{size}.c").read_text()
|
||||
assert ".bitmap_format = 1" in source
|
||||
assert "#if !LV_USE_FONT_COMPRESSED" in source
|
||||
assert "#error \"NomadNet fonts require LV_USE_FONT_COMPRESSED=1\"" in source
|
||||
assert "#define LV_USE_FONT_COMPRESSED 1" in config
|
||||
|
||||
|
||||
def test_directory_icons_use_lvgl_symbol_font_separately_from_remote_text():
|
||||
screen = (INCLUDE / "NomadNetScreen.cpp").read_text()
|
||||
assert "add_row(LV_SYMBOL" not in screen
|
||||
assert "const char* symbol=nullptr" in screen
|
||||
assert "lv_label_set_text(icon,symbol)" in screen
|
||||
assert "lv_obj_set_style_text_font(icon,&lv_font_montserrat_12,0)" in screen
|
||||
for symbol in ("LV_SYMBOL_DIRECTORY", "LV_SYMBOL_SAVE", "LV_SYMBOL_LIST", "LV_SYMBOL_EDIT"):
|
||||
assert f",{symbol});" in screen
|
||||
|
||||
|
||||
def test_nomadnet_latency_and_path_lifecycle_contracts():
|
||||
manager_cpp = (INCLUDE / "UIManager.cpp").read_text()
|
||||
main_cpp = (ROOT / "src" / "main.cpp").read_text()
|
||||
|
||||
Reference in New Issue
Block a user