From 24e0eaaf07cb4781dd47ee3e34eb47b4af8e605c Mon Sep 17 00:00:00 2001 From: Vinzenz Schroeter Date: Sat, 23 Nov 2024 23:47:41 +0100 Subject: [PATCH 01/10] sp_packet_from_parts, sp_bitmap_new_screen_sized --- crates/servicepoint_binding_c/src/bitmap.rs | 16 ++++++ crates/servicepoint_binding_c/src/packet.rs | 59 ++++++++++++++++++++- 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/crates/servicepoint_binding_c/src/bitmap.rs b/crates/servicepoint_binding_c/src/bitmap.rs index a7ae48a..2956de3 100644 --- a/crates/servicepoint_binding_c/src/bitmap.rs +++ b/crates/servicepoint_binding_c/src/bitmap.rs @@ -49,6 +49,22 @@ pub unsafe extern "C" fn sp_bitmap_new( NonNull::from(Box::leak(result)) } +/// Creates a new [SPBitmap] with a size matching the screen. +/// +/// returns: [SPBitmap] initialized to all pixels off. Will never return NULL. +/// +/// # Safety +/// +/// The caller has to make sure that: +/// +/// - the returned instance is freed in some way, either by using a consuming function or +/// by explicitly calling [sp_bitmap_free]. +#[no_mangle] +pub unsafe extern "C" fn sp_bitmap_new_screen_sized() -> NonNull { + let result = Box::new(SPBitmap(servicepoint::Bitmap::max_sized())); + NonNull::from(Box::leak(result)) +} + /// Loads a [SPBitmap] with the specified dimensions from the provided data. /// /// # Arguments diff --git a/crates/servicepoint_binding_c/src/packet.rs b/crates/servicepoint_binding_c/src/packet.rs index e44c23f..1f7082a 100644 --- a/crates/servicepoint_binding_c/src/packet.rs +++ b/crates/servicepoint_binding_c/src/packet.rs @@ -65,6 +65,63 @@ pub unsafe extern "C" fn sp_packet_try_load( } } +/// Creates a raw [SPPacket] from parts. +/// +/// # Arguments +/// +/// - `command_code` specifies which command this packet contains +/// - `a`, `b`, `c` and `d` are command-specific header values +/// - `payload` is the optional data that is part of the command +/// - `payload_len` is the size of the payload +/// +/// returns: new instance. Will never return null. +/// +/// # Panics +/// +/// - when `payload` is null, but `payload_len` is not zero +/// - when `payload_len` is zero, but `payload` is nonnull +/// +/// # Safety +/// +/// The caller has to make sure that: +/// +/// - `payload` points to a valid memory region of at least `payload_len` bytes +/// - `payload` is not written to concurrently +/// - the returned [SPPacket] instance is freed in some way, either by using a consuming function or +/// by explicitly calling [sp_packet_free]. +#[no_mangle] +pub unsafe extern "C" fn sp_packet_from_parts( + command_code: u16, + a: u16, + b: u16, + c: u16, + d: u16, + payload: *const u8, + payload_len: usize, +) -> NonNull { + assert_eq!(payload.is_null(), payload_len == 0); + + let payload = if payload.is_null() { + vec![] + } else { + let payload = std::slice::from_raw_parts(payload, payload_len); + Vec::from(payload) + }; + + let packet = servicepoint::packet::Packet { + header: servicepoint::packet::Header { + command_code, + a, + b, + c, + d, + }, + payload, + }; + let result = Box::new(SPPacket(packet)); + NonNull::from(Box::leak(result)) +} + /// Clones a [SPPacket]. /// /// Will never return NULL. @@ -94,7 +151,7 @@ pub unsafe extern "C" fn sp_packet_clone( /// /// # Panics /// -/// - when `sp_packet_free` is NULL +/// - when `packet` is NULL /// /// # Safety /// From 38316169e94ce1b3bdbc736512afb625024f28f3 Mon Sep 17 00:00:00 2001 From: Vinzenz Schroeter Date: Sun, 24 Nov 2024 16:12:38 +0100 Subject: [PATCH 02/10] update dependencies and flake update dependencies --- .github/workflows/rust.yml | 3 + Cargo.lock | 268 +++++++++--------- Cargo.toml | 2 +- crates/servicepoint/Cargo.toml | 10 +- crates/servicepoint/src/connection.rs | 2 +- .../examples/lang_c/Cargo.toml | 2 +- crates/servicepoint_binding_uniffi/Cargo.toml | 4 +- flake.lock | 14 +- flake.nix | 4 +- 9 files changed, 162 insertions(+), 147 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 86f2771..0480ccf 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -17,6 +17,9 @@ jobs: steps: - uses: actions/checkout@v4 + - name: install lzma + run: sudo apt-get update && sudo apt-get install -y liblzma-dev + - name: build default features run: cargo build --all --verbose - name: build default features -- examples diff --git a/Cargo.lock b/Cargo.lock index 69d32db..788e680 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,7 +44,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -54,14 +54,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] name = "anyhow" -version = "1.0.93" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "askama" @@ -108,7 +108,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] @@ -186,9 +186,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be" [[package]] name = "bitvec" @@ -219,15 +219,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "bzip2" -version = "0.4.4" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +checksum = "bafdbf26611df8c14810e268ddceda071c297570a5fb360ceddf617fe417ef58" dependencies = [ "bzip2-sys", "libc", @@ -255,9 +255,9 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" dependencies = [ "serde", ] @@ -273,7 +273,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -282,24 +282,24 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fce8dd7fcfcbf3a0a87d8f515194b49d6135acab73e18bd380d1d93bb1a15eb" dependencies = [ - "clap 4.5.20", + "clap 4.5.26", "heck 0.4.1", - "indexmap 2.6.0", + "indexmap 2.7.0", "log", "proc-macro2", "quote", "serde", "serde_json", - "syn 2.0.87", + "syn 2.0.96", "tempfile", "toml 0.8.19", ] [[package]] name = "cc" -version = "1.2.0" +version = "1.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aeb932158bd710538c73702db6945cb68a8fb08c519e6e12706b94263b36db8" +checksum = "c8293772165d9345bdaaa39b45b2109591e63fe5e6fbc23c6ff930a048aa310b" dependencies = [ "jobserver", "libc", @@ -331,23 +331,23 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.20" +version = "4.5.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783" dependencies = [ "clap_builder", - "clap_derive 4.5.18", + "clap_derive 4.5.24", ] [[package]] name = "clap_builder" -version = "4.5.20" +version = "4.5.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121" dependencies = [ "anstream", "anstyle", - "clap_lex 0.7.2", + "clap_lex 0.7.4", "strsim 0.11.1", ] @@ -366,14 +366,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] @@ -387,9 +387,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "colorchoice" @@ -399,9 +399,9 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "cpufeatures" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -449,12 +449,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -465,20 +465,20 @@ checksum = "311a6d2f1f9d60bff73d2c78a0af97ed27f79672f15c238192a5bbb64db56d00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] name = "fastrand" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "flate2" -version = "1.0.34" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", "miniz_oxide", @@ -528,9 +528,9 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "goblin" @@ -551,9 +551,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "heck" @@ -578,9 +578,9 @@ dependencies = [ [[package]] name = "http" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" dependencies = [ "bytes", "fnv", @@ -605,12 +605,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown 0.15.1", + "hashbrown 0.15.2", ] [[package]] @@ -621,9 +621,9 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jobserver" @@ -644,15 +644,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.162" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "log" @@ -690,9 +690,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" dependencies = [ "adler2", ] @@ -778,18 +778,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -842,15 +842,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.40" +version = "0.38.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" +checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.7.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -876,43 +876,43 @@ checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] name = "semver" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.215" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] name = "serde_json" -version = "1.0.132" +version = "1.0.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" dependencies = [ "itoa", "memchr", @@ -935,13 +935,13 @@ version = "0.12.0" dependencies = [ "bitvec", "bzip2", - "clap 4.5.20", + "clap 4.5.26", "flate2", "log", "once_cell", "rand", "rust-lzma", - "thiserror", + "thiserror 2.0.11", "tungstenite", "zstd", ] @@ -959,7 +959,7 @@ name = "servicepoint_binding_uniffi" version = "0.12.0" dependencies = [ "servicepoint", - "thiserror", + "thiserror 2.0.11", "uniffi", "uniffi-bindgen-cs", "uniffi-bindgen-go", @@ -1025,9 +1025,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.87" +version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", @@ -1042,15 +1042,16 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.14.0" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" dependencies = [ "cfg-if", "fastrand", + "getrandom", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -1079,7 +1080,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +dependencies = [ + "thiserror-impl 2.0.11", ] [[package]] @@ -1090,7 +1100,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", ] [[package]] @@ -1129,7 +1150,7 @@ version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.7.0", "serde", "serde_spanned", "toml_datetime", @@ -1138,9 +1159,9 @@ dependencies = [ [[package]] name = "tungstenite" -version = "0.24.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" +checksum = "413083a99c579593656008130e29255e54dcaae495be556cc26888f211648c24" dependencies = [ "byteorder", "bytes", @@ -1150,7 +1171,7 @@ dependencies = [ "log", "rand", "sha1", - "thiserror", + "thiserror 2.0.11", "utf-8", ] @@ -1162,15 +1183,15 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicase" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-linebreak" @@ -1218,8 +1239,8 @@ dependencies = [ [[package]] name = "uniffi-bindgen-go" -version = "0.2.1+v0.25.0" -source = "git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=a77dc0462dc18d53846c758155ab4e0a42e5b240#a77dc0462dc18d53846c758155ab4e0a42e5b240" +version = "0.2.2+v0.25.0" +source = "git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=ba23bab72f1a9bcc39ce81924d3d9265598e017c#ba23bab72f1a9bcc39ce81924d3d9265598e017c" dependencies = [ "anyhow", "askama 0.12.1", @@ -1234,15 +1255,15 @@ dependencies = [ "serde_json", "textwrap", "toml 0.5.11", - "uniffi_bindgen 0.25.0 (git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=a77dc0462dc18d53846c758155ab4e0a42e5b240)", - "uniffi_meta 0.25.0 (git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=a77dc0462dc18d53846c758155ab4e0a42e5b240)", - "uniffi_udl 0.25.0 (git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=a77dc0462dc18d53846c758155ab4e0a42e5b240)", + "uniffi_bindgen 0.25.0 (git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=ba23bab72f1a9bcc39ce81924d3d9265598e017c)", + "uniffi_meta 0.25.0 (git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=ba23bab72f1a9bcc39ce81924d3d9265598e017c)", + "uniffi_udl 0.25.0 (git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=ba23bab72f1a9bcc39ce81924d3d9265598e017c)", ] [[package]] name = "uniffi_bindgen" version = "0.25.0" -source = "git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=a77dc0462dc18d53846c758155ab4e0a42e5b240#a77dc0462dc18d53846c758155ab4e0a42e5b240" +source = "git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=ba23bab72f1a9bcc39ce81924d3d9265598e017c#ba23bab72f1a9bcc39ce81924d3d9265598e017c" dependencies = [ "anyhow", "askama 0.12.1", @@ -1257,9 +1278,9 @@ dependencies = [ "serde", "textwrap", "toml 0.5.11", - "uniffi_meta 0.25.0 (git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=a77dc0462dc18d53846c758155ab4e0a42e5b240)", - "uniffi_testing 0.25.0 (git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=a77dc0462dc18d53846c758155ab4e0a42e5b240)", - "uniffi_udl 0.25.0 (git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=a77dc0462dc18d53846c758155ab4e0a42e5b240)", + "uniffi_meta 0.25.0 (git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=ba23bab72f1a9bcc39ce81924d3d9265598e017c)", + "uniffi_testing 0.25.0 (git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=ba23bab72f1a9bcc39ce81924d3d9265598e017c)", + "uniffi_udl 0.25.0 (git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=ba23bab72f1a9bcc39ce81924d3d9265598e017c)", ] [[package]] @@ -1322,10 +1343,10 @@ dependencies = [ [[package]] name = "uniffi_checksum_derive" version = "0.25.0" -source = "git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=a77dc0462dc18d53846c758155ab4e0a42e5b240#a77dc0462dc18d53846c758155ab4e0a42e5b240" +source = "git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=ba23bab72f1a9bcc39ce81924d3d9265598e017c#ba23bab72f1a9bcc39ce81924d3d9265598e017c" dependencies = [ "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] @@ -1334,7 +1355,7 @@ version = "0.25.0" source = "git+https://github.com/NordSecurity/uniffi-bindgen-cs?rev=f68639fbc720b50ebe561ba75c66c84dc456bdce#f68639fbc720b50ebe561ba75c66c84dc456bdce" dependencies = [ "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] @@ -1344,7 +1365,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55137c122f712d9330fd985d66fa61bdc381752e89c35708c13ce63049a3002c" dependencies = [ "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] @@ -1376,7 +1397,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.87", + "syn 2.0.96", "toml 0.5.11", "uniffi_build", "uniffi_meta 0.25.3", @@ -1385,12 +1406,12 @@ dependencies = [ [[package]] name = "uniffi_meta" version = "0.25.0" -source = "git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=a77dc0462dc18d53846c758155ab4e0a42e5b240#a77dc0462dc18d53846c758155ab4e0a42e5b240" +source = "git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=ba23bab72f1a9bcc39ce81924d3d9265598e017c#ba23bab72f1a9bcc39ce81924d3d9265598e017c" dependencies = [ "anyhow", "bytes", "siphasher", - "uniffi_checksum_derive 0.25.0 (git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=a77dc0462dc18d53846c758155ab4e0a42e5b240)", + "uniffi_checksum_derive 0.25.0 (git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=ba23bab72f1a9bcc39ce81924d3d9265598e017c)", ] [[package]] @@ -1419,7 +1440,7 @@ dependencies = [ [[package]] name = "uniffi_testing" version = "0.25.0" -source = "git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=a77dc0462dc18d53846c758155ab4e0a42e5b240#a77dc0462dc18d53846c758155ab4e0a42e5b240" +source = "git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=ba23bab72f1a9bcc39ce81924d3d9265598e017c#ba23bab72f1a9bcc39ce81924d3d9265598e017c" dependencies = [ "anyhow", "camino", @@ -1456,12 +1477,12 @@ dependencies = [ [[package]] name = "uniffi_udl" version = "0.25.0" -source = "git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=a77dc0462dc18d53846c758155ab4e0a42e5b240#a77dc0462dc18d53846c758155ab4e0a42e5b240" +source = "git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=ba23bab72f1a9bcc39ce81924d3d9265598e017c#ba23bab72f1a9bcc39ce81924d3d9265598e017c" dependencies = [ "anyhow", - "uniffi_meta 0.25.0 (git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=a77dc0462dc18d53846c758155ab4e0a42e5b240)", - "uniffi_testing 0.25.0 (git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=a77dc0462dc18d53846c758155ab4e0a42e5b240)", - "weedle2 4.0.0 (git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=a77dc0462dc18d53846c758155ab4e0a42e5b240)", + "uniffi_meta 0.25.0 (git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=ba23bab72f1a9bcc39ce81924d3d9265598e017c)", + "uniffi_testing 0.25.0 (git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=ba23bab72f1a9bcc39ce81924d3d9265598e017c)", + "weedle2 4.0.0 (git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=ba23bab72f1a9bcc39ce81924d3d9265598e017c)", ] [[package]] @@ -1529,7 +1550,7 @@ dependencies = [ [[package]] name = "weedle2" version = "4.0.0" -source = "git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=a77dc0462dc18d53846c758155ab4e0a42e5b240#a77dc0462dc18d53846c758155ab4e0a42e5b240" +source = "git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=ba23bab72f1a9bcc39ce81924d3d9265598e017c#ba23bab72f1a9bcc39ce81924d3d9265598e017c" dependencies = [ "nom", ] @@ -1564,7 +1585,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -1573,15 +1594,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-sys" version = "0.59.0" @@ -1657,9 +1669,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.20" +version = "0.6.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" dependencies = [ "memchr", ] @@ -1691,7 +1703,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 20457c9..1b5a109 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,4 +14,4 @@ version = "0.12.0" missing-docs = "warn" [workspace.dependencies] -thiserror = "1.0.69" +thiserror = "2.0" diff --git a/crates/servicepoint/Cargo.toml b/crates/servicepoint/Cargo.toml index 3497c6c..a167b16 100644 --- a/crates/servicepoint/Cargo.toml +++ b/crates/servicepoint/Cargo.toml @@ -16,12 +16,12 @@ crate-type = ["rlib"] log = "0.4" bitvec = "1.0" flate2 = { version = "1.0", optional = true } -bzip2 = { version = "0.4", optional = true } +bzip2 = { version = "0.5", optional = true } zstd = { version = "0.13", optional = true } -rust-lzma = { version = "0.6.0", optional = true } +rust-lzma = { version = "0.6", optional = true } rand = { version = "0.8", optional = true } -tungstenite = { version = "0.24.0", optional = true } -once_cell = { version = "1.20.2", optional = true } +tungstenite = { version = "0.26", optional = true } +once_cell = { version = "1.20", optional = true } thiserror.workspace = true [features] @@ -56,4 +56,4 @@ clap = { version = "4.5", features = ["derive"] } workspace = true [package.metadata.docs.rs] -all-features = true \ No newline at end of file +all-features = true diff --git a/crates/servicepoint/src/connection.rs b/crates/servicepoint/src/connection.rs index 9d53611..046257f 100644 --- a/crates/servicepoint/src/connection.rs +++ b/crates/servicepoint/src/connection.rs @@ -144,7 +144,7 @@ impl Connection { Connection::WebSocket(socket) => { let mut socket = socket.lock().unwrap(); socket - .send(tungstenite::Message::Binary(data)) + .send(tungstenite::Message::Binary(data.into())) .map_err(SendError::WebsocketError) } Connection::Fake => { diff --git a/crates/servicepoint_binding_c/examples/lang_c/Cargo.toml b/crates/servicepoint_binding_c/examples/lang_c/Cargo.toml index 4997310..2231f3c 100644 --- a/crates/servicepoint_binding_c/examples/lang_c/Cargo.toml +++ b/crates/servicepoint_binding_c/examples/lang_c/Cargo.toml @@ -8,7 +8,7 @@ publish = false test = false [build-dependencies] -cc = "1.0" +cc = "1.2" [dependencies] servicepoint_binding_c = { path = "../.." } diff --git a/crates/servicepoint_binding_uniffi/Cargo.toml b/crates/servicepoint_binding_uniffi/Cargo.toml index c5c901a..0365d19 100644 --- a/crates/servicepoint_binding_uniffi/Cargo.toml +++ b/crates/servicepoint_binding_uniffi/Cargo.toml @@ -32,8 +32,8 @@ optional = true [dependencies.uniffi-bindgen-go] git = "https://github.com/NordSecurity/uniffi-bindgen-go.git" -# tag = "0.2.1+v0.25.0" -rev = "a77dc0462dc18d53846c758155ab4e0a42e5b240" +# tag = "0.2.2+v0.25.0" +rev = "ba23bab72f1a9bcc39ce81924d3d9265598e017c" optional = true [lints] diff --git a/flake.lock b/flake.lock index 16ff6f5..a7ec191 100644 --- a/flake.lock +++ b/flake.lock @@ -7,11 +7,11 @@ ] }, "locked": { - "lastModified": 1721727458, - "narHash": "sha256-r/xppY958gmZ4oTfLiHN0ZGuQ+RSTijDblVgVLFi1mw=", + "lastModified": 1736429655, + "narHash": "sha256-BwMekRuVlSB9C0QgwKMICiJ5EVbLGjfe4qyueyNQyGI=", "owner": "nix-community", "repo": "naersk", - "rev": "3fb418eaf352498f6b6c30592e3beb63df42ef11", + "rev": "0621e47bd95542b8e1ce2ee2d65d6a1f887a13ce", "type": "github" }, "original": { @@ -22,16 +22,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1730963269, - "narHash": "sha256-rz30HrFYCHiWEBCKHMffHbMdWJ35hEkcRVU0h7ms3x0=", + "lastModified": 1736549401, + "narHash": "sha256-ibkQrMHxF/7TqAYcQE+tOnIsSEzXmMegzyBWza6uHKM=", "owner": "nixos", "repo": "nixpkgs", - "rev": "83fb6c028368e465cd19bb127b86f971a5e41ebc", + "rev": "1dab772dd4a68a7bba5d9460685547ff8e17d899", "type": "github" }, "original": { "owner": "nixos", - "ref": "nixos-24.05", + "ref": "nixos-24.11", "repo": "nixpkgs", "type": "github" } diff --git a/flake.nix b/flake.nix index e373fd2..d3303e4 100644 --- a/flake.nix +++ b/flake.nix @@ -2,7 +2,7 @@ description = "Flake for servicepoint-simulator"; inputs = { - nixpkgs.url = "github:nixos/nixpkgs/nixos-24.05"; + nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11"; naersk = { url = "github:nix-community/naersk"; inputs.nixpkgs.follows = "nixpkgs"; @@ -51,7 +51,7 @@ ]; buildInputs = with pkgs; [ xe - lzma + xz ]; makeExample = { From efaa52faa1731da47c9ad6f9ad83fa70f7ccbfa6 Mon Sep 17 00:00:00 2001 From: Vinzenz Schroeter Date: Thu, 9 Jan 2025 23:02:41 +0100 Subject: [PATCH 03/10] first CMD_UTF8_DATA implementation UTF8 now works --- .gitignore | 1 + crates/servicepoint/examples/announce.rs | 20 +++- .../examples/random_brightness.rs | 7 +- crates/servicepoint/examples/wiping_clear.rs | 6 +- crates/servicepoint/src/bitmap.rs | 10 +- crates/servicepoint/src/brightness.rs | 28 ++++-- crates/servicepoint/src/char_grid.rs | 93 +++++++++++++++---- crates/servicepoint/src/command.rs | 63 +++++++++++-- crates/servicepoint/src/command_code.rs | 4 + crates/servicepoint/src/connection.rs | 8 +- crates/servicepoint/src/cp437.rs | 6 +- crates/servicepoint/src/lib.rs | 6 +- crates/servicepoint/src/packet.rs | 3 + crates/servicepoint/src/primitive_grid.rs | 53 +++++++++-- .../src/brightness_grid.rs | 4 +- .../src/char_grid.rs | 4 +- 16 files changed, 245 insertions(+), 71 deletions(-) diff --git a/.gitignore b/.gitignore index 8792323..f48287d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ out .direnv .envrc result +mutants.* \ No newline at end of file diff --git a/crates/servicepoint/examples/announce.rs b/crates/servicepoint/examples/announce.rs index 66227a4..ab2657a 100644 --- a/crates/servicepoint/examples/announce.rs +++ b/crates/servicepoint/examples/announce.rs @@ -2,7 +2,7 @@ use clap::Parser; -use servicepoint::{CharGrid, Command, Connection, Cp437Grid, Origin}; +use servicepoint::{CharGrid, Command, Connection, Origin, TILE_WIDTH}; #[derive(Parser, Debug)] struct Cli { @@ -41,11 +41,21 @@ fn main() { .expect("sending clear failed"); } - let text = cli.text.join("\n"); - let grid = CharGrid::from(text); - let grid = Cp437Grid::from(grid); + let text = cli + .text + .iter() + .flat_map(move |x| { + x.chars() + .collect::>() + .chunks(TILE_WIDTH) + .map(|c| String::from_iter(c)) + .collect::>() + }) + .collect::>() + .join("\n"); + let grid = CharGrid::from(text); connection - .send(Command::Cp437Data(Origin::ZERO, grid)) + .send(Command::Utf8Data(Origin::ZERO, grid)) .expect("sending text failed"); } diff --git a/crates/servicepoint/examples/random_brightness.rs b/crates/servicepoint/examples/random_brightness.rs index f47cf72..00f4d56 100644 --- a/crates/servicepoint/examples/random_brightness.rs +++ b/crates/servicepoint/examples/random_brightness.rs @@ -31,11 +31,8 @@ fn main() { let mut filled_grid = Bitmap::max_sized(); filled_grid.fill(true); - let command = BitmapLinearWin( - Origin::ZERO, - filled_grid, - CompressionCode::Lzma, - ); + let command = + BitmapLinearWin(Origin::ZERO, filled_grid, CompressionCode::Lzma); connection.send(command).expect("send failed"); } diff --git a/crates/servicepoint/examples/wiping_clear.rs b/crates/servicepoint/examples/wiping_clear.rs index 6d85724..21733bf 100644 --- a/crates/servicepoint/examples/wiping_clear.rs +++ b/crates/servicepoint/examples/wiping_clear.rs @@ -34,7 +34,11 @@ fn main() { } connection - .send(Command::BitmapLinearWin(Origin::ZERO, enabled_pixels.clone(), CompressionCode::Lzma)) + .send(Command::BitmapLinearWin( + Origin::ZERO, + enabled_pixels.clone(), + CompressionCode::Lzma, + )) .expect("could not send command to display"); thread::sleep(sleep_duration); } diff --git a/crates/servicepoint/src/bitmap.rs b/crates/servicepoint/src/bitmap.rs index 3d23706..a0c03b4 100644 --- a/crates/servicepoint/src/bitmap.rs +++ b/crates/servicepoint/src/bitmap.rs @@ -203,7 +203,7 @@ impl<'t> Iterator for IterRows<'t> { #[cfg(test)] mod tests { - use crate::{Bitmap, DataRef, Grid}; + use crate::{BitVec, Bitmap, DataRef, Grid}; #[test] fn fill() { @@ -295,4 +295,12 @@ mod tests { data[1] = 0x0F; assert!(grid.get(7, 1)); } + + #[test] + fn to_bitvec() { + let mut grid = Bitmap::new(8, 2); + grid.set(0, 0, true); + let bitvec: BitVec = grid.into(); + assert_eq!(bitvec.as_raw_slice(), [0x80, 0x00]); + } } diff --git a/crates/servicepoint/src/brightness.rs b/crates/servicepoint/src/brightness.rs index 7787cc2..73cbaf6 100644 --- a/crates/servicepoint/src/brightness.rs +++ b/crates/servicepoint/src/brightness.rs @@ -1,5 +1,5 @@ -use crate::{Grid, PrimitiveGrid}; - +use crate::primitive_grid::PrimitiveGrid; +use crate::{ByteGrid, Grid}; #[cfg(feature = "rand")] use rand::{ distributions::{Distribution, Standard}, @@ -40,7 +40,8 @@ pub type BrightnessGrid = PrimitiveGrid; impl BrightnessGrid { /// Like [Self::load], but ignoring any out-of-range brightness values pub fn saturating_load(width: usize, height: usize, data: &[u8]) -> Self { - PrimitiveGrid::load(width, height, data).map(Brightness::saturating_from) + PrimitiveGrid::load(width, height, data) + .map(Brightness::saturating_from) } } @@ -101,7 +102,7 @@ impl From for Vec { } } -impl From<&BrightnessGrid> for PrimitiveGrid { +impl From<&BrightnessGrid> for ByteGrid { fn from(value: &PrimitiveGrid) -> Self { let u8s = value .iter() @@ -111,10 +112,10 @@ impl From<&BrightnessGrid> for PrimitiveGrid { } } -impl TryFrom> for BrightnessGrid { +impl TryFrom for BrightnessGrid { type Error = u8; - fn try_from(value: PrimitiveGrid) -> Result { + fn try_from(value: ByteGrid) -> Result { let brightnesses = value .iter() .map(|b| Brightness::try_from(*b)) @@ -171,7 +172,18 @@ mod tests { #[test] fn saturating_load() { - assert_eq!(BrightnessGrid::load(2,2, &[Brightness::MAX, Brightness::MAX, Brightness::MIN, Brightness::MAX]), - BrightnessGrid::saturating_load(2,2, &[255u8, 23, 0, 42])); + assert_eq!( + BrightnessGrid::load( + 2, + 2, + &[ + Brightness::MAX, + Brightness::MAX, + Brightness::MIN, + Brightness::MAX + ] + ), + BrightnessGrid::saturating_load(2, 2, &[255u8, 23, 0, 42]) + ); } } diff --git a/crates/servicepoint/src/char_grid.rs b/crates/servicepoint/src/char_grid.rs index 1a9045c..97eb170 100644 --- a/crates/servicepoint/src/char_grid.rs +++ b/crates/servicepoint/src/char_grid.rs @@ -1,5 +1,8 @@ -use crate::primitive_grid::SeriesError; -use crate::{Grid, PrimitiveGrid}; +use crate::primitive_grid::{ + PrimitiveGrid, SeriesError, TryLoadPrimitiveGridError, +}; +use crate::Grid; +use std::string::FromUtf8Error; /// A grid containing UTF-8 characters. pub type CharGrid = PrimitiveGrid; @@ -40,17 +43,39 @@ impl CharGrid { ) -> Result<(), SeriesError> { self.set_col(x, value.chars().collect::>().as_ref()) } + + /// Loads a [CharGrid] with the specified dimensions from the provided UTF-8 bytes. + /// + /// returns: [CharGrid] that contains the provided data, or [FromUtf8Error] if the data is invalid. + /// + /// # Panics + /// + /// - when the dimensions and data size do not match exactly. + pub fn load_utf8( + width: usize, + height: usize, + bytes: Vec, + ) -> Result { + let s: Vec = String::from_utf8(bytes)?.chars().collect(); + Ok(CharGrid::try_load(width, height, s)?) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum LoadUtf8Error { + #[error(transparent)] + FromUtf8Error(#[from] FromUtf8Error), + #[error(transparent)] + TryLoadError(#[from] TryLoadPrimitiveGridError), } impl From<&str> for CharGrid { fn from(value: &str) -> Self { let value = value.replace("\r\n", "\n"); - let mut lines = value - .split('\n') - .map(move |line| line.trim_end()) - .collect::>(); - let width = - lines.iter().fold(0, move |a, x| std::cmp::max(a, x.len())); + let mut lines = value.split('\n').collect::>(); + let width = lines + .iter() + .fold(0, move |a, x| std::cmp::max(a, x.chars().count())); while lines.last().is_some_and(move |line| line.is_empty()) { _ = lines.pop(); @@ -73,22 +98,34 @@ impl From for CharGrid { } } +impl From for String { + fn from(grid: CharGrid) -> Self { + String::from(&grid) + } +} + impl From<&CharGrid> for String { fn from(value: &CharGrid) -> Self { value .iter_rows() - .map(move |chars| { - chars - .collect::() - .replace('\0', " ") - .trim_end() - .to_string() - }) - .collect::>() + .map(String::from_iter) + .collect::>() .join("\n") } } +impl From<&CharGrid> for Vec { + fn from(value: &CharGrid) -> Self { + String::from_iter(value.iter()).into_bytes() + } +} + +impl From for Vec { + fn from(value: CharGrid) -> Self { + Self::from(&value) + } +} + #[cfg(test)] mod test { use super::*; @@ -120,10 +157,28 @@ mod test { #[test] fn str_to_char_grid() { - let original = "Hello\r\nWorld!\n...\n"; + // conversion with .to_string() covers one more line + let original = "Hello\r\nWorld!\n...\n".to_string(); + let grid = CharGrid::from(original); assert_eq!(3, grid.height()); - let actual = String::from(&grid); - assert_eq!("Hello\nWorld!\n...", actual); + assert_eq!("Hello\0\nWorld!\n...\0\0\0", String::from(grid)); + } + + #[test] + fn round_trip_bytes() { + let grid = CharGrid::from("Hello\0\nWorld!\n...\0\0\0"); + let bytes: Vec = grid.clone().into(); + let copy = + CharGrid::load_utf8(grid.width(), grid.height(), bytes).unwrap(); + assert_eq!(grid, copy); + } + + #[test] + fn round_trip_string() { + let grid = CharGrid::from("Hello\0\nWorld!\n...\0\0\0"); + let str: String = grid.clone().into(); + let copy = CharGrid::from(str); + assert_eq!(grid, copy); } } diff --git a/crates/servicepoint/src/command.rs b/crates/servicepoint/src/command.rs index 462e448..25603ff 100644 --- a/crates/servicepoint/src/command.rs +++ b/crates/servicepoint/src/command.rs @@ -1,9 +1,10 @@ +use crate::primitive_grid::PrimitiveGrid; use crate::{ command_code::CommandCode, compression::into_decompressed, packet::{Header, Packet}, - Bitmap, Brightness, BrightnessGrid, CompressionCode, Cp437Grid, Origin, - Pixels, PrimitiveGrid, BitVec, Tiles, TILE_SIZE, + BitVec, Bitmap, Brightness, BrightnessGrid, CharGrid, CompressionCode, + Cp437Grid, Origin, Pixels, Tiles, TILE_SIZE, }; /// Type alias for documenting the meaning of the u16 in enum values @@ -72,10 +73,27 @@ pub enum Command { /// ``` Clear, + /// Show text on the screen. + /// + /// The text is sent in the form of a 2D grid of UTF-8 encoded characters (the default encoding in rust). + /// + /// # Examples + /// + /// ```rust + /// # use servicepoint::{Command, Connection, Origin}; + /// # let connection = Connection::Fake; + /// use servicepoint::{CharGrid}; + /// let grid = CharGrid::from("Hello,\nWorld!"); + /// connection.send(Command::Utf8Data(Origin::ZERO, grid)).expect("send failed"); + /// ``` + Utf8Data(Origin, CharGrid), + /// Show text on the screen. /// /// The text is sent in the form of a 2D grid of [CP-437] encoded characters. /// + ///
You probably want to use [Command::Utf8Data] instead
+ /// /// # Examples /// /// ```rust @@ -234,6 +252,8 @@ pub enum TryFromPacketError { /// The given brightness value is out of bounds #[error("The given brightness value {0} is out of bounds.")] InvalidBrightness(u8), + #[error(transparent)] + InvalidUtf8(#[from] std::string::FromUtf8Error), } impl TryFrom for Command { @@ -269,6 +289,7 @@ impl TryFrom for Command { CommandCode::CharBrightness => { Self::packet_into_char_brightness(&packet) } + CommandCode::Utf8Data => Self::packet_into_utf8(&packet), #[allow(deprecated)] CommandCode::BitmapLegacy => Ok(Command::BitmapLegacy), CommandCode::BitmapLinear => { @@ -489,6 +510,28 @@ impl Command { Cp437Grid::load(*c as usize, *d as usize, payload), )) } + + fn packet_into_utf8( + packet: &Packet, + ) -> Result { + let Packet { + header: + Header { + command_code: _, + a, + b, + c, + d, + }, + payload, + } = packet; + let payload: Vec<_> = + String::from_utf8(payload.clone())?.chars().collect(); + Ok(Command::Utf8Data( + Origin::new(*a as usize, *b as usize), + CharGrid::load(*c as usize, *d as usize, &*payload), + )) + } } #[cfg(test)] @@ -499,8 +542,8 @@ mod tests { command_code::CommandCode, origin::Pixels, packet::{Header, Packet}, - Bitmap, Brightness, BrightnessGrid, Command, CompressionCode, Origin, - PrimitiveGrid, + Bitmap, Brightness, BrightnessGrid, CharGrid, Command, CompressionCode, + Cp437Grid, Origin, }; fn round_trip(original: Command) { @@ -556,16 +599,18 @@ mod tests { fn round_trip_char_brightness() { round_trip(Command::CharBrightness( Origin::new(5, 2), - PrimitiveGrid::new(7, 5), + BrightnessGrid::new(7, 5), )); } #[test] fn round_trip_cp437_data() { - round_trip(Command::Cp437Data( - Origin::new(5, 2), - PrimitiveGrid::new(7, 5), - )); + round_trip(Command::Cp437Data(Origin::new(5, 2), Cp437Grid::new(7, 5))); + } + + #[test] + fn round_trip_utf8_data() { + round_trip(Command::Utf8Data(Origin::new(5, 2), CharGrid::new(7, 5))); } #[test] diff --git a/crates/servicepoint/src/command_code.rs b/crates/servicepoint/src/command_code.rs index 4735e44..25ddfeb 100644 --- a/crates/servicepoint/src/command_code.rs +++ b/crates/servicepoint/src/command_code.rs @@ -21,6 +21,7 @@ pub(crate) enum CommandCode { BitmapLinearWinBzip2 = 0x0018, #[cfg(feature = "compression_lzma")] BitmapLinearWinLzma = 0x0019, + Utf8Data = 0x0020, #[cfg(feature = "compression_zstd")] BitmapLinearWinZstd = 0x001A, } @@ -93,6 +94,9 @@ impl TryFrom for CommandCode { value if value == CommandCode::BitmapLinearWinBzip2 as u16 => { Ok(CommandCode::BitmapLinearWinBzip2) } + value if value == CommandCode::Utf8Data as u16 => { + Ok(CommandCode::Utf8Data) + } _ => Err(()), } } diff --git a/crates/servicepoint/src/connection.rs b/crates/servicepoint/src/connection.rs index 046257f..a0a6f11 100644 --- a/crates/servicepoint/src/connection.rs +++ b/crates/servicepoint/src/connection.rs @@ -107,9 +107,7 @@ impl Connection { let request = ClientRequestBuilder::new(uri).into_client_request()?; let (sock, _) = connect(request)?; - Ok(Self::WebSocket(std::sync::Mutex::new( - sock, - ))) + Ok(Self::WebSocket(std::sync::Mutex::new(sock))) } /// Send something packet-like to the display. Usually this is in the form of a Command. @@ -159,9 +157,7 @@ impl Drop for Connection { fn drop(&mut self) { #[cfg(feature = "protocol_websocket")] if let Connection::WebSocket(sock) = self { - _ = sock - .try_lock() - .map(move |mut sock| sock.close(None)); + _ = sock.try_lock().map(move |mut sock| sock.close(None)); } } } diff --git a/crates/servicepoint/src/cp437.rs b/crates/servicepoint/src/cp437.rs index bfd8b5a..17b5d45 100644 --- a/crates/servicepoint/src/cp437.rs +++ b/crates/servicepoint/src/cp437.rs @@ -2,7 +2,7 @@ //! //! Most of the functionality is only available with feature "cp437" enabled. -use crate::{Grid, PrimitiveGrid}; +use crate::{Grid, primitive_grid::PrimitiveGrid}; use std::collections::HashMap; /// A grid containing codepage 437 characters. @@ -12,7 +12,9 @@ pub type Cp437Grid = PrimitiveGrid; /// The error occurring when loading an invalid character #[derive(Debug, PartialEq, thiserror::Error)] -#[error("The character {char:?} at position {index} is not a valid CP437 character")] +#[error( + "The character {char:?} at position {index} is not a valid CP437 character" +)] pub struct InvalidCharError { /// invalid character is at this position in input index: usize, diff --git a/crates/servicepoint/src/lib.rs b/crates/servicepoint/src/lib.rs index 46478e1..d89e0d7 100644 --- a/crates/servicepoint/src/lib.rs +++ b/crates/servicepoint/src/lib.rs @@ -49,11 +49,13 @@ pub use crate::cp437::Cp437Grid; pub use crate::data_ref::DataRef; pub use crate::grid::Grid; pub use crate::origin::{Origin, Pixels, Tiles}; -pub use crate::primitive_grid::{PrimitiveGrid, SeriesError}; /// An alias for the specific type of [bitvec::prelude::BitVec] used. pub type BitVec = bitvec::prelude::BitVec; +/// A simple grid of bytes - see [primitive_grid::PrimitiveGrid]. +pub type ByteGrid = primitive_grid::PrimitiveGrid; + mod bitmap; mod brightness; mod char_grid; @@ -67,7 +69,7 @@ mod data_ref; mod grid; mod origin; pub mod packet; -mod primitive_grid; +pub mod primitive_grid; /// size of a single tile in one dimension pub const TILE_SIZE: usize = 8; diff --git a/crates/servicepoint/src/packet.rs b/crates/servicepoint/src/packet.rs index 2b8688d..ee66426 100644 --- a/crates/servicepoint/src/packet.rs +++ b/crates/servicepoint/src/packet.rs @@ -209,6 +209,9 @@ impl From for Packet { grid, CommandCode::Cp437Data, ), + Command::Utf8Data(origin, grid) => { + Self::origin_grid_to_packet(origin, grid, CommandCode::Utf8Data) + } } } } diff --git a/crates/servicepoint/src/primitive_grid.rs b/crates/servicepoint/src/primitive_grid.rs index 366f0ca..a6eaf5c 100644 --- a/crates/servicepoint/src/primitive_grid.rs +++ b/crates/servicepoint/src/primitive_grid.rs @@ -1,9 +1,13 @@ +//! This module contains the implementation of the [PrimitiveGrid]. + +use std::fmt::Debug; use std::slice::{Iter, IterMut}; use crate::{DataRef, Grid}; -pub trait PrimitiveGridType: Sized + Default + Copy + Clone {} -impl PrimitiveGridType for T {} +/// A type that can be stored in a [PrimitiveGrid], e.g. [char], [u8]. +pub trait PrimitiveGridType: Sized + Default + Copy + Clone + Debug {} +impl PrimitiveGridType for T {} /// A 2D grid of bytes #[derive(Debug, Clone, PartialEq)] @@ -60,7 +64,11 @@ impl PrimitiveGrid { /// - when the dimensions and data size do not match exactly. #[must_use] pub fn load(width: usize, height: usize, data: &[T]) -> Self { - assert_eq!(width * height, data.len()); + assert_eq!( + width * height, + data.len(), + "dimension mismatch for data {data:?}" + ); Self { data: Vec::from(data), width, @@ -68,12 +76,31 @@ impl PrimitiveGrid { } } + /// Loads a [PrimitiveGrid] with the specified dimensions from the provided data. + /// + /// returns: [PrimitiveGrid] that contains a copy of the provided data or [TryLoadPrimitiveGridError]. + pub fn try_load( + width: usize, + height: usize, + data: Vec, + ) -> Result { + if width * height != data.len() { + return Err(TryLoadPrimitiveGridError::InvalidDimensions); + } + + Ok(Self { + data, + width, + height, + }) + } + /// Iterate over all cells in [PrimitiveGrid]. /// /// Order is equivalent to the following loop: /// ``` - /// # use servicepoint::{PrimitiveGrid, Grid}; - /// # let grid = PrimitiveGrid::::new(2,2); + /// # use servicepoint::{ByteGrid, Grid}; + /// # let grid = ByteGrid::new(2,2); /// for y in 0..grid.height() { /// for x in 0..grid.width() { /// grid.get(x, y); @@ -140,9 +167,9 @@ impl PrimitiveGrid { /// /// Use logic written for u8s and then convert to [Brightness] values for sending in a [Command]. /// ``` - /// # fn foo(grid: &mut PrimitiveGrid) {} - /// # use servicepoint::{Brightness, BrightnessGrid, Command, Origin, PrimitiveGrid, TILE_HEIGHT, TILE_WIDTH}; - /// let mut grid: PrimitiveGrid = PrimitiveGrid::new(TILE_WIDTH, TILE_HEIGHT); + /// # fn foo(grid: &mut ByteGrid) {} + /// # use servicepoint::{Brightness, BrightnessGrid, ByteGrid, Command, Origin, TILE_HEIGHT, TILE_WIDTH}; + /// let mut grid: ByteGrid = ByteGrid::new(TILE_WIDTH, TILE_HEIGHT); /// foo(&mut grid); /// let grid: BrightnessGrid = grid.map(Brightness::saturating_from); /// let command = Command::CharBrightness(Origin::ZERO, grid); @@ -238,6 +265,12 @@ impl PrimitiveGrid { } } +#[derive(Debug, thiserror::Error)] +pub enum TryLoadPrimitiveGridError { + #[error("The provided dimensions do not match with the data size")] + InvalidDimensions, +} + impl Grid for PrimitiveGrid { /// Sets the value of the cell at the specified position in the `PrimitiveGrid. /// @@ -300,6 +333,7 @@ impl From> for Vec { } } +/// An iterator iver the rows in a [PrimitiveGrid] pub struct IterRows<'t, T: PrimitiveGridType> { byte_grid: &'t PrimitiveGrid, row: usize, @@ -323,7 +357,8 @@ impl<'t, T: PrimitiveGridType> Iterator for IterRows<'t, T> { #[cfg(test)] mod tests { - use crate::{DataRef, Grid, PrimitiveGrid, SeriesError}; + use crate::primitive_grid::{PrimitiveGrid, SeriesError}; + use crate::{DataRef, Grid}; #[test] fn fill() { diff --git a/crates/servicepoint_binding_c/src/brightness_grid.rs b/crates/servicepoint_binding_c/src/brightness_grid.rs index 04187d7..d8c4310 100644 --- a/crates/servicepoint_binding_c/src/brightness_grid.rs +++ b/crates/servicepoint_binding_c/src/brightness_grid.rs @@ -3,7 +3,7 @@ //! prefix `sp_brightness_grid_` use crate::SPByteSlice; -use servicepoint::{Brightness, DataRef, Grid, PrimitiveGrid}; +use servicepoint::{Brightness, ByteGrid, DataRef, Grid}; use std::convert::Into; use std::intrinsics::transmute; use std::ptr::NonNull; @@ -80,7 +80,7 @@ pub unsafe extern "C" fn sp_brightness_grid_load( ) -> NonNull { assert!(!data.is_null()); let data = std::slice::from_raw_parts(data, data_length); - let grid = PrimitiveGrid::load(width, height, data); + let grid = ByteGrid::load(width, height, data); let grid = servicepoint::BrightnessGrid::try_from(grid) .expect("invalid brightness value"); let result = Box::new(SPBrightnessGrid(grid)); diff --git a/crates/servicepoint_binding_uniffi/src/char_grid.rs b/crates/servicepoint_binding_uniffi/src/char_grid.rs index bd9e1b8..ad686b4 100644 --- a/crates/servicepoint_binding_uniffi/src/char_grid.rs +++ b/crates/servicepoint_binding_uniffi/src/char_grid.rs @@ -1,7 +1,7 @@ -use servicepoint::{Grid, SeriesError}; +use crate::cp437_grid::Cp437Grid; +use servicepoint::{Grid, primitive_grid::SeriesError}; use std::convert::Into; use std::sync::{Arc, RwLock}; -use crate::cp437_grid::Cp437Grid; #[derive(uniffi::Object)] pub struct CharGrid { From 8320ee2d80b4ebb609c62323d03e8b06e7a5a5a0 Mon Sep 17 00:00:00 2001 From: Vinzenz Schroeter Date: Sun, 12 Jan 2025 02:06:02 +0100 Subject: [PATCH 04/10] restructure api --- crates/servicepoint/examples/announce.rs | 3 +- .../examples/brightness_tester.rs | 1 - crates/servicepoint/examples/game_of_life.rs | 4 +- crates/servicepoint/examples/moving_line.rs | 4 +- .../examples/random_brightness.rs | 18 +- crates/servicepoint/examples/wiping_clear.rs | 6 +- crates/servicepoint/src/bitmap.rs | 11 +- crates/servicepoint/src/bitvec.rs | 3 + crates/servicepoint/src/brightness.rs | 87 ---------- crates/servicepoint/src/brightness_grid.rs | 93 ++++++++++ crates/servicepoint/src/byte_grid.rs | 2 + crates/servicepoint/src/char_grid.rs | 17 +- crates/servicepoint/src/command.rs | 34 ++-- crates/servicepoint/src/compression.rs | 2 +- crates/servicepoint/src/connection.rs | 1 - crates/servicepoint/src/constants.rs | 75 ++++++++ crates/servicepoint/src/cp437.rs | 145 ---------------- crates/servicepoint/src/cp437_grid.rs | 160 ++++++++++++++++++ crates/servicepoint/src/lib.rs | 105 ++---------- crates/servicepoint/src/packet.rs | 12 +- .../src/{primitive_grid.rs => value_grid.rs} | 120 +++++++------ crates/servicepoint_binding_c/src/bitmap.rs | 11 +- crates/servicepoint_binding_c/src/bitvec.rs | 15 +- .../src/brightness_grid.rs | 20 +-- crates/servicepoint_binding_c/src/command.rs | 89 +++++----- .../servicepoint_binding_c/src/cp437_grid.rs | 13 +- crates/servicepoint_binding_c/src/lib.rs | 4 +- crates/servicepoint_binding_c/src/packet.rs | 8 +- .../src/char_grid.rs | 28 +-- .../src/constants.rs | 6 +- .../src/cp437_grid.rs | 6 +- crates/servicepoint_binding_uniffi/src/lib.rs | 2 +- 32 files changed, 561 insertions(+), 544 deletions(-) create mode 100644 crates/servicepoint/src/bitvec.rs create mode 100644 crates/servicepoint/src/brightness_grid.rs create mode 100644 crates/servicepoint/src/byte_grid.rs create mode 100644 crates/servicepoint/src/constants.rs create mode 100644 crates/servicepoint/src/cp437_grid.rs rename crates/servicepoint/src/{primitive_grid.rs => value_grid.rs} (78%) diff --git a/crates/servicepoint/examples/announce.rs b/crates/servicepoint/examples/announce.rs index ab2657a..4159384 100644 --- a/crates/servicepoint/examples/announce.rs +++ b/crates/servicepoint/examples/announce.rs @@ -1,8 +1,7 @@ //! An example for how to send text to the display. use clap::Parser; - -use servicepoint::{CharGrid, Command, Connection, Origin, TILE_WIDTH}; +use servicepoint::*; #[derive(Parser, Debug)] struct Cli { diff --git a/crates/servicepoint/examples/brightness_tester.rs b/crates/servicepoint/examples/brightness_tester.rs index 8a31ee8..ec78415 100644 --- a/crates/servicepoint/examples/brightness_tester.rs +++ b/crates/servicepoint/examples/brightness_tester.rs @@ -1,7 +1,6 @@ //! Show a brightness level test pattern on screen use clap::Parser; - use servicepoint::*; #[derive(Parser, Debug)] diff --git a/crates/servicepoint/examples/game_of_life.rs b/crates/servicepoint/examples/game_of_life.rs index 76e8c70..ab4f63b 100644 --- a/crates/servicepoint/examples/game_of_life.rs +++ b/crates/servicepoint/examples/game_of_life.rs @@ -1,11 +1,9 @@ //! A simple game of life implementation to show how to render graphics to the display. -use std::thread; - use clap::Parser; use rand::{distributions, Rng}; - use servicepoint::*; +use std::thread; #[derive(Parser, Debug)] struct Cli { diff --git a/crates/servicepoint/examples/moving_line.rs b/crates/servicepoint/examples/moving_line.rs index 50cfcca..3ebd6b0 100644 --- a/crates/servicepoint/examples/moving_line.rs +++ b/crates/servicepoint/examples/moving_line.rs @@ -1,10 +1,8 @@ //! A simple example for how to send pixel data to the display. -use std::thread; - use clap::Parser; - use servicepoint::*; +use std::thread; #[derive(Parser, Debug)] struct Cli { diff --git a/crates/servicepoint/examples/random_brightness.rs b/crates/servicepoint/examples/random_brightness.rs index 00f4d56..0f976c4 100644 --- a/crates/servicepoint/examples/random_brightness.rs +++ b/crates/servicepoint/examples/random_brightness.rs @@ -1,13 +1,10 @@ //! A simple example for how to set brightnesses for tiles on the screen. //! Continuously changes the tiles in a random window to random brightnesses. -use std::time::Duration; - use clap::Parser; use rand::Rng; - -use servicepoint::Command::{BitmapLinearWin, Brightness, CharBrightness}; use servicepoint::*; +use std::time::Duration; #[derive(Parser, Debug)] struct Cli { @@ -31,14 +28,17 @@ fn main() { let mut filled_grid = Bitmap::max_sized(); filled_grid.fill(true); - let command = - BitmapLinearWin(Origin::ZERO, filled_grid, CompressionCode::Lzma); + let command = Command::BitmapLinearWin( + Origin::ZERO, + filled_grid, + CompressionCode::Lzma, + ); connection.send(command).expect("send failed"); } // set all pixels to the same random brightness let mut rng = rand::thread_rng(); - connection.send(Brightness(rng.gen())).unwrap(); + connection.send(Command::Brightness(rng.gen())).unwrap(); // continuously update random windows to new random brightness loop { @@ -58,7 +58,9 @@ fn main() { } } - connection.send(CharBrightness(origin, luma)).unwrap(); + connection + .send(Command::CharBrightness(origin, luma)) + .unwrap(); std::thread::sleep(wait_duration); } } diff --git a/crates/servicepoint/examples/wiping_clear.rs b/crates/servicepoint/examples/wiping_clear.rs index 21733bf..101a9ae 100644 --- a/crates/servicepoint/examples/wiping_clear.rs +++ b/crates/servicepoint/examples/wiping_clear.rs @@ -1,11 +1,9 @@ //! An example on how to modify the image on screen without knowing the current content. +use clap::Parser; +use servicepoint::*; use std::thread; use std::time::Duration; -use clap::Parser; - -use servicepoint::*; - #[derive(Parser, Debug)] struct Cli { #[arg(short, long, default_value = "localhost:2342")] diff --git a/crates/servicepoint/src/bitmap.rs b/crates/servicepoint/src/bitmap.rs index a0c03b4..76170b3 100644 --- a/crates/servicepoint/src/bitmap.rs +++ b/crates/servicepoint/src/bitmap.rs @@ -1,8 +1,11 @@ -use bitvec::order::Msb0; -use bitvec::prelude::BitSlice; -use bitvec::slice::IterMut; +//! Implementation of [Bitmap] -use crate::{BitVec, DataRef, Grid, PIXEL_HEIGHT, PIXEL_WIDTH}; +use crate::data_ref::DataRef; +use crate::BitVec; +use crate::*; +use ::bitvec::order::Msb0; +use ::bitvec::prelude::BitSlice; +use ::bitvec::slice::IterMut; /// A grid of pixels stored in packed bytes. #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/crates/servicepoint/src/bitvec.rs b/crates/servicepoint/src/bitvec.rs new file mode 100644 index 0000000..7006105 --- /dev/null +++ b/crates/servicepoint/src/bitvec.rs @@ -0,0 +1,3 @@ +pub use bitvec::prelude::*; +/// An alias for the specific type of [bitvec::prelude::BitVec] used. +pub type BitVec = bitvec::prelude::BitVec; diff --git a/crates/servicepoint/src/brightness.rs b/crates/servicepoint/src/brightness.rs index 73cbaf6..4e12326 100644 --- a/crates/servicepoint/src/brightness.rs +++ b/crates/servicepoint/src/brightness.rs @@ -1,5 +1,3 @@ -use crate::primitive_grid::PrimitiveGrid; -use crate::{ByteGrid, Grid}; #[cfg(feature = "rand")] use rand::{ distributions::{Distribution, Standard}, @@ -22,29 +20,6 @@ use rand::{ #[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)] pub struct Brightness(u8); -/// A grid containing brightness values. -/// -/// # Examples -/// -/// ```rust -/// # use servicepoint::{Brightness, BrightnessGrid, Command, Connection, Grid, Origin}; -/// let mut grid = BrightnessGrid::new(2,2); -/// grid.set(0, 0, Brightness::MIN); -/// grid.set(1, 1, Brightness::MIN); -/// -/// # let connection = Connection::open("127.0.0.1:2342").unwrap(); -/// connection.send(Command::CharBrightness(Origin::new(3, 7), grid)).unwrap() -/// ``` -pub type BrightnessGrid = PrimitiveGrid; - -impl BrightnessGrid { - /// Like [Self::load], but ignoring any out-of-range brightness values - pub fn saturating_load(width: usize, height: usize, data: &[u8]) -> Self { - PrimitiveGrid::load(width, height, data) - .map(Brightness::saturating_from) - } -} - impl From for u8 { fn from(brightness: Brightness) -> Self { Self::from(&brightness) @@ -93,41 +68,6 @@ impl Default for Brightness { } } -impl From for Vec { - fn from(value: PrimitiveGrid) -> Self { - value - .iter() - .map(|brightness| (*brightness).into()) - .collect() - } -} - -impl From<&BrightnessGrid> for ByteGrid { - fn from(value: &PrimitiveGrid) -> Self { - let u8s = value - .iter() - .map(|brightness| (*brightness).into()) - .collect::>(); - PrimitiveGrid::load(value.width(), value.height(), &u8s) - } -} - -impl TryFrom for BrightnessGrid { - type Error = u8; - - fn try_from(value: ByteGrid) -> Result { - let brightnesses = value - .iter() - .map(|b| Brightness::try_from(*b)) - .collect::, _>>()?; - Ok(BrightnessGrid::load( - value.width(), - value.height(), - &brightnesses, - )) - } -} - #[cfg(feature = "rand")] impl Distribution for Standard { fn sample(&self, rng: &mut R) -> Brightness { @@ -138,7 +78,6 @@ impl Distribution for Standard { #[cfg(test)] mod tests { use super::*; - use crate::DataRef; #[test] fn brightness_from_u8() { @@ -155,35 +94,9 @@ mod tests { } } - #[test] - fn to_u8_grid() { - let mut grid = BrightnessGrid::new(2, 2); - grid.set(1, 0, Brightness::MIN); - grid.set(0, 1, Brightness::MAX); - let actual = PrimitiveGrid::from(&grid); - assert_eq!(actual.data_ref(), &[11, 0, 11, 11]); - } - #[test] fn saturating_convert() { assert_eq!(Brightness::MAX, Brightness::saturating_from(100)); assert_eq!(Brightness(5), Brightness::saturating_from(5)); } - - #[test] - fn saturating_load() { - assert_eq!( - BrightnessGrid::load( - 2, - 2, - &[ - Brightness::MAX, - Brightness::MAX, - Brightness::MIN, - Brightness::MAX - ] - ), - BrightnessGrid::saturating_load(2, 2, &[255u8, 23, 0, 42]) - ); - } } diff --git a/crates/servicepoint/src/brightness_grid.rs b/crates/servicepoint/src/brightness_grid.rs new file mode 100644 index 0000000..122df35 --- /dev/null +++ b/crates/servicepoint/src/brightness_grid.rs @@ -0,0 +1,93 @@ +use crate::brightness::Brightness; +use crate::grid::Grid; +use crate::value_grid::ValueGrid; +use crate::ByteGrid; + +/// A grid containing brightness values. +/// +/// # Examples +/// +/// ```rust +/// # use servicepoint::{Brightness, BrightnessGrid, Command, Connection, Grid, Origin}; +/// let mut grid = BrightnessGrid::new(2,2); +/// grid.set(0, 0, Brightness::MIN); +/// grid.set(1, 1, Brightness::MIN); +/// +/// # let connection = Connection::open("127.0.0.1:2342").unwrap(); +/// connection.send(Command::CharBrightness(Origin::new(3, 7), grid)).unwrap() +/// ``` +pub type BrightnessGrid = ValueGrid; + +impl BrightnessGrid { + /// Like [Self::load], but ignoring any out-of-range brightness values + pub fn saturating_load(width: usize, height: usize, data: &[u8]) -> Self { + ValueGrid::load(width, height, data).map(Brightness::saturating_from) + } +} + +impl From for Vec { + fn from(value: ValueGrid) -> Self { + value + .iter() + .map(|brightness| (*brightness).into()) + .collect() + } +} + +impl From<&BrightnessGrid> for ByteGrid { + fn from(value: &ValueGrid) -> Self { + let u8s = value + .iter() + .map(|brightness| (*brightness).into()) + .collect::>(); + ValueGrid::load(value.width(), value.height(), &u8s) + } +} + +impl TryFrom for BrightnessGrid { + type Error = u8; + + fn try_from(value: ByteGrid) -> Result { + let brightnesses = value + .iter() + .map(|b| Brightness::try_from(*b)) + .collect::, _>>()?; + Ok(BrightnessGrid::load( + value.width(), + value.height(), + &brightnesses, + )) + } +} + +#[cfg(test)] +mod tests { + use crate::value_grid::ValueGrid; + use crate::{Brightness, BrightnessGrid, DataRef, Grid}; + + #[test] + fn to_u8_grid() { + let mut grid = BrightnessGrid::new(2, 2); + grid.set(1, 0, Brightness::MIN); + grid.set(0, 1, Brightness::MAX); + let actual = ValueGrid::from(&grid); + assert_eq!(actual.data_ref(), &[11, 0, 11, 11]); + } + + #[test] + fn saturating_load() { + assert_eq!( + BrightnessGrid::load( + 2, + 2, + &[ + Brightness::MAX, + Brightness::MAX, + Brightness::MIN, + Brightness::MAX + ] + ), + BrightnessGrid::saturating_load(2, 2, &[255u8, 23, 0, 42]) + ); + } +} diff --git a/crates/servicepoint/src/byte_grid.rs b/crates/servicepoint/src/byte_grid.rs new file mode 100644 index 0000000..6ebf882 --- /dev/null +++ b/crates/servicepoint/src/byte_grid.rs @@ -0,0 +1,2 @@ +/// A simple grid of bytes - see [primitive_grid::PrimitiveGrid]. +pub type ByteGrid = crate::value_grid::ValueGrid; diff --git a/crates/servicepoint/src/char_grid.rs b/crates/servicepoint/src/char_grid.rs index 97eb170..7d55cf3 100644 --- a/crates/servicepoint/src/char_grid.rs +++ b/crates/servicepoint/src/char_grid.rs @@ -1,11 +1,9 @@ -use crate::primitive_grid::{ - PrimitiveGrid, SeriesError, TryLoadPrimitiveGridError, -}; +use crate::value_grid::{SetValueSeriesError, TryLoadPrimitiveGridError, ValueGrid}; use crate::Grid; use std::string::FromUtf8Error; /// A grid containing UTF-8 characters. -pub type CharGrid = PrimitiveGrid; +pub type CharGrid = ValueGrid; impl CharGrid { /// Copies a column from the grid as a String. @@ -24,23 +22,23 @@ impl CharGrid { /// Overwrites a row in the grid with a str. /// - /// Returns [SeriesError] if y is out of bounds or `row` is not of the correct size. + /// Returns [SetValueSeriesError] if y is out of bounds or `row` is not of the correct size. pub fn set_row_str( &mut self, y: usize, value: &str, - ) -> Result<(), SeriesError> { + ) -> Result<(), SetValueSeriesError> { self.set_row(y, value.chars().collect::>().as_ref()) } /// Overwrites a column in the grid with a str. /// - /// Returns [SeriesError] if y is out of bounds or `row` is not of the correct size. + /// Returns [SetValueSeriesError] if y is out of bounds or `row` is not of the correct size. pub fn set_col_str( &mut self, x: usize, value: &str, - ) -> Result<(), SeriesError> { + ) -> Result<(), SetValueSeriesError> { self.set_col(x, value.chars().collect::>().as_ref()) } @@ -129,7 +127,6 @@ impl From for Vec { #[cfg(test)] mod test { use super::*; - use crate::Grid; #[test] fn col_str() { let mut grid = CharGrid::new(2, 3); @@ -146,7 +143,7 @@ mod test { assert_eq!(grid.get_row_str(1), Some(String::from("\0\0"))); assert_eq!( grid.set_row_str(1, "abc"), - Err(SeriesError::InvalidLength { + Err(SetValueSeriesError::InvalidLength { expected: 2, actual: 3 }) diff --git a/crates/servicepoint/src/command.rs b/crates/servicepoint/src/command.rs index 25603ff..0807c80 100644 --- a/crates/servicepoint/src/command.rs +++ b/crates/servicepoint/src/command.rs @@ -1,11 +1,7 @@ -use crate::primitive_grid::PrimitiveGrid; -use crate::{ - command_code::CommandCode, - compression::into_decompressed, - packet::{Header, Packet}, - BitVec, Bitmap, Brightness, BrightnessGrid, CharGrid, CompressionCode, - Cp437Grid, Origin, Pixels, Tiles, TILE_SIZE, -}; +use crate::command_code::CommandCode; +use crate::compression::into_decompressed; +use crate::value_grid::ValueGrid; +use crate::*; /// Type alias for documenting the meaning of the u16 in enum values pub type Offset = usize; @@ -42,7 +38,7 @@ pub type Offset = usize; /// # Examples /// /// ```rust -/// # use servicepoint::{Brightness, Command, Connection, packet::Packet}; +/// use servicepoint::{Brightness, Command, Connection, Packet}; /// # /// // create command /// let command = Command::Brightness(Brightness::MAX); @@ -80,9 +76,8 @@ pub enum Command { /// # Examples /// /// ```rust - /// # use servicepoint::{Command, Connection, Origin}; + /// # use servicepoint::{Command, Connection, Origin, CharGrid}; /// # let connection = Connection::Fake; - /// use servicepoint::{CharGrid}; /// let grid = CharGrid::from("Hello,\nWorld!"); /// connection.send(Command::Utf8Data(Origin::ZERO, grid)).expect("send failed"); /// ``` @@ -97,9 +92,8 @@ pub enum Command { /// # Examples /// /// ```rust - /// # use servicepoint::{Command, Connection, Origin}; + /// # use servicepoint::{Command, Connection, Origin, CharGrid, Cp437Grid}; /// # let connection = Connection::Fake; - /// use servicepoint::{CharGrid, Cp437Grid}; /// let grid = CharGrid::from("Hello,\nWorld!"); /// let grid = Cp437Grid::from(&grid); /// connection.send(Command::Cp437Data(Origin::ZERO, grid)).expect("send failed"); @@ -447,8 +441,7 @@ impl Command { payload, } = packet; - let grid = - PrimitiveGrid::load(*width as usize, *height as usize, payload); + let grid = ValueGrid::load(*width as usize, *height as usize, payload); let grid = match BrightnessGrid::try_from(grid) { Ok(grid) => grid, Err(val) => return Err(TryFromPacketError::InvalidBrightness(val)), @@ -536,14 +529,11 @@ impl Command { #[cfg(test)] mod tests { + use crate::command::TryFromPacketError; + use crate::command_code::CommandCode; use crate::{ - bitvec::prelude::BitVec, - command::TryFromPacketError, - command_code::CommandCode, - origin::Pixels, - packet::{Header, Packet}, - Bitmap, Brightness, BrightnessGrid, CharGrid, Command, CompressionCode, - Cp437Grid, Origin, + BitVec, Bitmap, Brightness, BrightnessGrid, CharGrid, Command, + CompressionCode, Cp437Grid, Header, Origin, Packet, Pixels, }; fn round_trip(original: Command) { diff --git a/crates/servicepoint/src/compression.rs b/crates/servicepoint/src/compression.rs index 12c79d5..2e78073 100644 --- a/crates/servicepoint/src/compression.rs +++ b/crates/servicepoint/src/compression.rs @@ -8,7 +8,7 @@ use flate2::{FlushCompress, FlushDecompress, Status}; #[cfg(feature = "compression_zstd")] use zstd::{Decoder as ZstdDecoder, Encoder as ZstdEncoder}; -use crate::{packet::Payload, CompressionCode}; +use crate::{CompressionCode, Payload}; pub(crate) fn into_decompressed( kind: CompressionCode, diff --git a/crates/servicepoint/src/connection.rs b/crates/servicepoint/src/connection.rs index a0a6f11..417fd1d 100644 --- a/crates/servicepoint/src/connection.rs +++ b/crates/servicepoint/src/connection.rs @@ -165,7 +165,6 @@ impl Drop for Connection { #[cfg(test)] mod tests { use super::*; - use crate::packet::*; #[test] fn send_fake() { diff --git a/crates/servicepoint/src/constants.rs b/crates/servicepoint/src/constants.rs new file mode 100644 index 0000000..95c8684 --- /dev/null +++ b/crates/servicepoint/src/constants.rs @@ -0,0 +1,75 @@ +use std::time::Duration; + +/// size of a single tile in one dimension +pub const TILE_SIZE: usize = 8; + +/// Display tile count in the x-direction +/// +/// # Examples +/// +/// ```rust +/// # use servicepoint::{Cp437Grid, TILE_HEIGHT, TILE_WIDTH}; +/// let grid = Cp437Grid::new(TILE_WIDTH, TILE_HEIGHT); +/// ``` +pub const TILE_WIDTH: usize = 56; + +/// Display tile count in the y-direction +/// +/// # Examples +/// +/// ```rust +/// # use servicepoint::{Cp437Grid, TILE_HEIGHT, TILE_WIDTH}; +/// let grid = Cp437Grid::new(TILE_WIDTH, TILE_HEIGHT); +/// ``` +pub const TILE_HEIGHT: usize = 20; + +/// Display width in pixels +/// +/// # Examples +/// +/// ```rust +/// # use servicepoint::{PIXEL_HEIGHT, PIXEL_WIDTH, Bitmap}; +/// let grid = Bitmap::new(PIXEL_WIDTH, PIXEL_HEIGHT); +/// ``` +pub const PIXEL_WIDTH: usize = TILE_WIDTH * TILE_SIZE; + +/// Display height in pixels +/// +/// # Examples +/// +/// ```rust +/// # use servicepoint::{PIXEL_HEIGHT, PIXEL_WIDTH, Bitmap}; +/// let grid = Bitmap::new(PIXEL_WIDTH, PIXEL_HEIGHT); +/// ``` +pub const PIXEL_HEIGHT: usize = TILE_HEIGHT * TILE_SIZE; + +/// pixel count on whole screen +pub const PIXEL_COUNT: usize = PIXEL_WIDTH * PIXEL_HEIGHT; + +/// Actual hardware limit is around 28-29ms/frame. Rounded up for less dropped packets. +/// +/// # Examples +/// +/// ```rust +/// # use std::time::Instant; +/// # use servicepoint::{Command, CompressionCode, FRAME_PACING, Origin, Bitmap}; +/// # let connection = servicepoint::Connection::Fake; +/// # let pixels = Bitmap::max_sized(); +/// loop { +/// let start = Instant::now(); +/// +/// // Change pixels here +/// +/// connection.send(Command::BitmapLinearWin( +/// Origin::new(0,0), +/// pixels, +/// CompressionCode::Lzma +/// )) +/// .expect("send failed"); +/// +/// // warning: will crash if resulting duration is negative, e.g. when resuming from standby +/// std::thread::sleep(FRAME_PACING - start.elapsed()); +/// # break; // prevent doctest from hanging +/// } +/// ``` +pub const FRAME_PACING: Duration = Duration::from_millis(30); diff --git a/crates/servicepoint/src/cp437.rs b/crates/servicepoint/src/cp437.rs index 17b5d45..1129c85 100644 --- a/crates/servicepoint/src/cp437.rs +++ b/crates/servicepoint/src/cp437.rs @@ -2,94 +2,14 @@ //! //! Most of the functionality is only available with feature "cp437" enabled. -use crate::{Grid, primitive_grid::PrimitiveGrid}; use std::collections::HashMap; -/// A grid containing codepage 437 characters. -/// -/// The encoding is currently not enforced. -pub type Cp437Grid = PrimitiveGrid; - -/// The error occurring when loading an invalid character -#[derive(Debug, PartialEq, thiserror::Error)] -#[error( - "The character {char:?} at position {index} is not a valid CP437 character" -)] -pub struct InvalidCharError { - /// invalid character is at this position in input - index: usize, - /// the invalid character - char: char, -} - -impl Cp437Grid { - /// Load an ASCII-only [&str] into a [Cp437Grid] of specified width. - /// - /// # Panics - /// - /// - for width == 0 - /// - on empty strings - pub fn load_ascii( - value: &str, - width: usize, - wrap: bool, - ) -> Result { - assert!(width > 0); - assert!(!value.is_empty()); - - let mut chars = { - let mut x = 0; - let mut y = 0; - - for (index, char) in value.chars().enumerate() { - if !char.is_ascii() { - return Err(InvalidCharError { index, char }); - } - - let is_lf = char == '\n'; - if is_lf || (wrap && x == width) { - y += 1; - x = 0; - if is_lf { - continue; - } - } - - x += 1; - } - - Cp437Grid::new(width, y + 1) - }; - - let mut x = 0; - let mut y = 0; - for char in value.chars().map(move |c| c as u8) { - let is_lf = char == b'\n'; - if is_lf || (wrap && x == width) { - y += 1; - x = 0; - if is_lf { - continue; - } - } - - if wrap || x < width { - chars.set(x, y, char); - } - x += 1; - } - - Ok(chars) - } -} - #[allow(unused)] // depends on features pub use feature_cp437::*; #[cfg(feature = "cp437")] mod feature_cp437 { use super::*; - use crate::CharGrid; /// An array of 256 elements, mapping most of the CP437 values to UTF-8 characters /// @@ -129,24 +49,6 @@ mod feature_cp437 { const MISSING_CHAR_CP437: u8 = 0x3F; // '?' - impl From<&Cp437Grid> for CharGrid { - fn from(value: &Cp437Grid) -> Self { - value.map(cp437_to_char) - } - } - - impl From<&CharGrid> for Cp437Grid { - fn from(value: &CharGrid) -> Self { - value.map(char_to_cp437) - } - } - - impl From for Cp437Grid { - fn from(value: CharGrid) -> Self { - Cp437Grid::from(&value) - } - } - /// Convert the provided bytes to UTF-8. pub fn cp437_to_str(cp437: &[u8]) -> String { cp437.iter().map(move |char| cp437_to_char(*char)).collect() @@ -170,57 +72,10 @@ mod feature_cp437 { } } -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn load_ascii_nowrap() { - let chars = ['H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd'] - .map(move |c| c as u8); - let expected = Cp437Grid::load(5, 2, &chars); - - let actual = Cp437Grid::load_ascii("Hello,\nWorld!", 5, false).unwrap(); - // comma will be removed because line is too long and wrap is off - assert_eq!(actual, expected); - } - - #[test] - fn load_ascii_wrap() { - let chars = ['H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd'] - .map(move |c| c as u8); - let expected = Cp437Grid::load(5, 2, &chars); - - let actual = Cp437Grid::load_ascii("HelloWorld", 5, true).unwrap(); - // line break will be added - assert_eq!(actual, expected); - } - - #[test] - fn load_ascii_invalid() { - assert_eq!( - Err(InvalidCharError { - char: '🥶', - index: 2 - }), - Cp437Grid::load_ascii("?#🥶42", 3, false) - ); - } -} - #[cfg(test)] #[cfg(feature = "cp437")] mod tests_feature_cp437 { use super::*; - use crate::CharGrid; - - #[test] - fn round_trip_cp437() { - let utf8 = CharGrid::load(2, 2, &['Ä', 'x', '\n', '$']); - let cp437 = Cp437Grid::from(&utf8); - let actual = CharGrid::from(&cp437); - assert_eq!(actual, utf8); - } #[test] fn convert_str() { diff --git a/crates/servicepoint/src/cp437_grid.rs b/crates/servicepoint/src/cp437_grid.rs new file mode 100644 index 0000000..a19e014 --- /dev/null +++ b/crates/servicepoint/src/cp437_grid.rs @@ -0,0 +1,160 @@ +/// A grid containing codepage 437 characters. +/// +/// The encoding is currently not enforced. +pub type Cp437Grid = crate::value_grid::ValueGrid; + +/// The error occurring when loading an invalid character +#[derive(Debug, PartialEq, thiserror::Error)] +#[error( + "The character {char:?} at position {index} is not a valid CP437 character" +)] +pub struct InvalidCharError { + /// invalid character is at this position in input + index: usize, + /// the invalid character + char: char, +} + +impl Cp437Grid { + /// Load an ASCII-only [&str] into a [Cp437Grid] of specified width. + /// + /// # Panics + /// + /// - for width == 0 + /// - on empty strings + pub fn load_ascii( + value: &str, + width: usize, + wrap: bool, + ) -> Result { + assert!(width > 0); + assert!(!value.is_empty()); + + let mut chars = { + let mut x = 0; + let mut y = 0; + + for (index, char) in value.chars().enumerate() { + if !char.is_ascii() { + return Err(InvalidCharError { index, char }); + } + + let is_lf = char == '\n'; + if is_lf || (wrap && x == width) { + y += 1; + x = 0; + if is_lf { + continue; + } + } + + x += 1; + } + + Cp437Grid::new(width, y + 1) + }; + + let mut x = 0; + let mut y = 0; + for char in value.chars().map(move |c| c as u8) { + let is_lf = char == b'\n'; + if is_lf || (wrap && x == width) { + y += 1; + x = 0; + if is_lf { + continue; + } + } + + if wrap || x < width { + chars.set(x, y, char); + } + x += 1; + } + + Ok(chars) + } +} + +use crate::Grid; +#[allow(unused)] // depends on features +pub use feature_cp437::*; + +#[cfg(feature = "cp437")] +mod feature_cp437 { + use super::*; + use crate::{ + cp437::{char_to_cp437, cp437_to_char}, + CharGrid, + }; + + impl From<&Cp437Grid> for CharGrid { + fn from(value: &Cp437Grid) -> Self { + value.map(cp437_to_char) + } + } + + impl From<&CharGrid> for Cp437Grid { + fn from(value: &CharGrid) -> Self { + value.map(char_to_cp437) + } + } + + impl From for Cp437Grid { + fn from(value: CharGrid) -> Self { + Cp437Grid::from(&value) + } + } +} +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn load_ascii_nowrap() { + let chars = ['H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd'] + .map(move |c| c as u8); + let expected = Cp437Grid::load(5, 2, &chars); + + let actual = Cp437Grid::load_ascii("Hello,\nWorld!", 5, false).unwrap(); + // comma will be removed because line is too long and wrap is off + assert_eq!(actual, expected); + } + + #[test] + fn load_ascii_wrap() { + let chars = ['H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd'] + .map(move |c| c as u8); + let expected = Cp437Grid::load(5, 2, &chars); + + let actual = Cp437Grid::load_ascii("HelloWorld", 5, true).unwrap(); + // line break will be added + assert_eq!(actual, expected); + } + + #[test] + fn load_ascii_invalid() { + assert_eq!( + Err(InvalidCharError { + char: '🥶', + index: 2 + }), + Cp437Grid::load_ascii("?#🥶42", 3, false) + ); + } +} + +#[cfg(test)] +#[cfg(feature = "cp437")] +mod tests_feature_cp437 { + use super::*; + use crate::CharGrid; + + #[test] + fn round_trip_cp437() { + let utf8 = CharGrid::load(2, 2, &['Ä', 'x', '\n', '$']); + let cp437 = Cp437Grid::from(&utf8); + let actual = CharGrid::from(&cp437); + assert_eq!(actual, utf8); + } +} diff --git a/crates/servicepoint/src/lib.rs b/crates/servicepoint/src/lib.rs index d89e0d7..8cb04d2 100644 --- a/crates/servicepoint/src/lib.rs +++ b/crates/servicepoint/src/lib.rs @@ -35,115 +35,44 @@ //! connection.send(command).expect("send failed"); //! ``` -use std::time::Duration; - -pub use bitvec; - pub use crate::bitmap::Bitmap; -pub use crate::brightness::{Brightness, BrightnessGrid}; +pub use crate::bitvec::BitVec; +pub use crate::brightness::Brightness; +pub use crate::brightness_grid::BrightnessGrid; +pub use crate::byte_grid::ByteGrid; pub use crate::char_grid::CharGrid; pub use crate::command::{Command, Offset}; pub use crate::compression_code::CompressionCode; pub use crate::connection::Connection; -pub use crate::cp437::Cp437Grid; +pub use crate::constants::*; +pub use crate::cp437_grid::Cp437Grid; pub use crate::data_ref::DataRef; pub use crate::grid::Grid; pub use crate::origin::{Origin, Pixels, Tiles}; - -/// An alias for the specific type of [bitvec::prelude::BitVec] used. -pub type BitVec = bitvec::prelude::BitVec; - -/// A simple grid of bytes - see [primitive_grid::PrimitiveGrid]. -pub type ByteGrid = primitive_grid::PrimitiveGrid; +pub use crate::packet::{Header, Packet, Payload}; +pub use crate::value_grid::{ + IterGridRows, SetValueSeriesError, TryLoadPrimitiveGridError, Value, ValueGrid, +}; mod bitmap; +mod bitvec; mod brightness; +mod brightness_grid; +mod byte_grid; mod char_grid; mod command; mod command_code; mod compression; mod compression_code; mod connection; +mod constants; pub mod cp437; +mod cp437_grid; mod data_ref; mod grid; mod origin; -pub mod packet; -pub mod primitive_grid; - -/// size of a single tile in one dimension -pub const TILE_SIZE: usize = 8; - -/// Display tile count in the x-direction -/// -/// # Examples -/// -/// ```rust -/// # use servicepoint::{Cp437Grid, TILE_HEIGHT, TILE_WIDTH}; -/// let grid = Cp437Grid::new(TILE_WIDTH, TILE_HEIGHT); -/// ``` -pub const TILE_WIDTH: usize = 56; - -/// Display tile count in the y-direction -/// -/// # Examples -/// -/// ```rust -/// # use servicepoint::{Cp437Grid, TILE_HEIGHT, TILE_WIDTH}; -/// let grid = Cp437Grid::new(TILE_WIDTH, TILE_HEIGHT); -/// ``` -pub const TILE_HEIGHT: usize = 20; - -/// Display width in pixels -/// -/// # Examples -/// -/// ```rust -/// # use servicepoint::{PIXEL_HEIGHT, PIXEL_WIDTH, Bitmap}; -/// let grid = Bitmap::new(PIXEL_WIDTH, PIXEL_HEIGHT); -/// ``` -pub const PIXEL_WIDTH: usize = TILE_WIDTH * TILE_SIZE; - -/// Display height in pixels -/// -/// # Examples -/// -/// ```rust -/// # use servicepoint::{PIXEL_HEIGHT, PIXEL_WIDTH, Bitmap}; -/// let grid = Bitmap::new(PIXEL_WIDTH, PIXEL_HEIGHT); -/// ``` -pub const PIXEL_HEIGHT: usize = TILE_HEIGHT * TILE_SIZE; - -/// pixel count on whole screen -pub const PIXEL_COUNT: usize = PIXEL_WIDTH * PIXEL_HEIGHT; - -/// Actual hardware limit is around 28-29ms/frame. Rounded up for less dropped packets. -/// -/// # Examples -/// -/// ```rust -/// # use std::time::Instant; -/// # use servicepoint::{Command, CompressionCode, FRAME_PACING, Origin, Bitmap}; -/// # let connection = servicepoint::Connection::Fake; -/// # let pixels = Bitmap::max_sized(); -/// loop { -/// let start = Instant::now(); -/// -/// // Change pixels here -/// -/// connection.send(Command::BitmapLinearWin( -/// Origin::new(0,0), -/// pixels, -/// CompressionCode::Lzma -/// )) -/// .expect("send failed"); -/// -/// // warning: will crash if resulting duration is negative, e.g. when resuming from standby -/// std::thread::sleep(FRAME_PACING - start.elapsed()); -/// # break; // prevent doctest from hanging -/// } -/// ``` -pub const FRAME_PACING: Duration = Duration::from_millis(30); +mod packet; +mod value_grid; // include README.md in doctest #[doc = include_str!("../README.md")] diff --git a/crates/servicepoint/src/packet.rs b/crates/servicepoint/src/packet.rs index ee66426..099c50c 100644 --- a/crates/servicepoint/src/packet.rs +++ b/crates/servicepoint/src/packet.rs @@ -7,7 +7,7 @@ //! Converting a packet to a command and back: //! //! ```rust -//! use servicepoint::{Command, packet::Packet}; +//! use servicepoint::{Command, Packet}; //! # let command = Command::Clear; //! let packet: Packet = command.into(); //! let command: Command = Command::try_from(packet).expect("could not read command from packet"); @@ -16,20 +16,20 @@ //! Converting a packet to bytes and back: //! //! ```rust -//! use servicepoint::{Command, packet::Packet}; +//! use servicepoint::{Command, Packet}; //! # let command = Command::Clear; //! # let packet: Packet = command.into(); //! let bytes: Vec = packet.into(); //! let packet = Packet::try_from(bytes).expect("could not read packet from bytes"); //! ``` -use std::mem::size_of; - +use crate::command_code::CommandCode; use crate::compression::into_compressed; use crate::{ - command_code::CommandCode, Bitmap, Command, CompressionCode, Grid, Offset, - Origin, Pixels, Tiles, TILE_SIZE, + Bitmap, Command, CompressionCode, Grid, Offset, Origin, Pixels, Tiles, + TILE_SIZE, }; +use std::mem::size_of; /// A raw header. /// diff --git a/crates/servicepoint/src/primitive_grid.rs b/crates/servicepoint/src/value_grid.rs similarity index 78% rename from crates/servicepoint/src/primitive_grid.rs rename to crates/servicepoint/src/value_grid.rs index a6eaf5c..b01067f 100644 --- a/crates/servicepoint/src/primitive_grid.rs +++ b/crates/servicepoint/src/value_grid.rs @@ -1,17 +1,17 @@ -//! This module contains the implementation of the [PrimitiveGrid]. +//! This module contains the implementation of the [ValueGrid]. use std::fmt::Debug; use std::slice::{Iter, IterMut}; -use crate::{DataRef, Grid}; +use crate::*; -/// A type that can be stored in a [PrimitiveGrid], e.g. [char], [u8]. -pub trait PrimitiveGridType: Sized + Default + Copy + Clone + Debug {} -impl PrimitiveGridType for T {} +/// A type that can be stored in a [ValueGrid], e.g. [char], [u8]. +pub trait Value: Sized + Default + Copy + Clone + Debug {} +impl Value for T {} /// A 2D grid of bytes #[derive(Debug, Clone, PartialEq)] -pub struct PrimitiveGrid { +pub struct ValueGrid { width: usize, height: usize, data: Vec, @@ -19,7 +19,7 @@ pub struct PrimitiveGrid { /// Error type for methods that change a whole column or row at once #[derive(thiserror::Error, Debug, PartialEq)] -pub enum SeriesError { +pub enum SetValueSeriesError { #[error("The index {index} was out of bounds for size {size}")] /// The index {index} was out of bounds for size {size} OutOfBounds { @@ -38,15 +38,15 @@ pub enum SeriesError { }, } -impl PrimitiveGrid { - /// Creates a new [PrimitiveGrid] with the specified dimensions. +impl ValueGrid { + /// Creates a new [ValueGrid] with the specified dimensions. /// /// # Arguments /// /// - width: size in x-direction /// - height: size in y-direction /// - /// returns: [PrimitiveGrid] initialized to default value. + /// returns: [ValueGrid] initialized to default value. pub fn new(width: usize, height: usize) -> Self { Self { data: vec![Default::default(); width * height], @@ -55,9 +55,9 @@ impl PrimitiveGrid { } } - /// Loads a [PrimitiveGrid] with the specified dimensions from the provided data. + /// Loads a [ValueGrid] with the specified dimensions from the provided data. /// - /// returns: [PrimitiveGrid] that contains a copy of the provided data + /// returns: [ValueGrid] that contains a copy of the provided data /// /// # Panics /// @@ -76,9 +76,9 @@ impl PrimitiveGrid { } } - /// Loads a [PrimitiveGrid] with the specified dimensions from the provided data. + /// Loads a [ValueGrid] with the specified dimensions from the provided data. /// - /// returns: [PrimitiveGrid] that contains a copy of the provided data or [TryLoadPrimitiveGridError]. + /// returns: [ValueGrid] that contains a copy of the provided data or [TryLoadPrimitiveGridError]. pub fn try_load( width: usize, height: usize, @@ -95,7 +95,7 @@ impl PrimitiveGrid { }) } - /// Iterate over all cells in [PrimitiveGrid]. + /// Iterate over all cells in [ValueGrid]. /// /// Order is equivalent to the following loop: /// ``` @@ -111,9 +111,9 @@ impl PrimitiveGrid { self.data.iter() } - /// Iterate over all rows in [PrimitiveGrid] top to bottom. - pub fn iter_rows(&self) -> IterRows { - IterRows { + /// Iterate over all rows in [ValueGrid] top to bottom. + pub fn iter_rows(&self) -> IterGridRows { + IterGridRows { byte_grid: self, row: 0, } @@ -176,9 +176,9 @@ impl PrimitiveGrid { /// ``` /// [Brightness]: [crate::Brightness] /// [Command]: [crate::Command] - pub fn map(&self, f: F) -> PrimitiveGrid + pub fn map(&self, f: F) -> ValueGrid where - TConverted: PrimitiveGridType, + TConverted: Value, F: Fn(T) -> TConverted, { let data = self @@ -186,7 +186,7 @@ impl PrimitiveGrid { .iter() .map(|elem| f(*elem)) .collect::>(); - PrimitiveGrid::load(self.width(), self.height(), &data) + ValueGrid::load(self.width(), self.height(), &data) } /// Copies a row from the grid. @@ -212,9 +212,13 @@ impl PrimitiveGrid { /// Overwrites a column in the grid. /// /// Returns [Err] if x is out of bounds or `col` is not of the correct size. - pub fn set_col(&mut self, x: usize, col: &[T]) -> Result<(), SeriesError> { + pub fn set_col( + &mut self, + x: usize, + col: &[T], + ) -> Result<(), SetValueSeriesError> { if col.len() != self.height() { - return Err(SeriesError::InvalidLength { + return Err(SetValueSeriesError::InvalidLength { expected: self.height(), actual: col.len(), }); @@ -231,7 +235,7 @@ impl PrimitiveGrid { { Ok(()) } else { - Err(SeriesError::OutOfBounds { + Err(SetValueSeriesError::OutOfBounds { index: x, size: width, }) @@ -241,10 +245,14 @@ impl PrimitiveGrid { /// Overwrites a row in the grid. /// /// Returns [Err] if y is out of bounds or `row` is not of the correct size. - pub fn set_row(&mut self, y: usize, row: &[T]) -> Result<(), SeriesError> { + pub fn set_row( + &mut self, + y: usize, + row: &[T], + ) -> Result<(), SetValueSeriesError> { let width = self.width(); if row.len() != width { - return Err(SeriesError::InvalidLength { + return Err(SetValueSeriesError::InvalidLength { expected: width, actual: row.len(), }); @@ -253,7 +261,7 @@ impl PrimitiveGrid { let chunk = match self.data.chunks_exact_mut(width).nth(y) { Some(row) => row, None => { - return Err(SeriesError::OutOfBounds { + return Err(SetValueSeriesError::OutOfBounds { size: self.height(), index: y, }) @@ -271,7 +279,7 @@ pub enum TryLoadPrimitiveGridError { InvalidDimensions, } -impl Grid for PrimitiveGrid { +impl Grid for ValueGrid { /// Sets the value of the cell at the specified position in the `PrimitiveGrid. /// /// # Arguments @@ -314,7 +322,7 @@ impl Grid for PrimitiveGrid { } } -impl DataRef for PrimitiveGrid { +impl DataRef for ValueGrid { /// Get the underlying byte rows mutable fn data_ref_mut(&mut self) -> &mut [T] { self.data.as_mut_slice() @@ -326,20 +334,20 @@ impl DataRef for PrimitiveGrid { } } -impl From> for Vec { +impl From> for Vec { /// Turn into the underlying [`Vec`] containing the rows of bytes. - fn from(value: PrimitiveGrid) -> Self { + fn from(value: ValueGrid) -> Self { value.data } } -/// An iterator iver the rows in a [PrimitiveGrid] -pub struct IterRows<'t, T: PrimitiveGridType> { - byte_grid: &'t PrimitiveGrid, +/// An iterator iver the rows in a [ValueGrid] +pub struct IterGridRows<'t, T: Value> { + byte_grid: &'t ValueGrid, row: usize, } -impl<'t, T: PrimitiveGridType> Iterator for IterRows<'t, T> { +impl<'t, T: Value> Iterator for IterGridRows<'t, T> { type Item = Iter<'t, T>; fn next(&mut self) -> Option { @@ -357,12 +365,14 @@ impl<'t, T: PrimitiveGridType> Iterator for IterRows<'t, T> { #[cfg(test)] mod tests { - use crate::primitive_grid::{PrimitiveGrid, SeriesError}; - use crate::{DataRef, Grid}; + use crate::{ + value_grid::{SetValueSeriesError, ValueGrid}, + *, + }; #[test] fn fill() { - let mut grid = PrimitiveGrid::::new(2, 2); + let mut grid = ValueGrid::::new(2, 2); assert_eq!(grid.data, [0x00, 0x00, 0x00, 0x00]); grid.fill(42); @@ -371,7 +381,7 @@ mod tests { #[test] fn get_set() { - let mut grid = PrimitiveGrid::new(2, 2); + let mut grid = ValueGrid::new(2, 2); assert_eq!(grid.get(0, 0), 0); assert_eq!(grid.get(1, 1), 0); @@ -386,7 +396,7 @@ mod tests { #[test] fn load() { - let mut grid = PrimitiveGrid::new(2, 3); + let mut grid = ValueGrid::new(2, 3); for x in 0..grid.width { for y in 0..grid.height { grid.set(x, y, (x + y) as u8); @@ -397,13 +407,13 @@ mod tests { let data: Vec = grid.into(); - let grid = PrimitiveGrid::load(2, 3, &data); + let grid = ValueGrid::load(2, 3, &data); assert_eq!(grid.data, [0, 1, 1, 2, 2, 3]); } #[test] fn mut_data_ref() { - let mut vec = PrimitiveGrid::new(2, 2); + let mut vec = ValueGrid::new(2, 2); let data_ref = vec.data_ref_mut(); data_ref.copy_from_slice(&[1, 2, 3, 4]); @@ -414,7 +424,7 @@ mod tests { #[test] fn iter() { - let mut vec = PrimitiveGrid::new(2, 2); + let mut vec = ValueGrid::new(2, 2); vec.set(1, 1, 5); let mut iter = vec.iter(); @@ -426,7 +436,7 @@ mod tests { #[test] fn iter_mut() { - let mut vec = PrimitiveGrid::new(2, 3); + let mut vec = ValueGrid::new(2, 3); for (index, cell) in vec.iter_mut().enumerate() { *cell = index as u8; } @@ -436,7 +446,7 @@ mod tests { #[test] fn iter_rows() { - let vec = PrimitiveGrid::load(2, 3, &[0, 1, 1, 2, 2, 3]); + let vec = ValueGrid::load(2, 3, &[0, 1, 1, 2, 2, 3]); for (y, row) in vec.iter_rows().enumerate() { for (x, val) in row.enumerate() { assert_eq!(*val, (x + y) as u8); @@ -447,20 +457,20 @@ mod tests { #[test] #[should_panic] fn out_of_bounds_x() { - let mut vec = PrimitiveGrid::load(2, 2, &[0, 1, 2, 3]); + let mut vec = ValueGrid::load(2, 2, &[0, 1, 2, 3]); vec.set(2, 1, 5); } #[test] #[should_panic] fn out_of_bounds_y() { - let vec = PrimitiveGrid::load(2, 2, &[0, 1, 2, 3]); + let vec = ValueGrid::load(2, 2, &[0, 1, 2, 3]); vec.get(1, 2); } #[test] fn ref_mut() { - let mut vec = PrimitiveGrid::load(2, 2, &[0, 1, 2, 3]); + let mut vec = ValueGrid::load(2, 2, &[0, 1, 2, 3]); let top_left = vec.get_ref_mut(0, 0); *top_left += 5; @@ -471,7 +481,7 @@ mod tests { #[test] fn optional() { - let mut grid = PrimitiveGrid::load(2, 2, &[0, 1, 2, 3]); + let mut grid = ValueGrid::load(2, 2, &[0, 1, 2, 3]); grid.set_optional(0, 0, 5); grid.set_optional(-1, 0, 8); grid.set_optional(0, 8, 42); @@ -483,18 +493,18 @@ mod tests { #[test] fn col() { - let mut grid = PrimitiveGrid::load(2, 3, &[0, 1, 2, 3, 4, 5]); + let mut grid = ValueGrid::load(2, 3, &[0, 1, 2, 3, 4, 5]); assert_eq!(grid.get_col(0), Some(vec![0, 2, 4])); assert_eq!(grid.get_col(1), Some(vec![1, 3, 5])); assert_eq!(grid.get_col(2), None); assert_eq!(grid.set_col(0, &[5, 7, 9]), Ok(())); assert_eq!( grid.set_col(2, &[5, 7, 9]), - Err(SeriesError::OutOfBounds { size: 2, index: 2 }) + Err(SetValueSeriesError::OutOfBounds { size: 2, index: 2 }) ); assert_eq!( grid.set_col(0, &[5, 7]), - Err(SeriesError::InvalidLength { + Err(SetValueSeriesError::InvalidLength { expected: 3, actual: 2 }) @@ -504,7 +514,7 @@ mod tests { #[test] fn row() { - let mut grid = PrimitiveGrid::load(2, 3, &[0, 1, 2, 3, 4, 5]); + let mut grid = ValueGrid::load(2, 3, &[0, 1, 2, 3, 4, 5]); assert_eq!(grid.get_row(0), Some(vec![0, 1])); assert_eq!(grid.get_row(2), Some(vec![4, 5])); assert_eq!(grid.get_row(3), None); @@ -512,11 +522,11 @@ mod tests { assert_eq!(grid.get_row(0), Some(vec![5, 7])); assert_eq!( grid.set_row(3, &[5, 7]), - Err(SeriesError::OutOfBounds { size: 3, index: 3 }) + Err(SetValueSeriesError::OutOfBounds { size: 3, index: 3 }) ); assert_eq!( grid.set_row(2, &[5, 7, 3]), - Err(SeriesError::InvalidLength { + Err(SetValueSeriesError::InvalidLength { expected: 2, actual: 3 }) diff --git a/crates/servicepoint_binding_c/src/bitmap.rs b/crates/servicepoint_binding_c/src/bitmap.rs index 2956de3..3313385 100644 --- a/crates/servicepoint_binding_c/src/bitmap.rs +++ b/crates/servicepoint_binding_c/src/bitmap.rs @@ -2,8 +2,8 @@ //! //! prefix `sp_bitmap_` -use std::ptr::NonNull; use servicepoint::{DataRef, Grid}; +use std::ptr::NonNull; use crate::byte_slice::SPByteSlice; @@ -43,9 +43,7 @@ pub unsafe extern "C" fn sp_bitmap_new( width: usize, height: usize, ) -> NonNull { - let result = Box::new(SPBitmap(servicepoint::Bitmap::new( - width, height, - ))); + let result = Box::new(SPBitmap(servicepoint::Bitmap::new(width, height))); NonNull::from(Box::leak(result)) } @@ -96,9 +94,8 @@ pub unsafe extern "C" fn sp_bitmap_load( ) -> NonNull { assert!(!data.is_null()); let data = std::slice::from_raw_parts(data, data_length); - let result = Box::new(SPBitmap(servicepoint::Bitmap::load( - width, height, data, - ))); + let result = + Box::new(SPBitmap(servicepoint::Bitmap::load(width, height, data))); NonNull::from(Box::leak(result)) } diff --git a/crates/servicepoint_binding_c/src/bitvec.rs b/crates/servicepoint_binding_c/src/bitvec.rs index 4ee5f52..484e849 100644 --- a/crates/servicepoint_binding_c/src/bitvec.rs +++ b/crates/servicepoint_binding_c/src/bitvec.rs @@ -2,9 +2,8 @@ //! //! prefix `sp_bitvec_` -use std::ptr::NonNull; use crate::SPByteSlice; -use servicepoint::bitvec::prelude::{BitVec, Msb0}; +use std::ptr::NonNull; /// A vector of bits /// @@ -14,15 +13,15 @@ use servicepoint::bitvec::prelude::{BitVec, Msb0}; /// sp_bitvec_set(vec, 5, true); /// sp_bitvec_free(vec); /// ``` -pub struct SPBitVec(BitVec); +pub struct SPBitVec(servicepoint::BitVec); -impl From> for SPBitVec { - fn from(actual: BitVec) -> Self { +impl From for SPBitVec { + fn from(actual: servicepoint::BitVec) -> Self { Self(actual) } } -impl From for BitVec { +impl From for servicepoint::BitVec { fn from(value: SPBitVec) -> Self { value.0 } @@ -54,7 +53,7 @@ impl Clone for SPBitVec { /// by explicitly calling `sp_bitvec_free`. #[no_mangle] pub unsafe extern "C" fn sp_bitvec_new(size: usize) -> NonNull { - let result = Box::new(SPBitVec(BitVec::repeat(false, size))); + let result = Box::new(SPBitVec(servicepoint::BitVec::repeat(false, size))); NonNull::from(Box::leak(result)) } @@ -81,7 +80,7 @@ pub unsafe extern "C" fn sp_bitvec_load( ) -> NonNull { assert!(!data.is_null()); let data = std::slice::from_raw_parts(data, data_length); - let result = Box::new(SPBitVec(BitVec::from_slice(data))); + let result = Box::new(SPBitVec(servicepoint::BitVec::from_slice(data))); NonNull::from(Box::leak(result)) } diff --git a/crates/servicepoint_binding_c/src/brightness_grid.rs b/crates/servicepoint_binding_c/src/brightness_grid.rs index d8c4310..93880a1 100644 --- a/crates/servicepoint_binding_c/src/brightness_grid.rs +++ b/crates/servicepoint_binding_c/src/brightness_grid.rs @@ -3,7 +3,7 @@ //! prefix `sp_brightness_grid_` use crate::SPByteSlice; -use servicepoint::{Brightness, ByteGrid, DataRef, Grid}; +use servicepoint::{DataRef, Grid}; use std::convert::Into; use std::intrinsics::transmute; use std::ptr::NonNull; @@ -48,9 +48,9 @@ pub unsafe extern "C" fn sp_brightness_grid_new( width: usize, height: usize, ) -> NonNull { - let result = Box::new(SPBrightnessGrid( - servicepoint::BrightnessGrid::new(width, height), - )); + let result = Box::new(SPBrightnessGrid(servicepoint::BrightnessGrid::new( + width, height, + ))); NonNull::from(Box::leak(result)) } @@ -80,7 +80,7 @@ pub unsafe extern "C" fn sp_brightness_grid_load( ) -> NonNull { assert!(!data.is_null()); let data = std::slice::from_raw_parts(data, data_length); - let grid = ByteGrid::load(width, height, data); + let grid = servicepoint::ByteGrid::load(width, height, data); let grid = servicepoint::BrightnessGrid::try_from(grid) .expect("invalid brightness value"); let result = Box::new(SPBrightnessGrid(grid)); @@ -203,8 +203,8 @@ pub unsafe extern "C" fn sp_brightness_grid_set( value: u8, ) { assert!(!brightness_grid.is_null()); - let brightness = - Brightness::try_from(value).expect("invalid brightness value"); + let brightness = servicepoint::Brightness::try_from(value) + .expect("invalid brightness value"); (*brightness_grid).0.set(x, y, brightness); } @@ -232,8 +232,8 @@ pub unsafe extern "C" fn sp_brightness_grid_fill( value: u8, ) { assert!(!brightness_grid.is_null()); - let brightness = - Brightness::try_from(value).expect("invalid brightness value"); + let brightness = servicepoint::Brightness::try_from(value) + .expect("invalid brightness value"); (*brightness_grid).0.fill(brightness); } @@ -311,7 +311,7 @@ pub unsafe extern "C" fn sp_brightness_grid_unsafe_data_ref( brightness_grid: *mut SPBrightnessGrid, ) -> SPByteSlice { assert!(!brightness_grid.is_null()); - assert_eq!(core::mem::size_of::(), 1); + assert_eq!(core::mem::size_of::(), 1); let data = (*brightness_grid).0.data_ref_mut(); // this assumes more about the memory layout than rust guarantees. yikes! let data: &mut [u8] = transmute(data); diff --git a/crates/servicepoint_binding_c/src/command.rs b/crates/servicepoint_binding_c/src/command.rs index 1c56d07..3da0cbb 100644 --- a/crates/servicepoint_binding_c/src/command.rs +++ b/crates/servicepoint_binding_c/src/command.rs @@ -4,8 +4,6 @@ use std::ptr::{null_mut, NonNull}; -use servicepoint::{Brightness, Origin}; - use crate::{ SPBitVec, SPBitmap, SPBrightnessGrid, SPCompressionCode, SPCp437Grid, SPPacket, @@ -164,11 +162,10 @@ pub unsafe extern "C" fn sp_command_fade_out() -> NonNull { pub unsafe extern "C" fn sp_command_brightness( brightness: u8, ) -> NonNull { - let brightness = - Brightness::try_from(brightness).expect("invalid brightness"); - let result = Box::new(SPCommand( - servicepoint::Command::Brightness(brightness), - )); + let brightness = servicepoint::Brightness::try_from(brightness) + .expect("invalid brightness"); + let result = + Box::new(SPCommand(servicepoint::Command::Brightness(brightness))); NonNull::from(Box::leak(result)) } @@ -198,9 +195,10 @@ pub unsafe extern "C" fn sp_command_char_brightness( ) -> NonNull { assert!(!grid.is_null()); let byte_grid = *Box::from_raw(grid); - let result = Box::new(SPCommand( - servicepoint::Command::CharBrightness(Origin::new(x, y), byte_grid.0), - )); + let result = Box::new(SPCommand(servicepoint::Command::CharBrightness( + servicepoint::Origin::new(x, y), + byte_grid.0, + ))); NonNull::from(Box::leak(result)) } @@ -237,13 +235,11 @@ pub unsafe extern "C" fn sp_command_bitmap_linear( ) -> NonNull { assert!(!bit_vec.is_null()); let bit_vec = *Box::from_raw(bit_vec); - let result = Box::new(SPCommand( - servicepoint::Command::BitmapLinear( - offset, - bit_vec.into(), - compression.try_into().expect("invalid compression code"), - ), - )); + let result = Box::new(SPCommand(servicepoint::Command::BitmapLinear( + offset, + bit_vec.into(), + compression.try_into().expect("invalid compression code"), + ))); NonNull::from(Box::leak(result)) } @@ -280,13 +276,11 @@ pub unsafe extern "C" fn sp_command_bitmap_linear_and( ) -> NonNull { assert!(!bit_vec.is_null()); let bit_vec = *Box::from_raw(bit_vec); - let result = Box::new(SPCommand( - servicepoint::Command::BitmapLinearAnd( - offset, - bit_vec.into(), - compression.try_into().expect("invalid compression code"), - ), - )); + let result = Box::new(SPCommand(servicepoint::Command::BitmapLinearAnd( + offset, + bit_vec.into(), + compression.try_into().expect("invalid compression code"), + ))); NonNull::from(Box::leak(result)) } @@ -323,13 +317,11 @@ pub unsafe extern "C" fn sp_command_bitmap_linear_or( ) -> NonNull { assert!(!bit_vec.is_null()); let bit_vec = *Box::from_raw(bit_vec); - let result = Box::new(SPCommand( - servicepoint::Command::BitmapLinearOr( - offset, - bit_vec.into(), - compression.try_into().expect("invalid compression code"), - ), - )); + let result = Box::new(SPCommand(servicepoint::Command::BitmapLinearOr( + offset, + bit_vec.into(), + compression.try_into().expect("invalid compression code"), + ))); NonNull::from(Box::leak(result)) } @@ -366,13 +358,11 @@ pub unsafe extern "C" fn sp_command_bitmap_linear_xor( ) -> NonNull { assert!(!bit_vec.is_null()); let bit_vec = *Box::from_raw(bit_vec); - let result = Box::new(SPCommand( - servicepoint::Command::BitmapLinearXor( - offset, - bit_vec.into(), - compression.try_into().expect("invalid compression code"), - ), - )); + let result = Box::new(SPCommand(servicepoint::Command::BitmapLinearXor( + offset, + bit_vec.into(), + compression.try_into().expect("invalid compression code"), + ))); NonNull::from(Box::leak(result)) } @@ -402,9 +392,10 @@ pub unsafe extern "C" fn sp_command_cp437_data( ) -> NonNull { assert!(!grid.is_null()); let grid = *Box::from_raw(grid); - let result = Box::new(SPCommand( - servicepoint::Command::Cp437Data(Origin::new(x, y), grid.0), - )); + let result = Box::new(SPCommand(servicepoint::Command::Cp437Data( + servicepoint::Origin::new(x, y), + grid.0, + ))); NonNull::from(Box::leak(result)) } @@ -437,15 +428,13 @@ pub unsafe extern "C" fn sp_command_bitmap_linear_win( ) -> NonNull { assert!(!bitmap.is_null()); let byte_grid = (*Box::from_raw(bitmap)).0; - let result = Box::new(SPCommand( - servicepoint::Command::BitmapLinearWin( - Origin::new(x, y), - byte_grid, - compression_code - .try_into() - .expect("invalid compression code"), - ), - )); + let result = Box::new(SPCommand(servicepoint::Command::BitmapLinearWin( + servicepoint::Origin::new(x, y), + byte_grid, + compression_code + .try_into() + .expect("invalid compression code"), + ))); NonNull::from(Box::leak(result)) } diff --git a/crates/servicepoint_binding_c/src/cp437_grid.rs b/crates/servicepoint_binding_c/src/cp437_grid.rs index 32e2642..9b366c8 100644 --- a/crates/servicepoint_binding_c/src/cp437_grid.rs +++ b/crates/servicepoint_binding_c/src/cp437_grid.rs @@ -2,9 +2,9 @@ //! //! prefix `sp_cp437_grid_` -use std::ptr::NonNull; use crate::SPByteSlice; use servicepoint::{DataRef, Grid}; +use std::ptr::NonNull; /// A C-wrapper for grid containing codepage 437 characters. /// @@ -41,9 +41,8 @@ pub unsafe extern "C" fn sp_cp437_grid_new( width: usize, height: usize, ) -> NonNull { - let result = Box::new(SPCp437Grid( - servicepoint::Cp437Grid::new(width, height), - )); + let result = + Box::new(SPCp437Grid(servicepoint::Cp437Grid::new(width, height))); NonNull::from(Box::leak(result)) } @@ -73,9 +72,9 @@ pub unsafe extern "C" fn sp_cp437_grid_load( ) -> NonNull { assert!(data.is_null()); let data = std::slice::from_raw_parts(data, data_length); - let result = Box::new(SPCp437Grid( - servicepoint::Cp437Grid::load(width, height, data), - )); + let result = Box::new(SPCp437Grid(servicepoint::Cp437Grid::load( + width, height, data, + ))); NonNull::from(Box::leak(result)) } diff --git a/crates/servicepoint_binding_c/src/lib.rs b/crates/servicepoint_binding_c/src/lib.rs index 0e0ddd0..42442f5 100644 --- a/crates/servicepoint_binding_c/src/lib.rs +++ b/crates/servicepoint_binding_c/src/lib.rs @@ -25,8 +25,8 @@ //! } //! ``` -pub use crate::bitvec::*; pub use crate::bitmap::*; +pub use crate::bitvec::*; pub use crate::brightness_grid::*; pub use crate::byte_slice::*; pub use crate::command::*; @@ -35,8 +35,8 @@ pub use crate::constants::*; pub use crate::cp437_grid::*; pub use crate::packet::*; -mod bitvec; mod bitmap; +mod bitvec; mod brightness_grid; mod byte_slice; mod command; diff --git a/crates/servicepoint_binding_c/src/packet.rs b/crates/servicepoint_binding_c/src/packet.rs index 1f7082a..9293a8a 100644 --- a/crates/servicepoint_binding_c/src/packet.rs +++ b/crates/servicepoint_binding_c/src/packet.rs @@ -7,7 +7,7 @@ use std::ptr::{null_mut, NonNull}; use crate::SPCommand; /// The raw packet -pub struct SPPacket(pub(crate) servicepoint::packet::Packet); +pub struct SPPacket(pub(crate) servicepoint::Packet); /// Turns a [SPCommand] into a [SPPacket]. /// The [SPCommand] gets consumed. @@ -59,7 +59,7 @@ pub unsafe extern "C" fn sp_packet_try_load( ) -> *mut SPPacket { assert!(!data.is_null()); let data = std::slice::from_raw_parts(data, length); - match servicepoint::packet::Packet::try_from(data) { + match servicepoint::Packet::try_from(data) { Err(_) => null_mut(), Ok(packet) => Box::into_raw(Box::new(SPPacket(packet))), } @@ -108,8 +108,8 @@ pub unsafe extern "C" fn sp_packet_from_parts( Vec::from(payload) }; - let packet = servicepoint::packet::Packet { - header: servicepoint::packet::Header { + let packet = servicepoint::Packet { + header: servicepoint::Header { command_code, a, b, diff --git a/crates/servicepoint_binding_uniffi/src/char_grid.rs b/crates/servicepoint_binding_uniffi/src/char_grid.rs index ad686b4..e5d59a8 100644 --- a/crates/servicepoint_binding_uniffi/src/char_grid.rs +++ b/crates/servicepoint_binding_uniffi/src/char_grid.rs @@ -1,5 +1,5 @@ use crate::cp437_grid::Cp437Grid; -use servicepoint::{Grid, primitive_grid::SeriesError}; +use servicepoint::{Grid, SetValueSeriesError}; use std::convert::Into; use std::sync::{Arc, RwLock}; @@ -107,7 +107,10 @@ impl CharGrid { .unwrap() .get_row(y as usize) .map(String::from_iter) - .ok_or(CharGridError::OutOfBounds {index: y, size: self.height()}) + .ok_or(CharGridError::OutOfBounds { + index: y, + size: self.height(), + }) } pub fn get_col(&self, x: u64) -> Result { @@ -116,11 +119,16 @@ impl CharGrid { .unwrap() .get_col(x as usize) .map(String::from_iter) - .ok_or(CharGridError::OutOfBounds {index: x, size: self.width()}) + .ok_or(CharGridError::OutOfBounds { + index: x, + size: self.width(), + }) } pub fn to_cp437(&self) -> Arc { - Cp437Grid::internal_new(servicepoint::Cp437Grid::from(&*self.actual.read().unwrap())) + Cp437Grid::internal_new(servicepoint::Cp437Grid::from( + &*self.actual.read().unwrap(), + )) } } @@ -133,9 +141,7 @@ impl CharGrid { fn str_to_char(value: String) -> Result { if value.len() != 1 { - return Err(CharGridError::StringNotOneChar { - value, - }); + return Err(CharGridError::StringNotOneChar { value }); } let value = value.chars().nth(0).unwrap(); @@ -143,16 +149,16 @@ impl CharGrid { } } -impl From for CharGridError { - fn from(e: SeriesError) -> Self { +impl From for CharGridError { + fn from(e: SetValueSeriesError) -> Self { match e { - SeriesError::OutOfBounds { index, size } => { + SetValueSeriesError::OutOfBounds { index, size } => { CharGridError::OutOfBounds { index: index as u64, size: size as u64, } } - SeriesError::InvalidLength { actual, expected } => { + SetValueSeriesError::InvalidLength { actual, expected } => { CharGridError::InvalidSeriesLength { actual: actual as u64, expected: expected as u64, diff --git a/crates/servicepoint_binding_uniffi/src/constants.rs b/crates/servicepoint_binding_uniffi/src/constants.rs index be89a9c..3d0cfcb 100644 --- a/crates/servicepoint_binding_uniffi/src/constants.rs +++ b/crates/servicepoint_binding_uniffi/src/constants.rs @@ -1,4 +1,6 @@ -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, uniffi::Record)] +#[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, uniffi::Record, +)] pub struct Constants { pub tile_size: u64, pub tile_width: u64, @@ -10,7 +12,7 @@ pub struct Constants { #[uniffi::export] fn get_constants() -> Constants { -Constants { + Constants { tile_size: servicepoint::TILE_SIZE as u64, tile_width: servicepoint::TILE_WIDTH as u64, tile_height: servicepoint::TILE_HEIGHT as u64, diff --git a/crates/servicepoint_binding_uniffi/src/cp437_grid.rs b/crates/servicepoint_binding_uniffi/src/cp437_grid.rs index 3819176..b201238 100644 --- a/crates/servicepoint_binding_uniffi/src/cp437_grid.rs +++ b/crates/servicepoint_binding_uniffi/src/cp437_grid.rs @@ -1,6 +1,6 @@ +use crate::char_grid::CharGrid; use servicepoint::{DataRef, Grid}; use std::sync::{Arc, RwLock}; -use crate::char_grid::CharGrid; #[derive(uniffi::Object)] pub struct Cp437Grid { @@ -72,6 +72,8 @@ impl Cp437Grid { } pub fn to_utf8(&self) -> Arc { - CharGrid::internal_new(servicepoint::CharGrid::from(&*self.actual.read().unwrap())) + CharGrid::internal_new(servicepoint::CharGrid::from( + &*self.actual.read().unwrap(), + )) } } diff --git a/crates/servicepoint_binding_uniffi/src/lib.rs b/crates/servicepoint_binding_uniffi/src/lib.rs index 1c932e6..2ecad45 100644 --- a/crates/servicepoint_binding_uniffi/src/lib.rs +++ b/crates/servicepoint_binding_uniffi/src/lib.rs @@ -7,6 +7,6 @@ mod char_grid; mod command; mod compression_code; mod connection; +mod constants; mod cp437_grid; mod errors; -mod constants; From dea176d0d92c0d49645a8f39e9ee28700ce1bc78 Mon Sep 17 00:00:00 2001 From: Vinzenz Schroeter Date: Sun, 12 Jan 2025 02:36:43 +0100 Subject: [PATCH 05/10] fix warnings, minor docs changes --- crates/servicepoint/src/bitmap.rs | 2 -- crates/servicepoint/src/bitvec.rs | 1 + crates/servicepoint/src/byte_grid.rs | 2 +- crates/servicepoint/src/char_grid.rs | 20 +++++++++++++--- crates/servicepoint/src/command.rs | 2 +- crates/servicepoint/src/lib.rs | 2 +- crates/servicepoint/src/value_grid.rs | 23 +++++++++++-------- .../src/brightness_grid.rs | 4 ++-- 8 files changed, 37 insertions(+), 19 deletions(-) diff --git a/crates/servicepoint/src/bitmap.rs b/crates/servicepoint/src/bitmap.rs index 76170b3..85983e5 100644 --- a/crates/servicepoint/src/bitmap.rs +++ b/crates/servicepoint/src/bitmap.rs @@ -1,5 +1,3 @@ -//! Implementation of [Bitmap] - use crate::data_ref::DataRef; use crate::BitVec; use crate::*; diff --git a/crates/servicepoint/src/bitvec.rs b/crates/servicepoint/src/bitvec.rs index 7006105..d512cf6 100644 --- a/crates/servicepoint/src/bitvec.rs +++ b/crates/servicepoint/src/bitvec.rs @@ -1,3 +1,4 @@ pub use bitvec::prelude::*; + /// An alias for the specific type of [bitvec::prelude::BitVec] used. pub type BitVec = bitvec::prelude::BitVec; diff --git a/crates/servicepoint/src/byte_grid.rs b/crates/servicepoint/src/byte_grid.rs index 6ebf882..b9a077a 100644 --- a/crates/servicepoint/src/byte_grid.rs +++ b/crates/servicepoint/src/byte_grid.rs @@ -1,2 +1,2 @@ -/// A simple grid of bytes - see [primitive_grid::PrimitiveGrid]. +/// A 2d grid of bytes - see [crate::ValueGrid]. pub type ByteGrid = crate::value_grid::ValueGrid; diff --git a/crates/servicepoint/src/char_grid.rs b/crates/servicepoint/src/char_grid.rs index 7d55cf3..717e70b 100644 --- a/crates/servicepoint/src/char_grid.rs +++ b/crates/servicepoint/src/char_grid.rs @@ -1,8 +1,22 @@ -use crate::value_grid::{SetValueSeriesError, TryLoadPrimitiveGridError, ValueGrid}; -use crate::Grid; +use crate::{Grid, SetValueSeriesError, TryLoadValueGridError, ValueGrid}; use std::string::FromUtf8Error; /// A grid containing UTF-8 characters. +/// +/// To send a CharGrid to the display, use [crate::Command::Utf8Data]. +/// +/// Also see [crate::ValueGrid] for the non-specialized operations and examples. +/// +/// # Examples +/// +/// ```rust +/// # use servicepoint::{CharGrid, Command, Connection, Origin}; +/// let grid = CharGrid::from("You can\nload multiline\nstrings directly"); +/// assert_eq!(grid.get_row_str(1), Some("load multiline\0\0".to_string())); +/// +/// # let connection = Connection::Fake; +/// let command = Command::Utf8Data(Origin::ZERO, grid); +/// ``` pub type CharGrid = ValueGrid; impl CharGrid { @@ -64,7 +78,7 @@ pub enum LoadUtf8Error { #[error(transparent)] FromUtf8Error(#[from] FromUtf8Error), #[error(transparent)] - TryLoadError(#[from] TryLoadPrimitiveGridError), + TryLoadError(#[from] TryLoadValueGridError), } impl From<&str> for CharGrid { diff --git a/crates/servicepoint/src/command.rs b/crates/servicepoint/src/command.rs index 0807c80..92ea8a9 100644 --- a/crates/servicepoint/src/command.rs +++ b/crates/servicepoint/src/command.rs @@ -522,7 +522,7 @@ impl Command { String::from_utf8(payload.clone())?.chars().collect(); Ok(Command::Utf8Data( Origin::new(*a as usize, *b as usize), - CharGrid::load(*c as usize, *d as usize, &*payload), + CharGrid::load(*c as usize, *d as usize, &payload), )) } } diff --git a/crates/servicepoint/src/lib.rs b/crates/servicepoint/src/lib.rs index 8cb04d2..74899ad 100644 --- a/crates/servicepoint/src/lib.rs +++ b/crates/servicepoint/src/lib.rs @@ -51,7 +51,7 @@ pub use crate::grid::Grid; pub use crate::origin::{Origin, Pixels, Tiles}; pub use crate::packet::{Header, Packet, Payload}; pub use crate::value_grid::{ - IterGridRows, SetValueSeriesError, TryLoadPrimitiveGridError, Value, ValueGrid, + IterGridRows, SetValueSeriesError, TryLoadValueGridError, Value, ValueGrid, }; mod bitmap; diff --git a/crates/servicepoint/src/value_grid.rs b/crates/servicepoint/src/value_grid.rs index b01067f..e6c92e8 100644 --- a/crates/servicepoint/src/value_grid.rs +++ b/crates/servicepoint/src/value_grid.rs @@ -1,5 +1,3 @@ -//! This module contains the implementation of the [ValueGrid]. - use std::fmt::Debug; use std::slice::{Iter, IterMut}; @@ -9,7 +7,12 @@ use crate::*; pub trait Value: Sized + Default + Copy + Clone + Debug {} impl Value for T {} -/// A 2D grid of bytes +/// A 2D grid of values. +/// +/// The memory layout is the one the display expects in [Command]s. +/// +/// This structure can be used with any type that implements the [Value] trait. +/// You can also use the concrete type aliases provided in this crate, e.g. [CharGrid] and [ByteGrid]. #[derive(Debug, Clone, PartialEq)] pub struct ValueGrid { width: usize, @@ -78,14 +81,14 @@ impl ValueGrid { /// Loads a [ValueGrid] with the specified dimensions from the provided data. /// - /// returns: [ValueGrid] that contains a copy of the provided data or [TryLoadPrimitiveGridError]. + /// returns: [ValueGrid] that contains a copy of the provided data or [TryLoadValueGridError]. pub fn try_load( width: usize, height: usize, data: Vec, - ) -> Result { + ) -> Result { if width * height != data.len() { - return Err(TryLoadPrimitiveGridError::InvalidDimensions); + return Err(TryLoadValueGridError::InvalidDimensions); } Ok(Self { @@ -159,7 +162,7 @@ impl ValueGrid { } } - /// Convert between PrimitiveGrid types. + /// Convert between ValueGrid types. /// /// See also [Iterator::map]. /// @@ -273,14 +276,16 @@ impl ValueGrid { } } +/// Errors that can occur when loading a grid #[derive(Debug, thiserror::Error)] -pub enum TryLoadPrimitiveGridError { +pub enum TryLoadValueGridError { #[error("The provided dimensions do not match with the data size")] + /// The provided dimensions do not match with the data size InvalidDimensions, } impl Grid for ValueGrid { - /// Sets the value of the cell at the specified position in the `PrimitiveGrid. + /// Sets the value of the cell at the specified position in the `ValueGrid. /// /// # Arguments /// diff --git a/crates/servicepoint_binding_c/src/brightness_grid.rs b/crates/servicepoint_binding_c/src/brightness_grid.rs index 93880a1..83af008 100644 --- a/crates/servicepoint_binding_c/src/brightness_grid.rs +++ b/crates/servicepoint_binding_c/src/brightness_grid.rs @@ -8,9 +8,9 @@ use std::convert::Into; use std::intrinsics::transmute; use std::ptr::NonNull; -/// see [Brightness::MIN] +/// see [servicepoint::Brightness::MIN] pub const SP_BRIGHTNESS_MIN: u8 = 0; -/// see [Brightness::MAX] +/// see [servicepoint::Brightness::MAX] pub const SP_BRIGHTNESS_MAX: u8 = 11; /// Count of possible brightness values pub const SP_BRIGHTNESS_LEVELS: u8 = 12; From 4f83aa3d5c777472cf50a13d656b412c8d8b0b3d Mon Sep 17 00:00:00 2001 From: Vinzenz Schroeter Date: Sun, 12 Jan 2025 12:01:40 +0100 Subject: [PATCH 06/10] group cp437 functions --- crates/servicepoint/src/cp437.rs | 105 +++++++++++++------------- crates/servicepoint/src/cp437_grid.rs | 9 +-- crates/servicepoint/src/lib.rs | 5 +- 3 files changed, 60 insertions(+), 59 deletions(-) diff --git a/crates/servicepoint/src/cp437.rs b/crates/servicepoint/src/cp437.rs index 1129c85..32a7396 100644 --- a/crates/servicepoint/src/cp437.rs +++ b/crates/servicepoint/src/cp437.rs @@ -1,57 +1,54 @@ -//! Conversion between UTF-8 and CP-437. -//! -//! Most of the functionality is only available with feature "cp437" enabled. - use std::collections::HashMap; -#[allow(unused)] // depends on features -pub use feature_cp437::*; +/// Contains functions to convert between UTF-8 and Codepage 437. +/// +/// See +pub struct Cp437Converter; -#[cfg(feature = "cp437")] -mod feature_cp437 { - use super::*; - - /// An array of 256 elements, mapping most of the CP437 values to UTF-8 characters - /// - /// Mostly follows CP437, except 0x0A, which is kept for use as line ending. - /// - /// See - /// - /// Mostly copied from . License: GPL-3.0 - #[rustfmt::skip] - pub const CP437_TO_UTF8: [char; 256] = [ - /* 0X */ '\0', '☺', '☻', '♥', '♦', '♣', '♠', '•', '◘', '○', '\n', '♂', '♀', '♪', '♫', '☼', - /* 1X */ '►', '◄', '↕', '‼', '¶', '§', '▬', '↨', '↑', '↓', '→', '←', '∟', '↔', '▲', '▼', - /* 2X */ ' ', '!', '"', '#', '$', '%', '&', '\'','(', ')', '*', '+', ',', '-', '.', '/', - /* 3X */ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', - /* 4X */ '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', - /* 5X */ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\',']', '^', '_', - /* 6X */ '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', - /* 7X */ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', '⌂', - /* 8X */ 'Ç', 'ü', 'é', 'â', 'ä', 'à', 'å', 'ç', 'ê', 'ë', 'è', 'ï', 'î', 'ì', 'Ä', 'Å', - /* 9X */ 'É', 'æ', 'Æ', 'ô', 'ö', 'ò', 'û', 'ù', 'ÿ', 'Ö', 'Ü', '¢', '£', '¥', '₧', 'ƒ', - /* AX */ 'á', 'í', 'ó', 'ú', 'ñ', 'Ñ', 'ª', 'º', '¿', '⌐', '¬', '½', '¼', '¡', '«', '»', - /* BX */ '░', '▒', '▓', '│', '┤', '╡', '╢', '╖', '╕', '╣', '║', '╗', '╝', '╜', '╛', '┐', - /* CX */ '└', '┴', '┬', '├', '─', '┼', '╞', '╟', '╚', '╔', '╩', '╦', '╠', '═', '╬', '╧', - /* DX */ '╨', '╤', '╥', '╙', '╘', '╒', '╓', '╫', '╪', '┘', '┌', '█', '▄', '▌', '▐', '▀', - /* EX */ 'α', 'ß', 'Γ', 'π', 'Σ', 'σ', 'µ', 'τ', 'Φ', 'Θ', 'Ω', 'δ', '∞', 'φ', 'ε', '∩', - /* FX */ '≡', '±', '≥', '≤', '⌠', '⌡', '÷', '≈', '°', '∙', '·', '√', 'ⁿ', '²', '■', ' ', - ]; - - static UTF8_TO_CP437: once_cell::sync::Lazy> = - once_cell::sync::Lazy::new(|| { - let pairs = CP437_TO_UTF8 - .iter() - .enumerate() - .map(move |(index, char)| (*char, index as u8)); - HashMap::from_iter(pairs) - }); +/// An array of 256 elements, mapping most of the CP437 values to UTF-8 characters +/// +/// Mostly follows CP437, except 0x0A, which is kept for use as line ending. +/// +/// See +/// +/// Mostly copied from . License: GPL-3.0 +#[rustfmt::skip] +const CP437_TO_UTF8: [char; 256] = [ + /* 0X */ '\0', '☺', '☻', '♥', '♦', '♣', '♠', '•', '◘', '○', '\n', '♂', '♀', '♪', '♫', '☼', + /* 1X */ '►', '◄', '↕', '‼', '¶', '§', '▬', '↨', '↑', '↓', '→', '←', '∟', '↔', '▲', '▼', + /* 2X */ ' ', '!', '"', '#', '$', '%', '&', '\'','(', ')', '*', '+', ',', '-', '.', '/', + /* 3X */ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', + /* 4X */ '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + /* 5X */ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\',']', '^', '_', + /* 6X */ '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + /* 7X */ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', '⌂', + /* 8X */ 'Ç', 'ü', 'é', 'â', 'ä', 'à', 'å', 'ç', 'ê', 'ë', 'è', 'ï', 'î', 'ì', 'Ä', 'Å', + /* 9X */ 'É', 'æ', 'Æ', 'ô', 'ö', 'ò', 'û', 'ù', 'ÿ', 'Ö', 'Ü', '¢', '£', '¥', '₧', 'ƒ', + /* AX */ 'á', 'í', 'ó', 'ú', 'ñ', 'Ñ', 'ª', 'º', '¿', '⌐', '¬', '½', '¼', '¡', '«', '»', + /* BX */ '░', '▒', '▓', '│', '┤', '╡', '╢', '╖', '╕', '╣', '║', '╗', '╝', '╜', '╛', '┐', + /* CX */ '└', '┴', '┬', '├', '─', '┼', '╞', '╟', '╚', '╔', '╩', '╦', '╠', '═', '╬', '╧', + /* DX */ '╨', '╤', '╥', '╙', '╘', '╒', '╓', '╫', '╪', '┘', '┌', '█', '▄', '▌', '▐', '▀', + /* EX */ 'α', 'ß', 'Γ', 'π', 'Σ', 'σ', 'µ', 'τ', 'Φ', 'Θ', 'Ω', 'δ', '∞', 'φ', 'ε', '∩', + /* FX */ '≡', '±', '≥', '≤', '⌠', '⌡', '÷', '≈', '°', '∙', '·', '√', 'ⁿ', '²', '■', ' ', +]; +static UTF8_TO_CP437: once_cell::sync::Lazy> = + once_cell::sync::Lazy::new(|| { + let pairs = CP437_TO_UTF8 + .iter() + .enumerate() + .map(move |(index, char)| (*char, index as u8)); + HashMap::from_iter(pairs) + }); +impl Cp437Converter { const MISSING_CHAR_CP437: u8 = 0x3F; // '?' /// Convert the provided bytes to UTF-8. pub fn cp437_to_str(cp437: &[u8]) -> String { - cp437.iter().map(move |char| cp437_to_char(*char)).collect() + cp437 + .iter() + .map(move |char| Self::cp437_to_char(*char)) + .collect() } /// Convert a single CP-437 character to UTF-8. @@ -63,17 +60,18 @@ mod feature_cp437 { /// /// Characters that are not available are mapped to '?'. pub fn str_to_cp437(utf8: &str) -> Vec { - utf8.chars().map(char_to_cp437).collect() + utf8.chars().map(Self::char_to_cp437).collect() } /// Convert a single UTF-8 character to CP-437. pub fn char_to_cp437(utf8: char) -> u8 { - *UTF8_TO_CP437.get(&utf8).unwrap_or(&MISSING_CHAR_CP437) + *UTF8_TO_CP437 + .get(&utf8) + .unwrap_or(&Self::MISSING_CHAR_CP437) } } #[cfg(test)] -#[cfg(feature = "cp437")] mod tests_feature_cp437 { use super::*; @@ -102,13 +100,16 @@ mod tests_feature_cp437 { │dx ≡ Σ √x²ⁿ·δx ⌡"#; - let cp437 = str_to_cp437(utf8); - let actual = cp437_to_str(&*cp437); + let cp437 = Cp437Converter::str_to_cp437(utf8); + let actual = Cp437Converter::cp437_to_str(&*cp437); assert_eq!(utf8, actual) } #[test] fn convert_invalid() { - assert_eq!(cp437_to_char(char_to_cp437('😜')), '?'); + assert_eq!( + Cp437Converter::cp437_to_char(Cp437Converter::char_to_cp437('😜')), + '?' + ); } } diff --git a/crates/servicepoint/src/cp437_grid.rs b/crates/servicepoint/src/cp437_grid.rs index a19e014..22b3293 100644 --- a/crates/servicepoint/src/cp437_grid.rs +++ b/crates/servicepoint/src/cp437_grid.rs @@ -83,20 +83,17 @@ pub use feature_cp437::*; #[cfg(feature = "cp437")] mod feature_cp437 { use super::*; - use crate::{ - cp437::{char_to_cp437, cp437_to_char}, - CharGrid, - }; + use crate::{CharGrid, Cp437Converter}; impl From<&Cp437Grid> for CharGrid { fn from(value: &Cp437Grid) -> Self { - value.map(cp437_to_char) + value.map(Cp437Converter::cp437_to_char) } } impl From<&CharGrid> for Cp437Grid { fn from(value: &CharGrid) -> Self { - value.map(char_to_cp437) + value.map(Cp437Converter::char_to_cp437) } } diff --git a/crates/servicepoint/src/lib.rs b/crates/servicepoint/src/lib.rs index 74899ad..3b5150d 100644 --- a/crates/servicepoint/src/lib.rs +++ b/crates/servicepoint/src/lib.rs @@ -45,6 +45,7 @@ pub use crate::command::{Command, Offset}; pub use crate::compression_code::CompressionCode; pub use crate::connection::Connection; pub use crate::constants::*; +pub use crate::cp437::Cp437Converter; pub use crate::cp437_grid::Cp437Grid; pub use crate::data_ref::DataRef; pub use crate::grid::Grid; @@ -66,7 +67,6 @@ mod compression; mod compression_code; mod connection; mod constants; -pub mod cp437; mod cp437_grid; mod data_ref; mod grid; @@ -74,6 +74,9 @@ mod origin; mod packet; mod value_grid; +#[cfg(feature = "cp437")] +mod cp437; + // include README.md in doctest #[doc = include_str!("../README.md")] #[cfg(doctest)] From 2f2160f246db1bffe93f06ad7294908756827401 Mon Sep 17 00:00:00 2001 From: Vinzenz Schroeter Date: Sun, 12 Jan 2025 13:07:24 +0100 Subject: [PATCH 07/10] expose CharGrid to C API --- crates/servicepoint/src/bit_vec.rs | 10 + crates/servicepoint/src/bitmap.rs | 53 +++- crates/servicepoint/src/bitvec.rs | 4 - crates/servicepoint/src/byte_grid.rs | 6 +- crates/servicepoint/src/lib.rs | 32 +- .../examples/lang_c/include/servicepoint.h | 289 +++++++++++++++++- .../servicepoint_binding_c/src/char_grid.rs | 263 ++++++++++++++++ crates/servicepoint_binding_c/src/command.rs | 39 ++- crates/servicepoint_binding_c/src/lib.rs | 2 + 9 files changed, 676 insertions(+), 22 deletions(-) create mode 100644 crates/servicepoint/src/bit_vec.rs delete mode 100644 crates/servicepoint/src/bitvec.rs create mode 100644 crates/servicepoint_binding_c/src/char_grid.rs diff --git a/crates/servicepoint/src/bit_vec.rs b/crates/servicepoint/src/bit_vec.rs new file mode 100644 index 0000000..2ece813 --- /dev/null +++ b/crates/servicepoint/src/bit_vec.rs @@ -0,0 +1,10 @@ +/// A byte-packed vector of booleans. +/// +/// The implementation is provided by [bitvec]. +/// This is an alias for the specific type of [bitvec::BitVec] used in this crate. +pub type BitVec = bitvec::BitVec; + +pub mod bitvec { + //! Re-export of the used library [mod@bitvec]. + pub use bitvec::prelude::*; +} diff --git a/crates/servicepoint/src/bitmap.rs b/crates/servicepoint/src/bitmap.rs index 85983e5..807e2ad 100644 --- a/crates/servicepoint/src/bitmap.rs +++ b/crates/servicepoint/src/bitmap.rs @@ -5,7 +5,18 @@ use ::bitvec::order::Msb0; use ::bitvec::prelude::BitSlice; use ::bitvec::slice::IterMut; -/// A grid of pixels stored in packed bytes. +/// A fixed-size 2D grid of booleans. +/// +/// The values are stored in packed bytes (8 values per byte) in the same order as used by the display for storing pixels. +/// This means that no conversion is necessary for sending the data to the display. +/// +/// # Examples +/// +/// ```rust +/// use servicepoint::Bitmap; +/// let mut bitmap = Bitmap::new(4, 2); +/// +/// ``` #[derive(Debug, Clone, PartialEq, Eq)] pub struct Bitmap { width: usize, @@ -27,7 +38,11 @@ impl Bitmap { /// /// - when the width is not dividable by 8 pub fn new(width: usize, height: usize) -> Self { - assert_eq!(width % 8, 0); + assert_eq!( + width % 8, + 0, + "width must be a multiple of 8, but is {width}" + ); Self { width, height, @@ -182,6 +197,26 @@ impl From for BitVec { } } +impl From<&ValueGrid> for Bitmap { + fn from(value: &ValueGrid) -> Self { + let mut result = Self::new(value.width(), value.height()); + for (mut to, from) in result.iter_mut().zip(value.iter()) { + *to = *from; + } + result + } +} + +impl From<&Bitmap> for ValueGrid { + fn from(value: &Bitmap) -> Self { + let mut result = Self::new(value.width(), value.height()); + for (to, from) in result.iter_mut().zip(value.iter()) { + *to = *from; + } + result + } +} + pub struct IterRows<'t> { bitmap: &'t Bitmap, row: usize, @@ -204,7 +239,7 @@ impl<'t> Iterator for IterRows<'t> { #[cfg(test)] mod tests { - use crate::{BitVec, Bitmap, DataRef, Grid}; + use crate::{BitVec, Bitmap, DataRef, Grid, ValueGrid}; #[test] fn fill() { @@ -304,4 +339,16 @@ mod tests { let bitvec: BitVec = grid.into(); assert_eq!(bitvec.as_raw_slice(), [0x80, 0x00]); } + + #[test] + fn from_bool_grid() { + let original = ValueGrid::load( + 8, + 1, + &[true, false, true, false, true, false, true, false], + ); + let converted = Bitmap::from(&original); + let reconverted = ValueGrid::from(&converted); + assert_eq!(original, reconverted); + } } diff --git a/crates/servicepoint/src/bitvec.rs b/crates/servicepoint/src/bitvec.rs deleted file mode 100644 index d512cf6..0000000 --- a/crates/servicepoint/src/bitvec.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub use bitvec::prelude::*; - -/// An alias for the specific type of [bitvec::prelude::BitVec] used. -pub type BitVec = bitvec::prelude::BitVec; diff --git a/crates/servicepoint/src/byte_grid.rs b/crates/servicepoint/src/byte_grid.rs index b9a077a..0a7fdae 100644 --- a/crates/servicepoint/src/byte_grid.rs +++ b/crates/servicepoint/src/byte_grid.rs @@ -1,2 +1,4 @@ -/// A 2d grid of bytes - see [crate::ValueGrid]. -pub type ByteGrid = crate::value_grid::ValueGrid; +use crate::ValueGrid; + +/// A 2d grid of bytes - see [ValueGrid]. +pub type ByteGrid = ValueGrid; diff --git a/crates/servicepoint/src/lib.rs b/crates/servicepoint/src/lib.rs index 3b5150d..790751a 100644 --- a/crates/servicepoint/src/lib.rs +++ b/crates/servicepoint/src/lib.rs @@ -2,14 +2,17 @@ //! //! Your starting point is a [Connection] to the display. //! With a connection, you can send [Command]s. -//! When received, the display will update the state of the pixels. +//! When received, the display will update the state of its pixels. //! //! # Examples //! -//! ```rust -//! use servicepoint::{Command, CompressionCode, Grid, Bitmap}; +//! ### Clear display //! -//! let connection = servicepoint::Connection::open("127.0.0.1:2342") +//! ```rust +//! use servicepoint::{Connection, Command}; +//! +//! // establish a connection +//! let connection = Connection::open("127.0.0.1:2342") //! .expect("connection failed"); //! //! // turn off all pixels on display @@ -17,6 +20,8 @@ //! .expect("send failed"); //! ``` //! +//! ### Set all pixels to on +//! //! ```rust //! # use servicepoint::{Command, CompressionCode, Grid, Bitmap}; //! # let connection = servicepoint::Connection::open("127.0.0.1:2342").expect("connection failed"); @@ -34,9 +39,24 @@ //! // send command to display //! connection.send(command).expect("send failed"); //! ``` +//! +//! ### Send text +//! +//! ```rust +//! # use servicepoint::{Command, CompressionCode, Grid, Bitmap, CharGrid}; +//! # let connection = servicepoint::Connection::open("127.0.0.1:2342").expect("connection failed"); +//! // create a text grid +//! let mut grid = CharGrid::from("Hello\nCCCB?"); +//! // modify the grid +//! grid.set(grid.width() - 1, 1, '!'); +//! // create the command to send the data +//! let command = Command::Utf8Data(servicepoint::Origin::ZERO, grid); +//! // send command to display +//! connection.send(command).expect("send failed"); +//! ``` +pub use crate::bit_vec::{bitvec, BitVec}; pub use crate::bitmap::Bitmap; -pub use crate::bitvec::BitVec; pub use crate::brightness::Brightness; pub use crate::brightness_grid::BrightnessGrid; pub use crate::byte_grid::ByteGrid; @@ -55,8 +75,8 @@ pub use crate::value_grid::{ IterGridRows, SetValueSeriesError, TryLoadValueGridError, Value, ValueGrid, }; +mod bit_vec; mod bitmap; -mod bitvec; mod brightness; mod brightness_grid; mod byte_grid; diff --git a/crates/servicepoint_binding_c/examples/lang_c/include/servicepoint.h b/crates/servicepoint_binding_c/examples/lang_c/include/servicepoint.h index ca57468..d9cbe57 100644 --- a/crates/servicepoint_binding_c/examples/lang_c/include/servicepoint.h +++ b/crates/servicepoint_binding_c/examples/lang_c/include/servicepoint.h @@ -14,12 +14,12 @@ #define SP_BRIGHTNESS_LEVELS 12 /** - * see [Brightness::MAX] + * see [servicepoint::Brightness::MAX] */ #define SP_BRIGHTNESS_MAX 11 /** - * see [Brightness::MIN] + * see [servicepoint::Brightness::MIN] */ #define SP_BRIGHTNESS_MIN 0 @@ -131,6 +131,25 @@ typedef struct SPBitmap SPBitmap; */ typedef struct SPBrightnessGrid SPBrightnessGrid; +/** + * A C-wrapper for grid containing UTF-8 characters. + * + * As the rust [char] type is not FFI-safe, characters are passed in their UTF-32 form as 32bit unsigned integers. + * + * The encoding is enforced in most cases by the rust standard library + * and will panic when provided with illegal characters. + * + * # Examples + * + * ```C + * CharGrid grid = sp_char_grid_new(4, 3); + * sp_char_grid_fill(grid, '?'); + * sp_char_grid_set(grid, 0, 0, '!'); + * sp_char_grid_free(grid); + * ``` + */ +typedef struct SPCharGrid SPCharGrid; + /** * A low-level display command. * @@ -367,6 +386,20 @@ SPBitmap *sp_bitmap_load(size_t width, SPBitmap *sp_bitmap_new(size_t width, size_t height); +/** + * Creates a new [SPBitmap] with a size matching the screen. + * + * returns: [SPBitmap] initialized to all pixels off. Will never return NULL. + * + * # Safety + * + * The caller has to make sure that: + * + * - the returned instance is freed in some way, either by using a consuming function or + * by explicitly calling [sp_bitmap_free]. + */ +SPBitmap *sp_bitmap_new_screen_sized(void); + /** * Sets the value of the specified position in the [SPBitmap]. * @@ -865,6 +898,196 @@ SPByteSlice sp_brightness_grid_unsafe_data_ref(SPBrightnessGrid *brightness_grid */ size_t sp_brightness_grid_width(const SPBrightnessGrid *brightness_grid); +/** + * Clones a [SPCharGrid]. + * + * Will never return NULL. + * + * # Panics + * + * - when `char_grid` is NULL + * + * # Safety + * + * The caller has to make sure that: + * + * - `char_grid` points to a valid [SPCharGrid] + * - `char_grid` is not written to concurrently + * - the returned instance is freed in some way, either by using a consuming function or + * by explicitly calling `sp_char_grid_free`. + */ +SPCharGrid *sp_char_grid_clone(const SPCharGrid *char_grid); + +/** + * Sets the value of all cells in the [SPCharGrid]. + * + * # Arguments + * + * - `char_grid`: instance to write to + * - `value`: the value to set all cells to + * + * # Panics + * + * - when `char_grid` is NULL + * + * # Safety + * + * The caller has to make sure that: + * + * - `char_grid` points to a valid [SPCharGrid] + * - `char_grid` is not written to or read from concurrently + */ +void sp_char_grid_fill(SPCharGrid *char_grid, uint32_t value); + +/** + * Deallocates a [SPCharGrid]. + * + * # Panics + * + * - when `char_grid` is NULL + * + * # Safety + * + * The caller has to make sure that: + * + * - `char_grid` points to a valid [SPCharGrid] + * - `char_grid` is not used concurrently or after char_grid call + * - `char_grid` was not passed to another consuming function, e.g. to create a [SPCommand] + * + * [SPCommand]: [crate::SPCommand] + */ +void sp_char_grid_free(SPCharGrid *char_grid); + +/** + * Gets the current value at the specified position. + * + * # Arguments + * + * - `char_grid`: instance to read from + * - `x` and `y`: position of the cell to read + * + * # Panics + * + * - when `char_grid` is NULL + * - when accessing `x` or `y` out of bounds + * + * # Safety + * + * The caller has to make sure that: + * + * - `char_grid` points to a valid [SPCharGrid] + * - `char_grid` is not written to concurrently + */ +uint32_t sp_char_grid_get(const SPCharGrid *char_grid, size_t x, size_t y); + +/** + * Gets the height of the [SPCharGrid] instance. + * + * # Arguments + * + * - `char_grid`: instance to read from + * + * # Panics + * + * - when `char_grid` is NULL + * + * # Safety + * + * The caller has to make sure that: + * + * - `char_grid` points to a valid [SPCharGrid] + */ +size_t sp_char_grid_height(const SPCharGrid *char_grid); + +/** + * Loads a [SPCharGrid] with the specified dimensions from the provided data. + * + * Will never return NULL. + * + * # Panics + * + * - when `data` is NULL + * - when the provided `data_length` does not match `height` and `width` + * - when `data` is not valid UTF-8 + * + * # Safety + * + * The caller has to make sure that: + * + * - `data` points to a valid memory location of at least `data_length` + * bytes in size. + * - the returned instance is freed in some way, either by using a consuming function or + * by explicitly calling `sp_char_grid_free`. + */ +SPCharGrid *sp_char_grid_load(size_t width, + size_t height, + const uint8_t *data, + size_t data_length); + +/** + * Creates a new [SPCharGrid] with the specified dimensions. + * + * returns: [SPCharGrid] initialized to 0. Will never return NULL. + * + * # Safety + * + * The caller has to make sure that: + * + * - the returned instance is freed in some way, either by using a consuming function or + * by explicitly calling `sp_char_grid_free`. + */ +SPCharGrid *sp_char_grid_new(size_t width, + size_t height); + +/** + * Sets the value of the specified position in the [SPCharGrid]. + * + * # Arguments + * + * - `char_grid`: instance to write to + * - `x` and `y`: position of the cell + * - `value`: the value to write to the cell + * + * returns: old value of the cell + * + * # Panics + * + * - when `char_grid` is NULL + * - when accessing `x` or `y` out of bounds + * + * # Safety + * + * The caller has to make sure that: + * + * - `char_grid` points to a valid [SPBitVec] + * - `char_grid` is not written to or read from concurrently + * + * [SPBitVec]: [crate::SPBitVec] + */ +void sp_char_grid_set(SPCharGrid *char_grid, + size_t x, + size_t y, + uint32_t value); + +/** + * Gets the width of the [SPCharGrid] instance. + * + * # Arguments + * + * - `char_grid`: instance to read from + * + * # Panics + * + * - when `char_grid` is NULL + * + * # Safety + * + * The caller has to make sure that: + * + * - `char_grid` points to a valid [SPCharGrid] + */ +size_t sp_char_grid_width(const SPCharGrid *char_grid); + /** * Set pixel data starting at the pixel offset on screen. * @@ -1101,7 +1324,7 @@ SPCommand *sp_command_clear(void); SPCommand *sp_command_clone(const SPCommand *command); /** - * Show text on the screen. + * Show codepage 437 encoded text on the screen. * * The passed [SPCp437Grid] gets consumed. * @@ -1201,6 +1424,30 @@ SPCommand *sp_command_hard_reset(void); */ SPCommand *sp_command_try_from_packet(SPPacket *packet); +/** + * Show UTF-8 encoded text on the screen. + * + * The passed [SPCharGrid] gets consumed. + * + * Returns: a new [servicepoint::Command::Utf8Data] instance. Will never return NULL. + * + * # Panics + * + * - when `grid` is null + * + * # Safety + * + * The caller has to make sure that: + * + * - `grid` points to a valid instance of [SPCharGrid] + * - `grid` is not used concurrently or after this call + * - the returned [SPCommand] instance is freed in some way, either by using a consuming function or + * by explicitly calling `sp_command_free`. + */ +SPCommand *sp_command_utf8_data(size_t x, + size_t y, + SPCharGrid *grid); + /** * Creates a new instance of [SPConnection] for testing that does not actually send anything. * @@ -1528,7 +1775,7 @@ SPPacket *sp_packet_clone(const SPPacket *packet); * * # Panics * - * - when `sp_packet_free` is NULL + * - when `packet` is NULL * * # Safety * @@ -1560,6 +1807,40 @@ void sp_packet_free(SPPacket *packet); */ SPPacket *sp_packet_from_command(SPCommand *command); +/** + * Creates a raw [SPPacket] from parts. + * + * # Arguments + * + * - `command_code` specifies which command this packet contains + * - `a`, `b`, `c` and `d` are command-specific header values + * - `payload` is the optional data that is part of the command + * - `payload_len` is the size of the payload + * + * returns: new instance. Will never return null. + * + * # Panics + * + * - when `payload` is null, but `payload_len` is not zero + * - when `payload_len` is zero, but `payload` is nonnull + * + * # Safety + * + * The caller has to make sure that: + * + * - `payload` points to a valid memory region of at least `payload_len` bytes + * - `payload` is not written to concurrently + * - the returned [SPPacket] instance is freed in some way, either by using a consuming function or + * by explicitly calling [sp_packet_free]. + */ +SPPacket *sp_packet_from_parts(uint16_t command_code, + uint16_t a, + uint16_t b, + uint16_t c, + uint16_t d, + const uint8_t *payload, + size_t payload_len); + /** * Tries to load a [SPPacket] from the passed array with the specified length. * diff --git a/crates/servicepoint_binding_c/src/char_grid.rs b/crates/servicepoint_binding_c/src/char_grid.rs new file mode 100644 index 0000000..dfaf225 --- /dev/null +++ b/crates/servicepoint_binding_c/src/char_grid.rs @@ -0,0 +1,263 @@ +//! C functions for interacting with [SPCharGrid]s +//! +//! prefix `sp_char_grid_` + +use servicepoint::Grid; +use std::ptr::NonNull; + +/// A C-wrapper for grid containing UTF-8 characters. +/// +/// As the rust [char] type is not FFI-safe, characters are passed in their UTF-32 form as 32bit unsigned integers. +/// +/// The encoding is enforced in most cases by the rust standard library +/// and will panic when provided with illegal characters. +/// +/// # Examples +/// +/// ```C +/// CharGrid grid = sp_char_grid_new(4, 3); +/// sp_char_grid_fill(grid, '?'); +/// sp_char_grid_set(grid, 0, 0, '!'); +/// sp_char_grid_free(grid); +/// ``` +pub struct SPCharGrid(pub(crate) servicepoint::CharGrid); + +impl Clone for SPCharGrid { + fn clone(&self) -> Self { + SPCharGrid(self.0.clone()) + } +} + +/// Creates a new [SPCharGrid] with the specified dimensions. +/// +/// returns: [SPCharGrid] initialized to 0. Will never return NULL. +/// +/// # Safety +/// +/// The caller has to make sure that: +/// +/// - the returned instance is freed in some way, either by using a consuming function or +/// by explicitly calling `sp_char_grid_free`. +#[no_mangle] +pub unsafe extern "C" fn sp_char_grid_new( + width: usize, + height: usize, +) -> NonNull { + let result = + Box::new(SPCharGrid(servicepoint::CharGrid::new(width, height))); + NonNull::from(Box::leak(result)) +} + +/// Loads a [SPCharGrid] with the specified dimensions from the provided data. +/// +/// Will never return NULL. +/// +/// # Panics +/// +/// - when `data` is NULL +/// - when the provided `data_length` does not match `height` and `width` +/// - when `data` is not valid UTF-8 +/// +/// # Safety +/// +/// The caller has to make sure that: +/// +/// - `data` points to a valid memory location of at least `data_length` +/// bytes in size. +/// - the returned instance is freed in some way, either by using a consuming function or +/// by explicitly calling `sp_char_grid_free`. +#[no_mangle] +pub unsafe extern "C" fn sp_char_grid_load( + width: usize, + height: usize, + data: *const u8, + data_length: usize, +) -> NonNull { + assert!(data.is_null()); + let data = std::slice::from_raw_parts(data, data_length); + let result = Box::new(SPCharGrid( + servicepoint::CharGrid::load_utf8(width, height, data.to_vec()) + .unwrap(), + )); + NonNull::from(Box::leak(result)) +} + +/// Clones a [SPCharGrid]. +/// +/// Will never return NULL. +/// +/// # Panics +/// +/// - when `char_grid` is NULL +/// +/// # Safety +/// +/// The caller has to make sure that: +/// +/// - `char_grid` points to a valid [SPCharGrid] +/// - `char_grid` is not written to concurrently +/// - the returned instance is freed in some way, either by using a consuming function or +/// by explicitly calling `sp_char_grid_free`. +#[no_mangle] +pub unsafe extern "C" fn sp_char_grid_clone( + char_grid: *const SPCharGrid, +) -> NonNull { + assert!(!char_grid.is_null()); + let result = Box::new((*char_grid).clone()); + NonNull::from(Box::leak(result)) +} + +/// Deallocates a [SPCharGrid]. +/// +/// # Panics +/// +/// - when `char_grid` is NULL +/// +/// # Safety +/// +/// The caller has to make sure that: +/// +/// - `char_grid` points to a valid [SPCharGrid] +/// - `char_grid` is not used concurrently or after char_grid call +/// - `char_grid` was not passed to another consuming function, e.g. to create a [SPCommand] +/// +/// [SPCommand]: [crate::SPCommand] +#[no_mangle] +pub unsafe extern "C" fn sp_char_grid_free(char_grid: *mut SPCharGrid) { + assert!(!char_grid.is_null()); + _ = Box::from_raw(char_grid); +} + +/// Gets the current value at the specified position. +/// +/// # Arguments +/// +/// - `char_grid`: instance to read from +/// - `x` and `y`: position of the cell to read +/// +/// # Panics +/// +/// - when `char_grid` is NULL +/// - when accessing `x` or `y` out of bounds +/// +/// # Safety +/// +/// The caller has to make sure that: +/// +/// - `char_grid` points to a valid [SPCharGrid] +/// - `char_grid` is not written to concurrently +#[no_mangle] +pub unsafe extern "C" fn sp_char_grid_get( + char_grid: *const SPCharGrid, + x: usize, + y: usize, +) -> u32 { + assert!(!char_grid.is_null()); + (*char_grid).0.get(x, y) as u32 +} + +/// Sets the value of the specified position in the [SPCharGrid]. +/// +/// # Arguments +/// +/// - `char_grid`: instance to write to +/// - `x` and `y`: position of the cell +/// - `value`: the value to write to the cell +/// +/// returns: old value of the cell +/// +/// # Panics +/// +/// - when `char_grid` is NULL +/// - when accessing `x` or `y` out of bounds +/// +/// # Safety +/// +/// The caller has to make sure that: +/// +/// - `char_grid` points to a valid [SPBitVec] +/// - `char_grid` is not written to or read from concurrently +/// +/// [SPBitVec]: [crate::SPBitVec] +#[no_mangle] +pub unsafe extern "C" fn sp_char_grid_set( + char_grid: *mut SPCharGrid, + x: usize, + y: usize, + value: u32, +) { + assert!(!char_grid.is_null()); + (*char_grid).0.set(x, y, char::from_u32(value).unwrap()); +} + +/// Sets the value of all cells in the [SPCharGrid]. +/// +/// # Arguments +/// +/// - `char_grid`: instance to write to +/// - `value`: the value to set all cells to +/// +/// # Panics +/// +/// - when `char_grid` is NULL +/// +/// # Safety +/// +/// The caller has to make sure that: +/// +/// - `char_grid` points to a valid [SPCharGrid] +/// - `char_grid` is not written to or read from concurrently +#[no_mangle] +pub unsafe extern "C" fn sp_char_grid_fill( + char_grid: *mut SPCharGrid, + value: u32, +) { + assert!(!char_grid.is_null()); + (*char_grid).0.fill(char::from_u32(value).unwrap()); +} + +/// Gets the width of the [SPCharGrid] instance. +/// +/// # Arguments +/// +/// - `char_grid`: instance to read from +/// +/// # Panics +/// +/// - when `char_grid` is NULL +/// +/// # Safety +/// +/// The caller has to make sure that: +/// +/// - `char_grid` points to a valid [SPCharGrid] +#[no_mangle] +pub unsafe extern "C" fn sp_char_grid_width( + char_grid: *const SPCharGrid, +) -> usize { + assert!(!char_grid.is_null()); + (*char_grid).0.width() +} + +/// Gets the height of the [SPCharGrid] instance. +/// +/// # Arguments +/// +/// - `char_grid`: instance to read from +/// +/// # Panics +/// +/// - when `char_grid` is NULL +/// +/// # Safety +/// +/// The caller has to make sure that: +/// +/// - `char_grid` points to a valid [SPCharGrid] +#[no_mangle] +pub unsafe extern "C" fn sp_char_grid_height( + char_grid: *const SPCharGrid, +) -> usize { + assert!(!char_grid.is_null()); + (*char_grid).0.height() +} diff --git a/crates/servicepoint_binding_c/src/command.rs b/crates/servicepoint_binding_c/src/command.rs index 3da0cbb..f7e50ea 100644 --- a/crates/servicepoint_binding_c/src/command.rs +++ b/crates/servicepoint_binding_c/src/command.rs @@ -5,8 +5,8 @@ use std::ptr::{null_mut, NonNull}; use crate::{ - SPBitVec, SPBitmap, SPBrightnessGrid, SPCompressionCode, SPCp437Grid, - SPPacket, + SPBitVec, SPBitmap, SPBrightnessGrid, SPCharGrid, SPCompressionCode, + SPCp437Grid, SPPacket, }; /// A low-level display command. @@ -366,7 +366,7 @@ pub unsafe extern "C" fn sp_command_bitmap_linear_xor( NonNull::from(Box::leak(result)) } -/// Show text on the screen. +/// Show codepage 437 encoded text on the screen. /// /// The passed [SPCp437Grid] gets consumed. /// @@ -399,6 +399,39 @@ pub unsafe extern "C" fn sp_command_cp437_data( NonNull::from(Box::leak(result)) } +/// Show UTF-8 encoded text on the screen. +/// +/// The passed [SPCharGrid] gets consumed. +/// +/// Returns: a new [servicepoint::Command::Utf8Data] instance. Will never return NULL. +/// +/// # Panics +/// +/// - when `grid` is null +/// +/// # Safety +/// +/// The caller has to make sure that: +/// +/// - `grid` points to a valid instance of [SPCharGrid] +/// - `grid` is not used concurrently or after this call +/// - the returned [SPCommand] instance is freed in some way, either by using a consuming function or +/// by explicitly calling `sp_command_free`. +#[no_mangle] +pub unsafe extern "C" fn sp_command_utf8_data( + x: usize, + y: usize, + grid: *mut SPCharGrid, +) -> NonNull { + assert!(!grid.is_null()); + let grid = *Box::from_raw(grid); + let result = Box::new(SPCommand(servicepoint::Command::Utf8Data( + servicepoint::Origin::new(x, y), + grid.0, + ))); + NonNull::from(Box::leak(result)) +} + /// Sets a window of pixels to the specified values. /// /// The passed [SPBitmap] gets consumed. diff --git a/crates/servicepoint_binding_c/src/lib.rs b/crates/servicepoint_binding_c/src/lib.rs index 42442f5..887fb40 100644 --- a/crates/servicepoint_binding_c/src/lib.rs +++ b/crates/servicepoint_binding_c/src/lib.rs @@ -29,6 +29,7 @@ pub use crate::bitmap::*; pub use crate::bitvec::*; pub use crate::brightness_grid::*; pub use crate::byte_slice::*; +pub use crate::char_grid::*; pub use crate::command::*; pub use crate::connection::*; pub use crate::constants::*; @@ -39,6 +40,7 @@ mod bitmap; mod bitvec; mod brightness_grid; mod byte_slice; +mod char_grid; mod command; mod connection; mod constants; From 2a6005fff962a7c6b42dff821954415298bfe586 Mon Sep 17 00:00:00 2001 From: Vinzenz Schroeter Date: Sun, 12 Jan 2025 13:12:02 +0100 Subject: [PATCH 08/10] expose new command to uniffi API --- .../servicepoint_binding_uniffi.cs | 21 +++++++++++++++++++ .../ruby/lib/servicepoint_binding_uniffi.rb | 15 +++++++++++++ .../src/command.rs | 13 ++++++++++++ 3 files changed, 49 insertions(+) diff --git a/crates/servicepoint_binding_uniffi/libraries/csharp/ServicePoint/servicepoint_binding_uniffi.cs b/crates/servicepoint_binding_uniffi/libraries/csharp/ServicePoint/servicepoint_binding_uniffi.cs index a9077dd..2039307 100644 --- a/crates/servicepoint_binding_uniffi/libraries/csharp/ServicePoint/servicepoint_binding_uniffi.cs +++ b/crates/servicepoint_binding_uniffi/libraries/csharp/ServicePoint/servicepoint_binding_uniffi.cs @@ -681,6 +681,10 @@ static class _UniFFILib { public static extern CommandSafeHandle uniffi_servicepoint_binding_uniffi_fn_constructor_command_hard_reset(ref RustCallStatus _uniffi_out_err ); + [DllImport("servicepoint_binding_uniffi")] + public static extern CommandSafeHandle uniffi_servicepoint_binding_uniffi_fn_constructor_command_utf8_data(ulong @offsetX,ulong @offsetY,CharGridSafeHandle @grid,ref RustCallStatus _uniffi_out_err + ); + [DllImport("servicepoint_binding_uniffi")] public static extern sbyte uniffi_servicepoint_binding_uniffi_fn_method_command_equals(CommandSafeHandle @ptr,CommandSafeHandle @other,ref RustCallStatus _uniffi_out_err ); @@ -1255,6 +1259,10 @@ static class _UniFFILib { public static extern ushort uniffi_servicepoint_binding_uniffi_checksum_constructor_command_hard_reset( ); + [DllImport("servicepoint_binding_uniffi")] + public static extern ushort uniffi_servicepoint_binding_uniffi_checksum_constructor_command_utf8_data( + ); + [DllImport("servicepoint_binding_uniffi")] public static extern ushort uniffi_servicepoint_binding_uniffi_checksum_constructor_connection_new( ); @@ -1697,6 +1705,12 @@ static class _UniFFILib { throw new UniffiContractChecksumException($"ServicePoint: uniffi bindings expected function `uniffi_servicepoint_binding_uniffi_checksum_constructor_command_hard_reset` checksum `62130`, library returned `{checksum}`"); } } + { + var checksum = _UniFFILib.uniffi_servicepoint_binding_uniffi_checksum_constructor_command_utf8_data(); + if (checksum != 2263) { + throw new UniffiContractChecksumException($"ServicePoint: uniffi bindings expected function `uniffi_servicepoint_binding_uniffi_checksum_constructor_command_utf8_data` checksum `2263`, library returned `{checksum}`"); + } + } { var checksum = _UniFFILib.uniffi_servicepoint_binding_uniffi_checksum_constructor_connection_new(); if (checksum != 30445) { @@ -2671,6 +2685,13 @@ public class Command: FFIObject, ICommand { )); } + public static Command Utf8Data(ulong @offsetX, ulong @offsetY, CharGrid @grid) { + return new Command( + _UniffiHelpers.RustCall( (ref RustCallStatus _status) => + _UniFFILib.uniffi_servicepoint_binding_uniffi_fn_constructor_command_utf8_data(FfiConverterUInt64.INSTANCE.Lower(@offsetX), FfiConverterUInt64.INSTANCE.Lower(@offsetY), FfiConverterTypeCharGrid.INSTANCE.Lower(@grid), ref _status) +)); + } + } diff --git a/crates/servicepoint_binding_uniffi/libraries/ruby/lib/servicepoint_binding_uniffi.rb b/crates/servicepoint_binding_uniffi/libraries/ruby/lib/servicepoint_binding_uniffi.rb index 1e7aac7..d097798 100644 --- a/crates/servicepoint_binding_uniffi/libraries/ruby/lib/servicepoint_binding_uniffi.rb +++ b/crates/servicepoint_binding_uniffi/libraries/ruby/lib/servicepoint_binding_uniffi.rb @@ -918,6 +918,9 @@ module UniFFILib attach_function :uniffi_servicepoint_binding_uniffi_fn_constructor_command_hard_reset, [RustCallStatus.by_ref], :pointer + attach_function :uniffi_servicepoint_binding_uniffi_fn_constructor_command_utf8_data, + [:uint64, :uint64, :pointer, RustCallStatus.by_ref], + :pointer attach_function :uniffi_servicepoint_binding_uniffi_fn_method_command_equals, [:pointer, :pointer, RustCallStatus.by_ref], :int8 @@ -1188,6 +1191,9 @@ module UniFFILib attach_function :uniffi_servicepoint_binding_uniffi_checksum_constructor_command_hard_reset, [RustCallStatus.by_ref], :uint16 + attach_function :uniffi_servicepoint_binding_uniffi_checksum_constructor_command_utf8_data, + [RustCallStatus.by_ref], + :uint16 attach_function :uniffi_servicepoint_binding_uniffi_checksum_constructor_connection_new, [RustCallStatus.by_ref], :uint16 @@ -1817,6 +1823,15 @@ end # and just create a new instance with the required pointer. return _uniffi_allocate(ServicepointBindingUniffi.rust_call(:uniffi_servicepoint_binding_uniffi_fn_constructor_command_hard_reset,)) end + def self.utf8_data(offset_x, offset_y, grid) + offset_x = ServicepointBindingUniffi::uniffi_in_range(offset_x, "u64", 0, 2**64) + offset_y = ServicepointBindingUniffi::uniffi_in_range(offset_y, "u64", 0, 2**64) + grid = grid + # Call the (fallible) function before creating any half-baked object instances. + # Lightly yucky way to bypass the usual "initialize" logic + # and just create a new instance with the required pointer. + return _uniffi_allocate(ServicepointBindingUniffi.rust_call(:uniffi_servicepoint_binding_uniffi_fn_constructor_command_utf8_data,offset_x,offset_y,(CharGrid._uniffi_lower grid))) + end def equals(other) diff --git a/crates/servicepoint_binding_uniffi/src/command.rs b/crates/servicepoint_binding_uniffi/src/command.rs index b9b399b..bb479ae 100644 --- a/crates/servicepoint_binding_uniffi/src/command.rs +++ b/crates/servicepoint_binding_uniffi/src/command.rs @@ -1,6 +1,7 @@ use crate::bitmap::Bitmap; use crate::bitvec::BitVec; use crate::brightness_grid::BrightnessGrid; +use crate::char_grid::CharGrid; use crate::compression_code::CompressionCode; use crate::cp437_grid::Cp437Grid; use crate::errors::ServicePointError; @@ -151,6 +152,18 @@ impl Command { Self::internal_new(actual) } + #[uniffi::constructor] + pub fn utf8_data( + offset_x: u64, + offset_y: u64, + grid: &Arc, + ) -> Arc { + let origin = Origin::new(offset_x as usize, offset_y as usize); + let grid = grid.actual.read().unwrap().clone(); + let actual = servicepoint::Command::Utf8Data(origin, grid); + Self::internal_new(actual) + } + #[uniffi::constructor] pub fn clone(other: &Arc) -> Arc { Self::internal_new(other.actual.clone()) From 04cda144ed5f320611bbac521170a2def3f52b3a Mon Sep 17 00:00:00 2001 From: Vinzenz Schroeter Date: Sun, 12 Jan 2025 13:50:33 +0100 Subject: [PATCH 09/10] add ValueGrid::wrap and CharGrid::wrap_str, more examples add examples --- crates/servicepoint/examples/announce.rs | 16 +-- crates/servicepoint/src/bitmap.rs | 10 +- crates/servicepoint/src/char_grid.rs | 111 +++++++++++++++++- crates/servicepoint/src/cp437_grid.rs | 12 +- crates/servicepoint/src/value_grid.rs | 32 ++++- .../examples/lang_c/src/main.c | 5 +- 6 files changed, 160 insertions(+), 26 deletions(-) diff --git a/crates/servicepoint/examples/announce.rs b/crates/servicepoint/examples/announce.rs index 4159384..05d2b19 100644 --- a/crates/servicepoint/examples/announce.rs +++ b/crates/servicepoint/examples/announce.rs @@ -40,20 +40,8 @@ fn main() { .expect("sending clear failed"); } - let text = cli - .text - .iter() - .flat_map(move |x| { - x.chars() - .collect::>() - .chunks(TILE_WIDTH) - .map(|c| String::from_iter(c)) - .collect::>() - }) - .collect::>() - .join("\n"); - - let grid = CharGrid::from(text); + let text = cli.text.join("\n"); + let grid = CharGrid::wrap_str(TILE_WIDTH, &text); connection .send(Command::Utf8Data(Origin::ZERO, grid)) .expect("sending text failed"); diff --git a/crates/servicepoint/src/bitmap.rs b/crates/servicepoint/src/bitmap.rs index 807e2ad..079410f 100644 --- a/crates/servicepoint/src/bitmap.rs +++ b/crates/servicepoint/src/bitmap.rs @@ -9,12 +9,13 @@ use ::bitvec::slice::IterMut; /// /// The values are stored in packed bytes (8 values per byte) in the same order as used by the display for storing pixels. /// This means that no conversion is necessary for sending the data to the display. +/// The downside is that the width has to be a multiple of 8. /// /// # Examples /// /// ```rust /// use servicepoint::Bitmap; -/// let mut bitmap = Bitmap::new(4, 2); +/// let mut bitmap = Bitmap::new(8, 2); /// /// ``` #[derive(Debug, Clone, PartialEq, Eq)] @@ -192,12 +193,18 @@ impl From for Vec { } impl From for BitVec { + /// Turns a [Bitmap] into the underlying [BitVec]. fn from(value: Bitmap) -> Self { value.bit_vec } } impl From<&ValueGrid> for Bitmap { + /// Converts a grid of [bool]s into a [Bitmap]. + /// + /// # Panics + /// + /// - when the width of `value` is not dividable by 8 fn from(value: &ValueGrid) -> Self { let mut result = Self::new(value.width(), value.height()); for (mut to, from) in result.iter_mut().zip(value.iter()) { @@ -208,6 +215,7 @@ impl From<&ValueGrid> for Bitmap { } impl From<&Bitmap> for ValueGrid { + /// Converts a [Bitmap] into a grid of [bool]s. fn from(value: &Bitmap) -> Self { let mut result = Self::new(value.width(), value.height()); for (to, from) in result.iter_mut().zip(value.iter()) { diff --git a/crates/servicepoint/src/char_grid.rs b/crates/servicepoint/src/char_grid.rs index 717e70b..d1a3fd7 100644 --- a/crates/servicepoint/src/char_grid.rs +++ b/crates/servicepoint/src/char_grid.rs @@ -3,9 +3,9 @@ use std::string::FromUtf8Error; /// A grid containing UTF-8 characters. /// -/// To send a CharGrid to the display, use [crate::Command::Utf8Data]. +/// To send a CharGrid to the display, use [Command::Utf8Data](crate::Command::Utf8Data). /// -/// Also see [crate::ValueGrid] for the non-specialized operations and examples. +/// Also see [ValueGrid] for the non-specialized operations and examples. /// /// # Examples /// @@ -20,9 +20,52 @@ use std::string::FromUtf8Error; pub type CharGrid = ValueGrid; impl CharGrid { + /// Loads a [CharGrid] with the specified width from the provided text, wrapping to as many rows as needed. + /// + /// The passed rows are extended with '\0' if needed. + /// + /// returns: [CharGrid] that contains a copy of the provided data. + /// + /// # Examples + /// + /// ``` + /// # use servicepoint::CharGrid; + /// let grid = CharGrid::wrap_str(2, "abc\ndef"); + /// ``` + pub fn wrap_str(width: usize, text: &str) -> Self { + let lines = text + .split('\n') + .flat_map(move |x| { + x.chars() + .collect::>() + .chunks(width) + .map(|c| { + let mut s = String::from_iter(c); + s.push_str(&"\0".repeat(width - s.chars().count())); + s + }) + .collect::>() + }) + .collect::>(); + let height = lines.len(); + let mut result = Self::new(width, height); + for (row, text_line) in lines.iter().enumerate() { + result.set_row_str(row, text_line).unwrap() + } + result + } + /// Copies a column from the grid as a String. /// /// Returns [None] if x is out of bounds. + /// + /// # Examples + /// + /// ``` + /// # use servicepoint::CharGrid; + /// let grid = CharGrid::from("ab\ncd"); + /// let col = grid.get_col_str(0).unwrap(); // "ac" + /// ``` pub fn get_col_str(&self, x: usize) -> Option { Some(String::from_iter(self.get_col(x)?)) } @@ -30,6 +73,14 @@ impl CharGrid { /// Copies a row from the grid as a String. /// /// Returns [None] if y is out of bounds. + /// + /// # Examples + /// + /// ``` + /// # use servicepoint::CharGrid; + /// let grid = CharGrid::from("ab\ncd"); + /// let row = grid.get_row_str(0).unwrap(); // "ab" + /// ``` pub fn get_row_str(&self, y: usize) -> Option { Some(String::from_iter(self.get_row(y)?)) } @@ -37,6 +88,14 @@ impl CharGrid { /// Overwrites a row in the grid with a str. /// /// Returns [SetValueSeriesError] if y is out of bounds or `row` is not of the correct size. + /// + /// # Examples + /// + /// ``` + /// # use servicepoint::CharGrid; + /// let mut grid = CharGrid::from("ab\ncd"); + /// grid.set_row_str(0, "ef").unwrap(); + /// ``` pub fn set_row_str( &mut self, y: usize, @@ -48,6 +107,14 @@ impl CharGrid { /// Overwrites a column in the grid with a str. /// /// Returns [SetValueSeriesError] if y is out of bounds or `row` is not of the correct size. + /// + /// # Examples + /// + /// ``` + /// # use servicepoint::CharGrid; + /// let mut grid = CharGrid::from("ab\ncd"); + /// grid.set_col_str(0, "ef").unwrap(); + /// ``` pub fn set_col_str( &mut self, x: usize, @@ -60,9 +127,12 @@ impl CharGrid { /// /// returns: [CharGrid] that contains the provided data, or [FromUtf8Error] if the data is invalid. /// - /// # Panics + /// # Examples /// - /// - when the dimensions and data size do not match exactly. + /// ``` + /// # use servicepoint::CharGrid; + /// let grid = CharGrid::load_utf8(2, 2, [97u8, 98, 99, 100].to_vec()); + /// ``` pub fn load_utf8( width: usize, height: usize, @@ -117,6 +187,18 @@ impl From for String { } impl From<&CharGrid> for String { + /// Converts a [CharGrid] into a [String]. + /// + /// Rows are separated by '\n'. + /// + /// # Examples + /// + /// ```rust + /// # use servicepoint::CharGrid; + /// let grid = CharGrid::from("ab\ncd"); + /// let string = String::from(grid); + /// let grid = CharGrid::from(string); + /// ``` fn from(value: &CharGrid) -> Self { value .iter_rows() @@ -127,12 +209,26 @@ impl From<&CharGrid> for String { } impl From<&CharGrid> for Vec { + /// Converts a [CharGrid] into a [`Vec`]. + /// + /// Rows are not separated. + /// + /// # Examples + /// + /// ```rust + /// # use servicepoint::{CharGrid, Grid}; + /// let grid = CharGrid::from("ab\ncd"); + /// let height = grid.height(); + /// let width = grid.width(); + /// let grid = CharGrid::load_utf8(width, height, grid.into()); + /// ``` fn from(value: &CharGrid) -> Self { String::from_iter(value.iter()).into_bytes() } } impl From for Vec { + /// See [`From<&CharGrid>::from`]. fn from(value: CharGrid) -> Self { Self::from(&value) } @@ -192,4 +288,11 @@ mod test { let copy = CharGrid::from(str); assert_eq!(grid, copy); } + + #[test] + fn wrap_str() { + let grid = CharGrid::wrap_str(2, "abc\ndef"); + assert_eq!(4, grid.height()); + assert_eq!("ab\nc\0\nde\nf\0", String::from(grid)); + } } diff --git a/crates/servicepoint/src/cp437_grid.rs b/crates/servicepoint/src/cp437_grid.rs index 22b3293..99506bc 100644 --- a/crates/servicepoint/src/cp437_grid.rs +++ b/crates/servicepoint/src/cp437_grid.rs @@ -90,6 +90,12 @@ mod feature_cp437 { value.map(Cp437Converter::cp437_to_char) } } + + impl From for CharGrid { + fn from(value: Cp437Grid) -> Self { + Self::from(&value) + } + } impl From<&CharGrid> for Cp437Grid { fn from(value: &CharGrid) -> Self { @@ -99,7 +105,7 @@ mod feature_cp437 { impl From for Cp437Grid { fn from(value: CharGrid) -> Self { - Cp437Grid::from(&value) + Self::from(&value) } } } @@ -150,8 +156,8 @@ mod tests_feature_cp437 { #[test] fn round_trip_cp437() { let utf8 = CharGrid::load(2, 2, &['Ä', 'x', '\n', '$']); - let cp437 = Cp437Grid::from(&utf8); - let actual = CharGrid::from(&cp437); + let cp437 = Cp437Grid::from(utf8.clone()); + let actual = CharGrid::from(cp437); assert_eq!(actual, utf8); } } diff --git a/crates/servicepoint/src/value_grid.rs b/crates/servicepoint/src/value_grid.rs index e6c92e8..a3400ae 100644 --- a/crates/servicepoint/src/value_grid.rs +++ b/crates/servicepoint/src/value_grid.rs @@ -79,6 +79,27 @@ impl ValueGrid { } } + /// Loads a [ValueGrid] with the specified width from the provided data, wrapping to as many rows as needed. + /// + /// returns: [ValueGrid] that contains a copy of the provided data or [TryLoadValueGridError]. + /// + /// # Examples + /// + /// ``` + /// # use servicepoint::ValueGrid; + /// let grid = ValueGrid::wrap(2, &[0, 1, 2, 3, 4, 5]).unwrap(); + /// ``` + pub fn wrap( + width: usize, + data: &[T], + ) -> Result { + let len = data.len(); + if len % width != 0 { + return Err(TryLoadValueGridError::InvalidDimensions); + } + Ok(Self::load(width, len / width, data)) + } + /// Loads a [ValueGrid] with the specified dimensions from the provided data. /// /// returns: [ValueGrid] that contains a copy of the provided data or [TryLoadValueGridError]. @@ -277,7 +298,7 @@ impl ValueGrid { } /// Errors that can occur when loading a grid -#[derive(Debug, thiserror::Error)] +#[derive(Debug, thiserror::Error, PartialEq)] pub enum TryLoadValueGridError { #[error("The provided dimensions do not match with the data size")] /// The provided dimensions do not match with the data size @@ -537,4 +558,13 @@ mod tests { }) ); } + + #[test] + fn wrap() { + let grid = ValueGrid::wrap(2, &[0, 1, 2, 3, 4, 5]).unwrap(); + assert_eq!(grid.height(), 3); + + let grid = ValueGrid::wrap(4, &[0, 1, 2, 3, 4, 5]); + assert_eq!(grid.err(), Some(TryLoadValueGridError::InvalidDimensions)); + } } diff --git a/crates/servicepoint_binding_c/examples/lang_c/src/main.c b/crates/servicepoint_binding_c/examples/lang_c/src/main.c index e84e510..1454804 100644 --- a/crates/servicepoint_binding_c/examples/lang_c/src/main.c +++ b/crates/servicepoint_binding_c/examples/lang_c/src/main.c @@ -2,7 +2,7 @@ #include "servicepoint.h" int main(void) { - SPConnection *connection = sp_connection_open("172.23.42.29:2342"); + SPConnection *connection = sp_connection_open("localhost:2342"); if (connection == NULL) return 1; @@ -10,9 +10,8 @@ int main(void) { sp_bitmap_fill(pixels, true); SPCommand *command = sp_command_bitmap_linear_win(0, 0, pixels, SP_COMPRESSION_CODE_UNCOMPRESSED); - while (sp_connection_send_command(connection, sp_command_clone(command))); + sp_connection_send_command(connection, command); - sp_command_free(command); sp_connection_free(connection); return 0; } From 7e1fb6cc99e2281832fa24c03f83abf107fa4f78 Mon Sep 17 00:00:00 2001 From: Vinzenz Schroeter Date: Thu, 16 Jan 2025 20:51:56 +0100 Subject: [PATCH 10/10] version 0.13.0 --- Cargo.lock | 6 +++--- Cargo.toml | 2 +- crates/servicepoint/README.md | 9 ++++++--- crates/servicepoint_binding_c/Cargo.toml | 2 +- crates/servicepoint_binding_uniffi/Cargo.toml | 2 +- .../libraries/csharp/ServicePoint/ServicePoint.csproj | 2 +- .../libraries/ruby/servicepoint.gemspec | 2 +- 7 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 788e680..7016c4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -931,7 +931,7 @@ dependencies = [ [[package]] name = "servicepoint" -version = "0.12.0" +version = "0.13.0" dependencies = [ "bitvec", "bzip2", @@ -948,7 +948,7 @@ dependencies = [ [[package]] name = "servicepoint_binding_c" -version = "0.12.0" +version = "0.13.0" dependencies = [ "cbindgen", "servicepoint", @@ -956,7 +956,7 @@ dependencies = [ [[package]] name = "servicepoint_binding_uniffi" -version = "0.12.0" +version = "0.13.0" dependencies = [ "servicepoint", "thiserror 2.0.11", diff --git a/Cargo.toml b/Cargo.toml index 1b5a109..e1bb85f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ ] [workspace.package] -version = "0.12.0" +version = "0.13.0" [workspace.lints.rust] missing-docs = "warn" diff --git a/crates/servicepoint/README.md b/crates/servicepoint/README.md index e4bae8a..fe4e24e 100644 --- a/crates/servicepoint/README.md +++ b/crates/servicepoint/README.md @@ -17,7 +17,7 @@ cargo add servicepoint or ```toml [dependencies] -servicepoint = "0.12.0" +servicepoint = "0.13.0" ``` ## Examples @@ -39,8 +39,11 @@ Execute `cargo run --example` for a list of available examples and `cargo run -- ## Note on stability -This library is still in early development. -You can absolutely use it, and it works, but expect minor breaking changes with every version bump. +This library can be used for creative project or just to play around with the display. +A decent coverage by unit tests prevents major problems and I also test this with my own projects, which mostly use up-to-date versions. + +That being said, the API is still being worked on. +Expect minor breaking changes with every version bump. Please specify the full version including patch in your Cargo.toml until 1.0 is released. ## Features diff --git a/crates/servicepoint_binding_c/Cargo.toml b/crates/servicepoint_binding_c/Cargo.toml index 0a4d2d5..27acabc 100644 --- a/crates/servicepoint_binding_c/Cargo.toml +++ b/crates/servicepoint_binding_c/Cargo.toml @@ -17,7 +17,7 @@ crate-type = ["staticlib", "cdylib", "rlib"] cbindgen = "0.27.0" [dependencies.servicepoint] -version = "0.12.0" +version = "0.13.0" path = "../servicepoint" features = ["all_compressions"] diff --git a/crates/servicepoint_binding_uniffi/Cargo.toml b/crates/servicepoint_binding_uniffi/Cargo.toml index 0365d19..3c52369 100644 --- a/crates/servicepoint_binding_uniffi/Cargo.toml +++ b/crates/servicepoint_binding_uniffi/Cargo.toml @@ -20,7 +20,7 @@ uniffi = { version = "0.25.3" } thiserror.workspace = true [dependencies.servicepoint] -version = "0.12.0" +version = "0.13.0" path = "../servicepoint" features = ["all_compressions"] diff --git a/crates/servicepoint_binding_uniffi/libraries/csharp/ServicePoint/ServicePoint.csproj b/crates/servicepoint_binding_uniffi/libraries/csharp/ServicePoint/ServicePoint.csproj index 135a26f..26e1dd7 100644 --- a/crates/servicepoint_binding_uniffi/libraries/csharp/ServicePoint/ServicePoint.csproj +++ b/crates/servicepoint_binding_uniffi/libraries/csharp/ServicePoint/ServicePoint.csproj @@ -9,7 +9,7 @@ ServicePoint - 0.12.0 + 0.13.0 Repository Authors None ServicePoint diff --git a/crates/servicepoint_binding_uniffi/libraries/ruby/servicepoint.gemspec b/crates/servicepoint_binding_uniffi/libraries/ruby/servicepoint.gemspec index 477a9ff..97129de 100644 --- a/crates/servicepoint_binding_uniffi/libraries/ruby/servicepoint.gemspec +++ b/crates/servicepoint_binding_uniffi/libraries/ruby/servicepoint.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |s| s.name = "servicepoint" - s.version = "0.12.0" + s.version = "0.13.0" s.summary = "" s.description = "" s.authors = ["kaesaecracker"]