Compare commits

..

11 commits

Author SHA1 Message Date
Vinzenz Schroeter b6fa1b161e Merge branch 'utf8'
Some checks failed
Rust / build (push) Has been cancelled
2025-01-16 21:33:01 +01:00
Vinzenz Schroeter 7e1fb6cc99 version 0.13.0 2025-01-16 20:51:56 +01:00
Vinzenz Schroeter 04cda144ed add ValueGrid::wrap and CharGrid::wrap_str, more examples
add examples
2025-01-12 15:22:54 +01:00
Vinzenz Schroeter 2a6005fff9 expose new command to uniffi API 2025-01-12 15:22:54 +01:00
Vinzenz Schroeter 2f2160f246 expose CharGrid to C API 2025-01-12 15:22:54 +01:00
Vinzenz Schroeter 4f83aa3d5c group cp437 functions 2025-01-12 15:22:54 +01:00
Vinzenz Schroeter dea176d0d9 fix warnings, minor docs changes 2025-01-12 15:22:54 +01:00
Vinzenz Schroeter 8320ee2d80 restructure api 2025-01-12 15:22:54 +01:00
Vinzenz Schroeter efaa52faa1 first CMD_UTF8_DATA implementation
UTF8 now works
2025-01-12 15:22:54 +01:00
Vinzenz Schroeter 38316169e9 update dependencies and flake
update dependencies
2025-01-12 15:22:54 +01:00
Vinzenz Schroeter 24e0eaaf07 sp_packet_from_parts, sp_bitmap_new_screen_sized 2024-11-23 23:47:41 +01:00
52 changed files with 1927 additions and 789 deletions

View file

@ -17,6 +17,9 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: install lzma
run: sudo apt-get update && sudo apt-get install -y liblzma-dev
- name: build default features - name: build default features
run: cargo build --all --verbose run: cargo build --all --verbose
- name: build default features -- examples - name: build default features -- examples

1
.gitignore vendored
View file

@ -4,3 +4,4 @@ out
.direnv .direnv
.envrc .envrc
result result
mutants.*

274
Cargo.lock generated
View file

@ -44,7 +44,7 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [ dependencies = [
"windows-sys 0.59.0", "windows-sys",
] ]
[[package]] [[package]]
@ -54,14 +54,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"windows-sys 0.59.0", "windows-sys",
] ]
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.93" version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
[[package]] [[package]]
name = "askama" name = "askama"
@ -108,7 +108,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"serde", "serde",
"syn 2.0.87", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -186,9 +186,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.6.0" version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be"
[[package]] [[package]]
name = "bitvec" name = "bitvec"
@ -219,15 +219,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.8.0" version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
[[package]] [[package]]
name = "bzip2" name = "bzip2"
version = "0.4.4" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" checksum = "bafdbf26611df8c14810e268ddceda071c297570a5fb360ceddf617fe417ef58"
dependencies = [ dependencies = [
"bzip2-sys", "bzip2-sys",
"libc", "libc",
@ -255,9 +255,9 @@ dependencies = [
[[package]] [[package]]
name = "cargo-platform" name = "cargo-platform"
version = "0.1.8" version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@ -273,7 +273,7 @@ dependencies = [
"semver", "semver",
"serde", "serde",
"serde_json", "serde_json",
"thiserror", "thiserror 1.0.69",
] ]
[[package]] [[package]]
@ -282,24 +282,24 @@ version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fce8dd7fcfcbf3a0a87d8f515194b49d6135acab73e18bd380d1d93bb1a15eb" checksum = "3fce8dd7fcfcbf3a0a87d8f515194b49d6135acab73e18bd380d1d93bb1a15eb"
dependencies = [ dependencies = [
"clap 4.5.20", "clap 4.5.26",
"heck 0.4.1", "heck 0.4.1",
"indexmap 2.6.0", "indexmap 2.7.0",
"log", "log",
"proc-macro2", "proc-macro2",
"quote", "quote",
"serde", "serde",
"serde_json", "serde_json",
"syn 2.0.87", "syn 2.0.96",
"tempfile", "tempfile",
"toml 0.8.19", "toml 0.8.19",
] ]
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.0" version = "1.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aeb932158bd710538c73702db6945cb68a8fb08c519e6e12706b94263b36db8" checksum = "c8293772165d9345bdaaa39b45b2109591e63fe5e6fbc23c6ff930a048aa310b"
dependencies = [ dependencies = [
"jobserver", "jobserver",
"libc", "libc",
@ -331,23 +331,23 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.20" version = "4.5.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive 4.5.18", "clap_derive 4.5.24",
] ]
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.20" version = "4.5.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
"clap_lex 0.7.2", "clap_lex 0.7.4",
"strsim 0.11.1", "strsim 0.11.1",
] ]
@ -366,14 +366,14 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.18" version = "4.5.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c"
dependencies = [ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.87", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -387,9 +387,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "0.7.2" version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]] [[package]]
name = "colorchoice" name = "colorchoice"
@ -399,9 +399,9 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.15" version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@ -449,12 +449,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.3.9" version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.52.0", "windows-sys",
] ]
[[package]] [[package]]
@ -465,20 +465,20 @@ checksum = "311a6d2f1f9d60bff73d2c78a0af97ed27f79672f15c238192a5bbb64db56d00"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.87", "syn 2.0.96",
] ]
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "2.2.0" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.0.34" version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
dependencies = [ dependencies = [
"crc32fast", "crc32fast",
"miniz_oxide", "miniz_oxide",
@ -528,9 +528,9 @@ dependencies = [
[[package]] [[package]]
name = "glob" name = "glob"
version = "0.3.1" version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
[[package]] [[package]]
name = "goblin" name = "goblin"
@ -551,9 +551,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.1" version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
[[package]] [[package]]
name = "heck" name = "heck"
@ -578,9 +578,9 @@ dependencies = [
[[package]] [[package]]
name = "http" name = "http"
version = "1.1.0" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea"
dependencies = [ dependencies = [
"bytes", "bytes",
"fnv", "fnv",
@ -605,12 +605,12 @@ dependencies = [
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.6.0" version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.15.1", "hashbrown 0.15.2",
] ]
[[package]] [[package]]
@ -621,9 +621,9 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.11" version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]] [[package]]
name = "jobserver" name = "jobserver"
@ -644,15 +644,15 @@ dependencies = [
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.162" version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.4.14" version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]] [[package]]
name = "log" name = "log"
@ -690,9 +690,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.8.0" version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394"
dependencies = [ dependencies = [
"adler2", "adler2",
] ]
@ -778,18 +778,18 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.89" version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.37" version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -842,15 +842,15 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.40" version = "0.38.43"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6"
dependencies = [ dependencies = [
"bitflags 2.6.0", "bitflags 2.7.0",
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
"windows-sys 0.52.0", "windows-sys",
] ]
[[package]] [[package]]
@ -876,43 +876,43 @@ checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.87", "syn 2.0.96",
] ]
[[package]] [[package]]
name = "semver" name = "semver"
version = "1.0.23" version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba"
dependencies = [ dependencies = [
"serde", "serde",
] ]
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.215" version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.215" version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.87", "syn 2.0.96",
] ]
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.132" version = "1.0.135"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@ -931,24 +931,24 @@ dependencies = [
[[package]] [[package]]
name = "servicepoint" name = "servicepoint"
version = "0.12.0" version = "0.13.0"
dependencies = [ dependencies = [
"bitvec", "bitvec",
"bzip2", "bzip2",
"clap 4.5.20", "clap 4.5.26",
"flate2", "flate2",
"log", "log",
"once_cell", "once_cell",
"rand", "rand",
"rust-lzma", "rust-lzma",
"thiserror", "thiserror 2.0.11",
"tungstenite", "tungstenite",
"zstd", "zstd",
] ]
[[package]] [[package]]
name = "servicepoint_binding_c" name = "servicepoint_binding_c"
version = "0.12.0" version = "0.13.0"
dependencies = [ dependencies = [
"cbindgen", "cbindgen",
"servicepoint", "servicepoint",
@ -956,10 +956,10 @@ dependencies = [
[[package]] [[package]]
name = "servicepoint_binding_uniffi" name = "servicepoint_binding_uniffi"
version = "0.12.0" version = "0.13.0"
dependencies = [ dependencies = [
"servicepoint", "servicepoint",
"thiserror", "thiserror 2.0.11",
"uniffi", "uniffi",
"uniffi-bindgen-cs", "uniffi-bindgen-cs",
"uniffi-bindgen-go", "uniffi-bindgen-go",
@ -1025,9 +1025,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.87" version = "2.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1042,15 +1042,16 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.14.0" version = "3.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand", "fastrand",
"getrandom",
"once_cell", "once_cell",
"rustix", "rustix",
"windows-sys 0.59.0", "windows-sys",
] ]
[[package]] [[package]]
@ -1079,7 +1080,16 @@ version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [ 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]] [[package]]
@ -1090,7 +1100,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "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]] [[package]]
@ -1129,7 +1150,7 @@ version = "0.22.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
dependencies = [ dependencies = [
"indexmap 2.6.0", "indexmap 2.7.0",
"serde", "serde",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime",
@ -1138,9 +1159,9 @@ dependencies = [
[[package]] [[package]]
name = "tungstenite" name = "tungstenite"
version = "0.24.0" version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" checksum = "413083a99c579593656008130e29255e54dcaae495be556cc26888f211648c24"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"bytes", "bytes",
@ -1150,7 +1171,7 @@ dependencies = [
"log", "log",
"rand", "rand",
"sha1", "sha1",
"thiserror", "thiserror 2.0.11",
"utf-8", "utf-8",
] ]
@ -1162,15 +1183,15 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]] [[package]]
name = "unicase" name = "unicase"
version = "2.8.0" version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.13" version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]] [[package]]
name = "unicode-linebreak" name = "unicode-linebreak"
@ -1218,8 +1239,8 @@ dependencies = [
[[package]] [[package]]
name = "uniffi-bindgen-go" name = "uniffi-bindgen-go"
version = "0.2.1+v0.25.0" version = "0.2.2+v0.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 = [ dependencies = [
"anyhow", "anyhow",
"askama 0.12.1", "askama 0.12.1",
@ -1234,15 +1255,15 @@ dependencies = [
"serde_json", "serde_json",
"textwrap", "textwrap",
"toml 0.5.11", "toml 0.5.11",
"uniffi_bindgen 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=a77dc0462dc18d53846c758155ab4e0a42e5b240)", "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=a77dc0462dc18d53846c758155ab4e0a42e5b240)", "uniffi_udl 0.25.0 (git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=ba23bab72f1a9bcc39ce81924d3d9265598e017c)",
] ]
[[package]] [[package]]
name = "uniffi_bindgen" name = "uniffi_bindgen"
version = "0.25.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"askama 0.12.1", "askama 0.12.1",
@ -1257,9 +1278,9 @@ dependencies = [
"serde", "serde",
"textwrap", "textwrap",
"toml 0.5.11", "toml 0.5.11",
"uniffi_meta 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=a77dc0462dc18d53846c758155ab4e0a42e5b240)", "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=a77dc0462dc18d53846c758155ab4e0a42e5b240)", "uniffi_udl 0.25.0 (git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=ba23bab72f1a9bcc39ce81924d3d9265598e017c)",
] ]
[[package]] [[package]]
@ -1322,10 +1343,10 @@ dependencies = [
[[package]] [[package]]
name = "uniffi_checksum_derive" name = "uniffi_checksum_derive"
version = "0.25.0" 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 = [ dependencies = [
"quote", "quote",
"syn 2.0.87", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -1334,7 +1355,7 @@ version = "0.25.0"
source = "git+https://github.com/NordSecurity/uniffi-bindgen-cs?rev=f68639fbc720b50ebe561ba75c66c84dc456bdce#f68639fbc720b50ebe561ba75c66c84dc456bdce" source = "git+https://github.com/NordSecurity/uniffi-bindgen-cs?rev=f68639fbc720b50ebe561ba75c66c84dc456bdce#f68639fbc720b50ebe561ba75c66c84dc456bdce"
dependencies = [ dependencies = [
"quote", "quote",
"syn 2.0.87", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -1344,7 +1365,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55137c122f712d9330fd985d66fa61bdc381752e89c35708c13ce63049a3002c" checksum = "55137c122f712d9330fd985d66fa61bdc381752e89c35708c13ce63049a3002c"
dependencies = [ dependencies = [
"quote", "quote",
"syn 2.0.87", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -1376,7 +1397,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"serde", "serde",
"syn 2.0.87", "syn 2.0.96",
"toml 0.5.11", "toml 0.5.11",
"uniffi_build", "uniffi_build",
"uniffi_meta 0.25.3", "uniffi_meta 0.25.3",
@ -1385,12 +1406,12 @@ dependencies = [
[[package]] [[package]]
name = "uniffi_meta" name = "uniffi_meta"
version = "0.25.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
"siphasher", "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]] [[package]]
@ -1419,7 +1440,7 @@ dependencies = [
[[package]] [[package]]
name = "uniffi_testing" name = "uniffi_testing"
version = "0.25.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"camino", "camino",
@ -1456,12 +1477,12 @@ dependencies = [
[[package]] [[package]]
name = "uniffi_udl" name = "uniffi_udl"
version = "0.25.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"uniffi_meta 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=a77dc0462dc18d53846c758155ab4e0a42e5b240)", "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=a77dc0462dc18d53846c758155ab4e0a42e5b240)", "weedle2 4.0.0 (git+https://github.com/NordSecurity/uniffi-bindgen-go.git?rev=ba23bab72f1a9bcc39ce81924d3d9265598e017c)",
] ]
[[package]] [[package]]
@ -1529,7 +1550,7 @@ dependencies = [
[[package]] [[package]]
name = "weedle2" name = "weedle2"
version = "4.0.0" 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 = [ dependencies = [
"nom", "nom",
] ]
@ -1564,7 +1585,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [ dependencies = [
"windows-sys 0.59.0", "windows-sys",
] ]
[[package]] [[package]]
@ -1573,15 +1594,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 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]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.59.0" version = "0.59.0"
@ -1657,9 +1669,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.6.20" version = "0.6.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -1691,7 +1703,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.87", "syn 2.0.96",
] ]
[[package]] [[package]]

View file

@ -8,10 +8,10 @@ members = [
] ]
[workspace.package] [workspace.package]
version = "0.12.0" version = "0.13.0"
[workspace.lints.rust] [workspace.lints.rust]
missing-docs = "warn" missing-docs = "warn"
[workspace.dependencies] [workspace.dependencies]
thiserror = "1.0.69" thiserror = "2.0"

View file

@ -16,12 +16,12 @@ crate-type = ["rlib"]
log = "0.4" log = "0.4"
bitvec = "1.0" bitvec = "1.0"
flate2 = { version = "1.0", optional = true } flate2 = { version = "1.0", optional = true }
bzip2 = { version = "0.4", optional = true } bzip2 = { version = "0.5", optional = true }
zstd = { version = "0.13", 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 } rand = { version = "0.8", optional = true }
tungstenite = { version = "0.24.0", optional = true } tungstenite = { version = "0.26", optional = true }
once_cell = { version = "1.20.2", optional = true } once_cell = { version = "1.20", optional = true }
thiserror.workspace = true thiserror.workspace = true
[features] [features]

View file

@ -17,7 +17,7 @@ cargo add servicepoint
or or
```toml ```toml
[dependencies] [dependencies]
servicepoint = "0.12.0" servicepoint = "0.13.0"
``` ```
## Examples ## Examples
@ -39,8 +39,11 @@ Execute `cargo run --example` for a list of available examples and `cargo run --
## Note on stability ## Note on stability
This library is still in early development. This library can be used for creative project or just to play around with the display.
You can absolutely use it, and it works, but expect minor breaking changes with every version bump. 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. Please specify the full version including patch in your Cargo.toml until 1.0 is released.
## Features ## Features

View file

@ -1,8 +1,7 @@
//! An example for how to send text to the display. //! An example for how to send text to the display.
use clap::Parser; use clap::Parser;
use servicepoint::*;
use servicepoint::{CharGrid, Command, Connection, Cp437Grid, Origin};
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
struct Cli { struct Cli {
@ -42,10 +41,8 @@ fn main() {
} }
let text = cli.text.join("\n"); let text = cli.text.join("\n");
let grid = CharGrid::from(text); let grid = CharGrid::wrap_str(TILE_WIDTH, &text);
let grid = Cp437Grid::from(grid);
connection connection
.send(Command::Cp437Data(Origin::ZERO, grid)) .send(Command::Utf8Data(Origin::ZERO, grid))
.expect("sending text failed"); .expect("sending text failed");
} }

View file

@ -1,7 +1,6 @@
//! Show a brightness level test pattern on screen //! Show a brightness level test pattern on screen
use clap::Parser; use clap::Parser;
use servicepoint::*; use servicepoint::*;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]

View file

@ -1,11 +1,9 @@
//! A simple game of life implementation to show how to render graphics to the display. //! A simple game of life implementation to show how to render graphics to the display.
use std::thread;
use clap::Parser; use clap::Parser;
use rand::{distributions, Rng}; use rand::{distributions, Rng};
use servicepoint::*; use servicepoint::*;
use std::thread;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
struct Cli { struct Cli {

View file

@ -1,10 +1,8 @@
//! A simple example for how to send pixel data to the display. //! A simple example for how to send pixel data to the display.
use std::thread;
use clap::Parser; use clap::Parser;
use servicepoint::*; use servicepoint::*;
use std::thread;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
struct Cli { struct Cli {

View file

@ -1,13 +1,10 @@
//! A simple example for how to set brightnesses for tiles on the screen. //! A simple example for how to set brightnesses for tiles on the screen.
//! Continuously changes the tiles in a random window to random brightnesses. //! Continuously changes the tiles in a random window to random brightnesses.
use std::time::Duration;
use clap::Parser; use clap::Parser;
use rand::Rng; use rand::Rng;
use servicepoint::Command::{BitmapLinearWin, Brightness, CharBrightness};
use servicepoint::*; use servicepoint::*;
use std::time::Duration;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
struct Cli { struct Cli {
@ -31,7 +28,7 @@ fn main() {
let mut filled_grid = Bitmap::max_sized(); let mut filled_grid = Bitmap::max_sized();
filled_grid.fill(true); filled_grid.fill(true);
let command = BitmapLinearWin( let command = Command::BitmapLinearWin(
Origin::ZERO, Origin::ZERO,
filled_grid, filled_grid,
CompressionCode::Lzma, CompressionCode::Lzma,
@ -41,7 +38,7 @@ fn main() {
// set all pixels to the same random brightness // set all pixels to the same random brightness
let mut rng = rand::thread_rng(); 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 // continuously update random windows to new random brightness
loop { loop {
@ -61,7 +58,9 @@ fn main() {
} }
} }
connection.send(CharBrightness(origin, luma)).unwrap(); connection
.send(Command::CharBrightness(origin, luma))
.unwrap();
std::thread::sleep(wait_duration); std::thread::sleep(wait_duration);
} }
} }

View file

@ -1,11 +1,9 @@
//! An example on how to modify the image on screen without knowing the current content. //! 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::thread;
use std::time::Duration; use std::time::Duration;
use clap::Parser;
use servicepoint::*;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
struct Cli { struct Cli {
#[arg(short, long, default_value = "localhost:2342")] #[arg(short, long, default_value = "localhost:2342")]
@ -34,7 +32,11 @@ fn main() {
} }
connection 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"); .expect("could not send command to display");
thread::sleep(sleep_duration); thread::sleep(sleep_duration);
} }

View file

@ -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<u8, bitvec::Msb0>;
pub mod bitvec {
//! Re-export of the used library [mod@bitvec].
pub use bitvec::prelude::*;
}

View file

@ -1,10 +1,23 @@
use bitvec::order::Msb0; use crate::data_ref::DataRef;
use bitvec::prelude::BitSlice; use crate::BitVec;
use bitvec::slice::IterMut; use crate::*;
use ::bitvec::order::Msb0;
use ::bitvec::prelude::BitSlice;
use ::bitvec::slice::IterMut;
use crate::{BitVec, DataRef, Grid, PIXEL_HEIGHT, PIXEL_WIDTH}; /// A fixed-size 2D grid of booleans.
///
/// A grid of pixels stored in packed bytes. /// 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(8, 2);
///
/// ```
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct Bitmap { pub struct Bitmap {
width: usize, width: usize,
@ -26,7 +39,11 @@ impl Bitmap {
/// ///
/// - when the width is not dividable by 8 /// - when the width is not dividable by 8
pub fn new(width: usize, height: usize) -> Self { 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 { Self {
width, width,
height, height,
@ -176,11 +193,38 @@ impl From<Bitmap> for Vec<u8> {
} }
impl From<Bitmap> for BitVec { impl From<Bitmap> for BitVec {
/// Turns a [Bitmap] into the underlying [BitVec].
fn from(value: Bitmap) -> Self { fn from(value: Bitmap) -> Self {
value.bit_vec value.bit_vec
} }
} }
impl From<&ValueGrid<bool>> 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<bool>) -> 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<bool> {
/// 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()) {
*to = *from;
}
result
}
}
pub struct IterRows<'t> { pub struct IterRows<'t> {
bitmap: &'t Bitmap, bitmap: &'t Bitmap,
row: usize, row: usize,
@ -203,7 +247,7 @@ impl<'t> Iterator for IterRows<'t> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{Bitmap, DataRef, Grid}; use crate::{BitVec, Bitmap, DataRef, Grid, ValueGrid};
#[test] #[test]
fn fill() { fn fill() {
@ -295,4 +339,24 @@ mod tests {
data[1] = 0x0F; data[1] = 0x0F;
assert!(grid.get(7, 1)); 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]);
}
#[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);
}
} }

View file

@ -1,5 +1,3 @@
use crate::{Grid, PrimitiveGrid};
#[cfg(feature = "rand")] #[cfg(feature = "rand")]
use rand::{ use rand::{
distributions::{Distribution, Standard}, distributions::{Distribution, Standard},
@ -22,28 +20,6 @@ use rand::{
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)]
pub struct Brightness(u8); 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<Brightness>;
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<Brightness> for u8 { impl From<Brightness> for u8 {
fn from(brightness: Brightness) -> Self { fn from(brightness: Brightness) -> Self {
Self::from(&brightness) Self::from(&brightness)
@ -92,41 +68,6 @@ impl Default for Brightness {
} }
} }
impl From<BrightnessGrid> for Vec<u8> {
fn from(value: PrimitiveGrid<Brightness>) -> Self {
value
.iter()
.map(|brightness| (*brightness).into())
.collect()
}
}
impl From<&BrightnessGrid> for PrimitiveGrid<u8> {
fn from(value: &PrimitiveGrid<Brightness>) -> Self {
let u8s = value
.iter()
.map(|brightness| (*brightness).into())
.collect::<Vec<u8>>();
PrimitiveGrid::load(value.width(), value.height(), &u8s)
}
}
impl TryFrom<PrimitiveGrid<u8>> for BrightnessGrid {
type Error = u8;
fn try_from(value: PrimitiveGrid<u8>) -> Result<Self, Self::Error> {
let brightnesses = value
.iter()
.map(|b| Brightness::try_from(*b))
.collect::<Result<Vec<_>, _>>()?;
Ok(BrightnessGrid::load(
value.width(),
value.height(),
&brightnesses,
))
}
}
#[cfg(feature = "rand")] #[cfg(feature = "rand")]
impl Distribution<Brightness> for Standard { impl Distribution<Brightness> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Brightness { fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Brightness {
@ -137,7 +78,6 @@ impl Distribution<Brightness> for Standard {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::DataRef;
#[test] #[test]
fn brightness_from_u8() { fn brightness_from_u8() {
@ -154,24 +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] #[test]
fn saturating_convert() { fn saturating_convert() {
assert_eq!(Brightness::MAX, Brightness::saturating_from(100)); assert_eq!(Brightness::MAX, Brightness::saturating_from(100));
assert_eq!(Brightness(5), Brightness::saturating_from(5)); 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]));
}
} }

View file

@ -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<Brightness>;
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<BrightnessGrid> for Vec<u8> {
fn from(value: ValueGrid<Brightness>) -> Self {
value
.iter()
.map(|brightness| (*brightness).into())
.collect()
}
}
impl From<&BrightnessGrid> for ByteGrid {
fn from(value: &ValueGrid<Brightness>) -> Self {
let u8s = value
.iter()
.map(|brightness| (*brightness).into())
.collect::<Vec<u8>>();
ValueGrid::load(value.width(), value.height(), &u8s)
}
}
impl TryFrom<ByteGrid> for BrightnessGrid {
type Error = u8;
fn try_from(value: ByteGrid) -> Result<Self, Self::Error> {
let brightnesses = value
.iter()
.map(|b| Brightness::try_from(*b))
.collect::<Result<Vec<_>, _>>()?;
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])
);
}
}

View file

@ -0,0 +1,4 @@
use crate::ValueGrid;
/// A 2d grid of bytes - see [ValueGrid].
pub type ByteGrid = ValueGrid<u8>;

View file

@ -1,13 +1,71 @@
use crate::primitive_grid::SeriesError; use crate::{Grid, SetValueSeriesError, TryLoadValueGridError, ValueGrid};
use crate::{Grid, PrimitiveGrid}; use std::string::FromUtf8Error;
/// A grid containing UTF-8 characters. /// A grid containing UTF-8 characters.
pub type CharGrid = PrimitiveGrid<char>; ///
/// To send a CharGrid to the display, use [Command::Utf8Data](crate::Command::Utf8Data).
///
/// Also see [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<char>;
impl CharGrid { 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::<Vec<char>>()
.chunks(width)
.map(|c| {
let mut s = String::from_iter(c);
s.push_str(&"\0".repeat(width - s.chars().count()));
s
})
.collect::<Vec<String>>()
})
.collect::<Vec<String>>();
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. /// Copies a column from the grid as a String.
/// ///
/// Returns [None] if x is out of bounds. /// 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<String> { pub fn get_col_str(&self, x: usize) -> Option<String> {
Some(String::from_iter(self.get_col(x)?)) Some(String::from_iter(self.get_col(x)?))
} }
@ -15,42 +73,91 @@ impl CharGrid {
/// Copies a row from the grid as a String. /// Copies a row from the grid as a String.
/// ///
/// Returns [None] if y is out of bounds. /// 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<String> { pub fn get_row_str(&self, y: usize) -> Option<String> {
Some(String::from_iter(self.get_row(y)?)) Some(String::from_iter(self.get_row(y)?))
} }
/// Overwrites a row in the grid with a str. /// 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.
///
/// # Examples
///
/// ```
/// # use servicepoint::CharGrid;
/// let mut grid = CharGrid::from("ab\ncd");
/// grid.set_row_str(0, "ef").unwrap();
/// ```
pub fn set_row_str( pub fn set_row_str(
&mut self, &mut self,
y: usize, y: usize,
value: &str, value: &str,
) -> Result<(), SeriesError> { ) -> Result<(), SetValueSeriesError> {
self.set_row(y, value.chars().collect::<Vec<_>>().as_ref()) self.set_row(y, value.chars().collect::<Vec<_>>().as_ref())
} }
/// Overwrites a column in the grid with a str. /// 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.
///
/// # Examples
///
/// ```
/// # use servicepoint::CharGrid;
/// let mut grid = CharGrid::from("ab\ncd");
/// grid.set_col_str(0, "ef").unwrap();
/// ```
pub fn set_col_str( pub fn set_col_str(
&mut self, &mut self,
x: usize, x: usize,
value: &str, value: &str,
) -> Result<(), SeriesError> { ) -> Result<(), SetValueSeriesError> {
self.set_col(x, value.chars().collect::<Vec<_>>().as_ref()) self.set_col(x, value.chars().collect::<Vec<_>>().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.
///
/// # Examples
///
/// ```
/// # use servicepoint::CharGrid;
/// let grid = CharGrid::load_utf8(2, 2, [97u8, 98, 99, 100].to_vec());
/// ```
pub fn load_utf8(
width: usize,
height: usize,
bytes: Vec<u8>,
) -> Result<CharGrid, LoadUtf8Error> {
let s: Vec<char> = 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] TryLoadValueGridError),
} }
impl From<&str> for CharGrid { impl From<&str> for CharGrid {
fn from(value: &str) -> Self { fn from(value: &str) -> Self {
let value = value.replace("\r\n", "\n"); let value = value.replace("\r\n", "\n");
let mut lines = value let mut lines = value.split('\n').collect::<Vec<_>>();
.split('\n') let width = lines
.map(move |line| line.trim_end()) .iter()
.collect::<Vec<_>>(); .fold(0, move |a, x| std::cmp::max(a, x.chars().count()));
let width =
lines.iter().fold(0, move |a, x| std::cmp::max(a, x.len()));
while lines.last().is_some_and(move |line| line.is_empty()) { while lines.last().is_some_and(move |line| line.is_empty()) {
_ = lines.pop(); _ = lines.pop();
@ -73,26 +180,63 @@ impl From<String> for CharGrid {
} }
} }
impl From<CharGrid> for String {
fn from(grid: CharGrid) -> Self {
String::from(&grid)
}
}
impl From<&CharGrid> 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 { fn from(value: &CharGrid) -> Self {
value value
.iter_rows() .iter_rows()
.map(move |chars| { .map(String::from_iter)
chars .collect::<Vec<String>>()
.collect::<String>()
.replace('\0', " ")
.trim_end()
.to_string()
})
.collect::<Vec<_>>()
.join("\n") .join("\n")
} }
} }
impl From<&CharGrid> for Vec<u8> {
/// Converts a [CharGrid] into a [`Vec<u8>`].
///
/// 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<CharGrid> for Vec<u8> {
/// See [`From<&CharGrid>::from`].
fn from(value: CharGrid) -> Self {
Self::from(&value)
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use crate::Grid;
#[test] #[test]
fn col_str() { fn col_str() {
let mut grid = CharGrid::new(2, 3); let mut grid = CharGrid::new(2, 3);
@ -109,7 +253,7 @@ mod test {
assert_eq!(grid.get_row_str(1), Some(String::from("\0\0"))); assert_eq!(grid.get_row_str(1), Some(String::from("\0\0")));
assert_eq!( assert_eq!(
grid.set_row_str(1, "abc"), grid.set_row_str(1, "abc"),
Err(SeriesError::InvalidLength { Err(SetValueSeriesError::InvalidLength {
expected: 2, expected: 2,
actual: 3 actual: 3
}) })
@ -120,10 +264,35 @@ mod test {
#[test] #[test]
fn str_to_char_grid() { 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); let grid = CharGrid::from(original);
assert_eq!(3, grid.height()); assert_eq!(3, grid.height());
let actual = String::from(&grid); assert_eq!("Hello\0\nWorld!\n...\0\0\0", String::from(grid));
assert_eq!("Hello\nWorld!\n...", actual); }
#[test]
fn round_trip_bytes() {
let grid = CharGrid::from("Hello\0\nWorld!\n...\0\0\0");
let bytes: Vec<u8> = 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);
}
#[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));
} }
} }

View file

@ -1,10 +1,7 @@
use crate::{ use crate::command_code::CommandCode;
command_code::CommandCode, use crate::compression::into_decompressed;
compression::into_decompressed, use crate::value_grid::ValueGrid;
packet::{Header, Packet}, use crate::*;
Bitmap, Brightness, BrightnessGrid, CompressionCode, Cp437Grid, Origin,
Pixels, PrimitiveGrid, BitVec, Tiles, TILE_SIZE,
};
/// Type alias for documenting the meaning of the u16 in enum values /// Type alias for documenting the meaning of the u16 in enum values
pub type Offset = usize; pub type Offset = usize;
@ -41,7 +38,7 @@ pub type Offset = usize;
/// # Examples /// # Examples
/// ///
/// ```rust /// ```rust
/// # use servicepoint::{Brightness, Command, Connection, packet::Packet}; /// use servicepoint::{Brightness, Command, Connection, Packet};
/// # /// #
/// // create command /// // create command
/// let command = Command::Brightness(Brightness::MAX); /// let command = Command::Brightness(Brightness::MAX);
@ -74,14 +71,29 @@ pub enum Command {
/// Show text on the screen. /// Show text on the screen.
/// ///
/// The text is sent in the form of a 2D grid of [CP-437] encoded characters. /// The text is sent in the form of a 2D grid of UTF-8 encoded characters (the default encoding in rust).
/// ///
/// # Examples /// # Examples
/// ///
/// ```rust /// ```rust
/// # use servicepoint::{Command, Connection, Origin}; /// # use servicepoint::{Command, Connection, Origin, CharGrid};
/// # let connection = Connection::Fake;
/// let grid = CharGrid::from("Hello,\nWorld!");
/// connection.send(Command::Utf8Data(Origin::ZERO, grid)).expect("send failed");
/// ```
Utf8Data(Origin<Tiles>, CharGrid),
/// Show text on the screen.
///
/// The text is sent in the form of a 2D grid of [CP-437] encoded characters.
///
/// <div class="warning">You probably want to use [Command::Utf8Data] instead</div>
///
/// # Examples
///
/// ```rust
/// # use servicepoint::{Command, Connection, Origin, CharGrid, Cp437Grid};
/// # let connection = Connection::Fake; /// # let connection = Connection::Fake;
/// use servicepoint::{CharGrid, Cp437Grid};
/// let grid = CharGrid::from("Hello,\nWorld!"); /// let grid = CharGrid::from("Hello,\nWorld!");
/// let grid = Cp437Grid::from(&grid); /// let grid = Cp437Grid::from(&grid);
/// connection.send(Command::Cp437Data(Origin::ZERO, grid)).expect("send failed"); /// connection.send(Command::Cp437Data(Origin::ZERO, grid)).expect("send failed");
@ -234,6 +246,8 @@ pub enum TryFromPacketError {
/// The given brightness value is out of bounds /// The given brightness value is out of bounds
#[error("The given brightness value {0} is out of bounds.")] #[error("The given brightness value {0} is out of bounds.")]
InvalidBrightness(u8), InvalidBrightness(u8),
#[error(transparent)]
InvalidUtf8(#[from] std::string::FromUtf8Error),
} }
impl TryFrom<Packet> for Command { impl TryFrom<Packet> for Command {
@ -269,6 +283,7 @@ impl TryFrom<Packet> for Command {
CommandCode::CharBrightness => { CommandCode::CharBrightness => {
Self::packet_into_char_brightness(&packet) Self::packet_into_char_brightness(&packet)
} }
CommandCode::Utf8Data => Self::packet_into_utf8(&packet),
#[allow(deprecated)] #[allow(deprecated)]
CommandCode::BitmapLegacy => Ok(Command::BitmapLegacy), CommandCode::BitmapLegacy => Ok(Command::BitmapLegacy),
CommandCode::BitmapLinear => { CommandCode::BitmapLinear => {
@ -426,8 +441,7 @@ impl Command {
payload, payload,
} = packet; } = packet;
let grid = let grid = ValueGrid::load(*width as usize, *height as usize, payload);
PrimitiveGrid::load(*width as usize, *height as usize, payload);
let grid = match BrightnessGrid::try_from(grid) { let grid = match BrightnessGrid::try_from(grid) {
Ok(grid) => grid, Ok(grid) => grid,
Err(val) => return Err(TryFromPacketError::InvalidBrightness(val)), Err(val) => return Err(TryFromPacketError::InvalidBrightness(val)),
@ -489,18 +503,37 @@ impl Command {
Cp437Grid::load(*c as usize, *d as usize, payload), Cp437Grid::load(*c as usize, *d as usize, payload),
)) ))
} }
fn packet_into_utf8(
packet: &Packet,
) -> Result<Command, TryFromPacketError> {
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)] #[cfg(test)]
mod tests { mod tests {
use crate::command::TryFromPacketError;
use crate::command_code::CommandCode;
use crate::{ use crate::{
bitvec::prelude::BitVec, BitVec, Bitmap, Brightness, BrightnessGrid, CharGrid, Command,
command::TryFromPacketError, CompressionCode, Cp437Grid, Header, Origin, Packet, Pixels,
command_code::CommandCode,
origin::Pixels,
packet::{Header, Packet},
Bitmap, Brightness, BrightnessGrid, Command, CompressionCode, Origin,
PrimitiveGrid,
}; };
fn round_trip(original: Command) { fn round_trip(original: Command) {
@ -556,16 +589,18 @@ mod tests {
fn round_trip_char_brightness() { fn round_trip_char_brightness() {
round_trip(Command::CharBrightness( round_trip(Command::CharBrightness(
Origin::new(5, 2), Origin::new(5, 2),
PrimitiveGrid::new(7, 5), BrightnessGrid::new(7, 5),
)); ));
} }
#[test] #[test]
fn round_trip_cp437_data() { fn round_trip_cp437_data() {
round_trip(Command::Cp437Data( round_trip(Command::Cp437Data(Origin::new(5, 2), Cp437Grid::new(7, 5)));
Origin::new(5, 2), }
PrimitiveGrid::new(7, 5),
)); #[test]
fn round_trip_utf8_data() {
round_trip(Command::Utf8Data(Origin::new(5, 2), CharGrid::new(7, 5)));
} }
#[test] #[test]

View file

@ -21,6 +21,7 @@ pub(crate) enum CommandCode {
BitmapLinearWinBzip2 = 0x0018, BitmapLinearWinBzip2 = 0x0018,
#[cfg(feature = "compression_lzma")] #[cfg(feature = "compression_lzma")]
BitmapLinearWinLzma = 0x0019, BitmapLinearWinLzma = 0x0019,
Utf8Data = 0x0020,
#[cfg(feature = "compression_zstd")] #[cfg(feature = "compression_zstd")]
BitmapLinearWinZstd = 0x001A, BitmapLinearWinZstd = 0x001A,
} }
@ -93,6 +94,9 @@ impl TryFrom<u16> for CommandCode {
value if value == CommandCode::BitmapLinearWinBzip2 as u16 => { value if value == CommandCode::BitmapLinearWinBzip2 as u16 => {
Ok(CommandCode::BitmapLinearWinBzip2) Ok(CommandCode::BitmapLinearWinBzip2)
} }
value if value == CommandCode::Utf8Data as u16 => {
Ok(CommandCode::Utf8Data)
}
_ => Err(()), _ => Err(()),
} }
} }

View file

@ -8,7 +8,7 @@ use flate2::{FlushCompress, FlushDecompress, Status};
#[cfg(feature = "compression_zstd")] #[cfg(feature = "compression_zstd")]
use zstd::{Decoder as ZstdDecoder, Encoder as ZstdEncoder}; use zstd::{Decoder as ZstdDecoder, Encoder as ZstdEncoder};
use crate::{packet::Payload, CompressionCode}; use crate::{CompressionCode, Payload};
pub(crate) fn into_decompressed( pub(crate) fn into_decompressed(
kind: CompressionCode, kind: CompressionCode,

View file

@ -107,9 +107,7 @@ impl Connection {
let request = ClientRequestBuilder::new(uri).into_client_request()?; let request = ClientRequestBuilder::new(uri).into_client_request()?;
let (sock, _) = connect(request)?; let (sock, _) = connect(request)?;
Ok(Self::WebSocket(std::sync::Mutex::new( Ok(Self::WebSocket(std::sync::Mutex::new(sock)))
sock,
)))
} }
/// Send something packet-like to the display. Usually this is in the form of a Command. /// Send something packet-like to the display. Usually this is in the form of a Command.
@ -144,7 +142,7 @@ impl Connection {
Connection::WebSocket(socket) => { Connection::WebSocket(socket) => {
let mut socket = socket.lock().unwrap(); let mut socket = socket.lock().unwrap();
socket socket
.send(tungstenite::Message::Binary(data)) .send(tungstenite::Message::Binary(data.into()))
.map_err(SendError::WebsocketError) .map_err(SendError::WebsocketError)
} }
Connection::Fake => { Connection::Fake => {
@ -159,9 +157,7 @@ impl Drop for Connection {
fn drop(&mut self) { fn drop(&mut self) {
#[cfg(feature = "protocol_websocket")] #[cfg(feature = "protocol_websocket")]
if let Connection::WebSocket(sock) = self { if let Connection::WebSocket(sock) = self {
_ = sock _ = sock.try_lock().map(move |mut sock| sock.close(None));
.try_lock()
.map(move |mut sock| sock.close(None));
} }
} }
} }
@ -169,7 +165,6 @@ impl Drop for Connection {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::packet::*;
#[test] #[test]
fn send_fake() { fn send_fake() {

View file

@ -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);

View file

@ -1,103 +1,19 @@
//! Conversion between UTF-8 and CP-437.
//!
//! Most of the functionality is only available with feature "cp437" enabled.
use crate::{Grid, PrimitiveGrid};
use std::collections::HashMap; use std::collections::HashMap;
/// A grid containing codepage 437 characters. /// Contains functions to convert between UTF-8 and Codepage 437.
/// ///
/// The encoding is currently not enforced. /// See <https://en.wikipedia.org/wiki/Code_page_437#Character_set>
pub type Cp437Grid = PrimitiveGrid<u8>; pub struct Cp437Converter;
/// The error occurring when loading an invalid character /// An array of 256 elements, mapping most of the CP437 values to UTF-8 characters
#[derive(Debug, PartialEq, thiserror::Error)] ///
#[error("The character {char:?} at position {index} is not a valid CP437 character")] /// Mostly follows CP437, except 0x0A, which is kept for use as line ending.
pub struct InvalidCharError { ///
/// invalid character is at this position in input /// See <https://en.wikipedia.org/wiki/Code_page_437#Character_set>
index: usize, ///
/// the invalid character /// Mostly copied from <https://github.com/kip93/cp437-tools>. License: GPL-3.0
char: char, #[rustfmt::skip]
} const CP437_TO_UTF8: [char; 256] = [
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<Self, InvalidCharError> {
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
///
/// Mostly follows CP437, except 0x0A, which is kept for use as line ending.
///
/// See <https://en.wikipedia.org/wiki/Code_page_437#Character_set>
///
/// Mostly copied from <https://github.com/kip93/cp437-tools>. License: GPL-3.0
#[rustfmt::skip]
pub const CP437_TO_UTF8: [char; 256] = [
/* 0X */ '\0', '☺', '☻', '♥', '♦', '♣', '♠', '•', '◘', '○', '\n', '♂', '♀', '♪', '♫', '☼', /* 0X */ '\0', '☺', '☻', '♥', '♦', '♣', '♠', '•', '◘', '○', '\n', '♂', '♀', '♪', '♫', '☼',
/* 1X */ '►', '◄', '↕', '‼', '¶', '§', '▬', '↨', '↑', '↓', '→', '←', '∟', '↔', '▲', '▼', /* 1X */ '►', '◄', '↕', '‼', '¶', '§', '▬', '↨', '↑', '↓', '→', '←', '∟', '↔', '▲', '▼',
/* 2X */ ' ', '!', '"', '#', '$', '%', '&', '\'','(', ')', '*', '+', ',', '-', '.', '/', /* 2X */ ' ', '!', '"', '#', '$', '%', '&', '\'','(', ')', '*', '+', ',', '-', '.', '/',
@ -114,9 +30,8 @@ mod feature_cp437 {
/* DX */ '╨', '╤', '╥', '╙', '╘', '╒', '╓', '╫', '╪', '┘', '┌', '█', '▄', '▌', '▐', '▀', /* DX */ '╨', '╤', '╥', '╙', '╘', '╒', '╓', '╫', '╪', '┘', '┌', '█', '▄', '▌', '▐', '▀',
/* EX */ 'α', 'ß', 'Γ', 'π', 'Σ', 'σ', 'µ', 'τ', 'Φ', 'Θ', 'Ω', 'δ', '∞', 'φ', 'ε', '∩', /* EX */ 'α', 'ß', 'Γ', 'π', 'Σ', 'σ', 'µ', 'τ', 'Φ', 'Θ', 'Ω', 'δ', '∞', 'φ', 'ε', '∩',
/* FX */ '≡', '±', '≥', '≤', '⌠', '⌡', '÷', '≈', '°', '∙', '·', '√', 'ⁿ', '²', '■', ' ', /* FX */ '≡', '±', '≥', '≤', '⌠', '⌡', '÷', '≈', '°', '∙', '·', '√', 'ⁿ', '²', '■', ' ',
]; ];
static UTF8_TO_CP437: once_cell::sync::Lazy<HashMap<char, u8>> =
static UTF8_TO_CP437: once_cell::sync::Lazy<HashMap<char, u8>> =
once_cell::sync::Lazy::new(|| { once_cell::sync::Lazy::new(|| {
let pairs = CP437_TO_UTF8 let pairs = CP437_TO_UTF8
.iter() .iter()
@ -125,29 +40,15 @@ mod feature_cp437 {
HashMap::from_iter(pairs) HashMap::from_iter(pairs)
}); });
impl Cp437Converter {
const MISSING_CHAR_CP437: u8 = 0x3F; // '?' 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<CharGrid> for Cp437Grid {
fn from(value: CharGrid) -> Self {
Cp437Grid::from(&value)
}
}
/// Convert the provided bytes to UTF-8. /// Convert the provided bytes to UTF-8.
pub fn cp437_to_str(cp437: &[u8]) -> String { 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. /// Convert a single CP-437 character to UTF-8.
@ -159,66 +60,20 @@ mod feature_cp437 {
/// ///
/// Characters that are not available are mapped to '?'. /// Characters that are not available are mapped to '?'.
pub fn str_to_cp437(utf8: &str) -> Vec<u8> { pub fn str_to_cp437(utf8: &str) -> Vec<u8> {
utf8.chars().map(char_to_cp437).collect() utf8.chars().map(Self::char_to_cp437).collect()
} }
/// Convert a single UTF-8 character to CP-437. /// Convert a single UTF-8 character to CP-437.
pub fn char_to_cp437(utf8: char) -> u8 { 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(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 { mod tests_feature_cp437 {
use super::*; 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] #[test]
fn convert_str() { fn convert_str() {
@ -245,13 +100,16 @@ mod tests_feature_cp437 {
dx Σ x²·δx dx Σ x²·δx
"#; "#;
let cp437 = str_to_cp437(utf8); let cp437 = Cp437Converter::str_to_cp437(utf8);
let actual = cp437_to_str(&*cp437); let actual = Cp437Converter::cp437_to_str(&*cp437);
assert_eq!(utf8, actual) assert_eq!(utf8, actual)
} }
#[test] #[test]
fn convert_invalid() { fn convert_invalid() {
assert_eq!(cp437_to_char(char_to_cp437('😜')), '?'); assert_eq!(
Cp437Converter::cp437_to_char(Cp437Converter::char_to_cp437('😜')),
'?'
);
} }
} }

View file

@ -0,0 +1,163 @@
/// A grid containing codepage 437 characters.
///
/// The encoding is currently not enforced.
pub type Cp437Grid = crate::value_grid::ValueGrid<u8>;
/// 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<Self, InvalidCharError> {
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::{CharGrid, Cp437Converter};
impl From<&Cp437Grid> for CharGrid {
fn from(value: &Cp437Grid) -> Self {
value.map(Cp437Converter::cp437_to_char)
}
}
impl From<Cp437Grid> for CharGrid {
fn from(value: Cp437Grid) -> Self {
Self::from(&value)
}
}
impl From<&CharGrid> for Cp437Grid {
fn from(value: &CharGrid) -> Self {
value.map(Cp437Converter::char_to_cp437)
}
}
impl From<CharGrid> for Cp437Grid {
fn from(value: CharGrid) -> Self {
Self::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.clone());
let actual = CharGrid::from(cp437);
assert_eq!(actual, utf8);
}
}

View file

@ -2,14 +2,17 @@
//! //!
//! Your starting point is a [Connection] to the display. //! Your starting point is a [Connection] to the display.
//! With a connection, you can send [Command]s. //! 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 //! # Examples
//! //!
//! ```rust //! ### Clear display
//! use servicepoint::{Command, CompressionCode, Grid, Bitmap};
//! //!
//! 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"); //! .expect("connection failed");
//! //!
//! // turn off all pixels on display //! // turn off all pixels on display
@ -17,6 +20,8 @@
//! .expect("send failed"); //! .expect("send failed");
//! ``` //! ```
//! //!
//! ### Set all pixels to on
//!
//! ```rust //! ```rust
//! # use servicepoint::{Command, CompressionCode, Grid, Bitmap}; //! # use servicepoint::{Command, CompressionCode, Grid, Bitmap};
//! # let connection = servicepoint::Connection::open("127.0.0.1:2342").expect("connection failed"); //! # let connection = servicepoint::Connection::open("127.0.0.1:2342").expect("connection failed");
@ -34,114 +39,63 @@
//! // send command to display //! // send command to display
//! connection.send(command).expect("send failed"); //! 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");
//! ```
use std::time::Duration; pub use crate::bit_vec::{bitvec, BitVec};
pub use bitvec;
pub use crate::bitmap::Bitmap; pub use crate::bitmap::Bitmap;
pub use crate::brightness::{Brightness, BrightnessGrid}; 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::char_grid::CharGrid;
pub use crate::command::{Command, Offset}; pub use crate::command::{Command, Offset};
pub use crate::compression_code::CompressionCode; pub use crate::compression_code::CompressionCode;
pub use crate::connection::Connection; pub use crate::connection::Connection;
pub use crate::cp437::Cp437Grid; pub use crate::constants::*;
pub use crate::cp437::Cp437Converter;
pub use crate::cp437_grid::Cp437Grid;
pub use crate::data_ref::DataRef; pub use crate::data_ref::DataRef;
pub use crate::grid::Grid; pub use crate::grid::Grid;
pub use crate::origin::{Origin, Pixels, Tiles}; pub use crate::origin::{Origin, Pixels, Tiles};
pub use crate::primitive_grid::{PrimitiveGrid, SeriesError}; pub use crate::packet::{Header, Packet, Payload};
pub use crate::value_grid::{
/// An alias for the specific type of [bitvec::prelude::BitVec] used. IterGridRows, SetValueSeriesError, TryLoadValueGridError, Value, ValueGrid,
pub type BitVec = bitvec::prelude::BitVec<u8, bitvec::prelude::Msb0>; };
mod bit_vec;
mod bitmap; mod bitmap;
mod brightness; mod brightness;
mod brightness_grid;
mod byte_grid;
mod char_grid; mod char_grid;
mod command; mod command;
mod command_code; mod command_code;
mod compression; mod compression;
mod compression_code; mod compression_code;
mod connection; mod connection;
pub mod cp437; mod constants;
mod cp437_grid;
mod data_ref; mod data_ref;
mod grid; mod grid;
mod origin; mod origin;
pub mod packet; mod packet;
mod primitive_grid; mod value_grid;
/// size of a single tile in one dimension #[cfg(feature = "cp437")]
pub const TILE_SIZE: usize = 8; mod cp437;
/// 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);
// include README.md in doctest // include README.md in doctest
#[doc = include_str!("../README.md")] #[doc = include_str!("../README.md")]

View file

@ -7,7 +7,7 @@
//! Converting a packet to a command and back: //! Converting a packet to a command and back:
//! //!
//! ```rust //! ```rust
//! use servicepoint::{Command, packet::Packet}; //! use servicepoint::{Command, Packet};
//! # let command = Command::Clear; //! # let command = Command::Clear;
//! let packet: Packet = command.into(); //! let packet: Packet = command.into();
//! let command: Command = Command::try_from(packet).expect("could not read command from packet"); //! 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: //! Converting a packet to bytes and back:
//! //!
//! ```rust //! ```rust
//! use servicepoint::{Command, packet::Packet}; //! use servicepoint::{Command, Packet};
//! # let command = Command::Clear; //! # let command = Command::Clear;
//! # let packet: Packet = command.into(); //! # let packet: Packet = command.into();
//! let bytes: Vec<u8> = packet.into(); //! let bytes: Vec<u8> = packet.into();
//! let packet = Packet::try_from(bytes).expect("could not read packet from bytes"); //! 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::compression::into_compressed;
use crate::{ use crate::{
command_code::CommandCode, Bitmap, Command, CompressionCode, Grid, Offset, Bitmap, Command, CompressionCode, Grid, Offset, Origin, Pixels, Tiles,
Origin, Pixels, Tiles, TILE_SIZE, TILE_SIZE,
}; };
use std::mem::size_of;
/// A raw header. /// A raw header.
/// ///
@ -209,6 +209,9 @@ impl From<Command> for Packet {
grid, grid,
CommandCode::Cp437Data, CommandCode::Cp437Data,
), ),
Command::Utf8Data(origin, grid) => {
Self::origin_grid_to_packet(origin, grid, CommandCode::Utf8Data)
}
} }
} }
} }

View file

@ -1,13 +1,20 @@
use std::fmt::Debug;
use std::slice::{Iter, IterMut}; use std::slice::{Iter, IterMut};
use crate::{DataRef, Grid}; use crate::*;
pub trait PrimitiveGridType: Sized + Default + Copy + Clone {} /// A type that can be stored in a [ValueGrid], e.g. [char], [u8].
impl<T: Sized + Default + Copy + Clone> PrimitiveGridType for T {} pub trait Value: Sized + Default + Copy + Clone + Debug {}
impl<T: Sized + Default + Copy + Clone + Debug> 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)] #[derive(Debug, Clone, PartialEq)]
pub struct PrimitiveGrid<T: PrimitiveGridType> { pub struct ValueGrid<T: Value> {
width: usize, width: usize,
height: usize, height: usize,
data: Vec<T>, data: Vec<T>,
@ -15,7 +22,7 @@ pub struct PrimitiveGrid<T: PrimitiveGridType> {
/// Error type for methods that change a whole column or row at once /// Error type for methods that change a whole column or row at once
#[derive(thiserror::Error, Debug, PartialEq)] #[derive(thiserror::Error, Debug, PartialEq)]
pub enum SeriesError { pub enum SetValueSeriesError {
#[error("The index {index} was out of bounds for size {size}")] #[error("The index {index} was out of bounds for size {size}")]
/// The index {index} was out of bounds for size {size} /// The index {index} was out of bounds for size {size}
OutOfBounds { OutOfBounds {
@ -34,15 +41,15 @@ pub enum SeriesError {
}, },
} }
impl<T: PrimitiveGridType> PrimitiveGrid<T> { impl<T: Value> ValueGrid<T> {
/// Creates a new [PrimitiveGrid] with the specified dimensions. /// Creates a new [ValueGrid] with the specified dimensions.
/// ///
/// # Arguments /// # Arguments
/// ///
/// - width: size in x-direction /// - width: size in x-direction
/// - height: size in y-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 { pub fn new(width: usize, height: usize) -> Self {
Self { Self {
data: vec![Default::default(); width * height], data: vec![Default::default(); width * height],
@ -51,16 +58,20 @@ impl<T: PrimitiveGridType> PrimitiveGrid<T> {
} }
} }
/// 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 /// # Panics
/// ///
/// - when the dimensions and data size do not match exactly. /// - when the dimensions and data size do not match exactly.
#[must_use] #[must_use]
pub fn load(width: usize, height: usize, data: &[T]) -> Self { 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 { Self {
data: Vec::from(data), data: Vec::from(data),
width, width,
@ -68,12 +79,52 @@ impl<T: PrimitiveGridType> PrimitiveGrid<T> {
} }
} }
/// Iterate over all cells in [PrimitiveGrid]. /// 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<Self, TryLoadValueGridError> {
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].
pub fn try_load(
width: usize,
height: usize,
data: Vec<T>,
) -> Result<Self, TryLoadValueGridError> {
if width * height != data.len() {
return Err(TryLoadValueGridError::InvalidDimensions);
}
Ok(Self {
data,
width,
height,
})
}
/// Iterate over all cells in [ValueGrid].
/// ///
/// Order is equivalent to the following loop: /// Order is equivalent to the following loop:
/// ``` /// ```
/// # use servicepoint::{PrimitiveGrid, Grid}; /// # use servicepoint::{ByteGrid, Grid};
/// # let grid = PrimitiveGrid::<u8>::new(2,2); /// # let grid = ByteGrid::new(2,2);
/// for y in 0..grid.height() { /// for y in 0..grid.height() {
/// for x in 0..grid.width() { /// for x in 0..grid.width() {
/// grid.get(x, y); /// grid.get(x, y);
@ -84,9 +135,9 @@ impl<T: PrimitiveGridType> PrimitiveGrid<T> {
self.data.iter() self.data.iter()
} }
/// Iterate over all rows in [PrimitiveGrid] top to bottom. /// Iterate over all rows in [ValueGrid] top to bottom.
pub fn iter_rows(&self) -> IterRows<T> { pub fn iter_rows(&self) -> IterGridRows<T> {
IterRows { IterGridRows {
byte_grid: self, byte_grid: self,
row: 0, row: 0,
} }
@ -132,7 +183,7 @@ impl<T: PrimitiveGridType> PrimitiveGrid<T> {
} }
} }
/// Convert between PrimitiveGrid types. /// Convert between ValueGrid types.
/// ///
/// See also [Iterator::map]. /// See also [Iterator::map].
/// ///
@ -140,18 +191,18 @@ impl<T: PrimitiveGridType> PrimitiveGrid<T> {
/// ///
/// Use logic written for u8s and then convert to [Brightness] values for sending in a [Command]. /// Use logic written for u8s and then convert to [Brightness] values for sending in a [Command].
/// ``` /// ```
/// # fn foo(grid: &mut PrimitiveGrid<u8>) {} /// # fn foo(grid: &mut ByteGrid) {}
/// # use servicepoint::{Brightness, BrightnessGrid, Command, Origin, PrimitiveGrid, TILE_HEIGHT, TILE_WIDTH}; /// # use servicepoint::{Brightness, BrightnessGrid, ByteGrid, Command, Origin, TILE_HEIGHT, TILE_WIDTH};
/// let mut grid: PrimitiveGrid<u8> = PrimitiveGrid::new(TILE_WIDTH, TILE_HEIGHT); /// let mut grid: ByteGrid = ByteGrid::new(TILE_WIDTH, TILE_HEIGHT);
/// foo(&mut grid); /// foo(&mut grid);
/// let grid: BrightnessGrid = grid.map(Brightness::saturating_from); /// let grid: BrightnessGrid = grid.map(Brightness::saturating_from);
/// let command = Command::CharBrightness(Origin::ZERO, grid); /// let command = Command::CharBrightness(Origin::ZERO, grid);
/// ``` /// ```
/// [Brightness]: [crate::Brightness] /// [Brightness]: [crate::Brightness]
/// [Command]: [crate::Command] /// [Command]: [crate::Command]
pub fn map<TConverted, F>(&self, f: F) -> PrimitiveGrid<TConverted> pub fn map<TConverted, F>(&self, f: F) -> ValueGrid<TConverted>
where where
TConverted: PrimitiveGridType, TConverted: Value,
F: Fn(T) -> TConverted, F: Fn(T) -> TConverted,
{ {
let data = self let data = self
@ -159,7 +210,7 @@ impl<T: PrimitiveGridType> PrimitiveGrid<T> {
.iter() .iter()
.map(|elem| f(*elem)) .map(|elem| f(*elem))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
PrimitiveGrid::load(self.width(), self.height(), &data) ValueGrid::load(self.width(), self.height(), &data)
} }
/// Copies a row from the grid. /// Copies a row from the grid.
@ -185,9 +236,13 @@ impl<T: PrimitiveGridType> PrimitiveGrid<T> {
/// Overwrites a column in the grid. /// Overwrites a column in the grid.
/// ///
/// Returns [Err] if x is out of bounds or `col` is not of the correct size. /// 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() { if col.len() != self.height() {
return Err(SeriesError::InvalidLength { return Err(SetValueSeriesError::InvalidLength {
expected: self.height(), expected: self.height(),
actual: col.len(), actual: col.len(),
}); });
@ -204,7 +259,7 @@ impl<T: PrimitiveGridType> PrimitiveGrid<T> {
{ {
Ok(()) Ok(())
} else { } else {
Err(SeriesError::OutOfBounds { Err(SetValueSeriesError::OutOfBounds {
index: x, index: x,
size: width, size: width,
}) })
@ -214,10 +269,14 @@ impl<T: PrimitiveGridType> PrimitiveGrid<T> {
/// Overwrites a row in the grid. /// Overwrites a row in the grid.
/// ///
/// Returns [Err] if y is out of bounds or `row` is not of the correct size. /// 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(); let width = self.width();
if row.len() != width { if row.len() != width {
return Err(SeriesError::InvalidLength { return Err(SetValueSeriesError::InvalidLength {
expected: width, expected: width,
actual: row.len(), actual: row.len(),
}); });
@ -226,7 +285,7 @@ impl<T: PrimitiveGridType> PrimitiveGrid<T> {
let chunk = match self.data.chunks_exact_mut(width).nth(y) { let chunk = match self.data.chunks_exact_mut(width).nth(y) {
Some(row) => row, Some(row) => row,
None => { None => {
return Err(SeriesError::OutOfBounds { return Err(SetValueSeriesError::OutOfBounds {
size: self.height(), size: self.height(),
index: y, index: y,
}) })
@ -238,8 +297,16 @@ impl<T: PrimitiveGridType> PrimitiveGrid<T> {
} }
} }
impl<T: PrimitiveGridType> Grid<T> for PrimitiveGrid<T> { /// Errors that can occur when loading a grid
/// Sets the value of the cell at the specified position in the `PrimitiveGrid. #[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
InvalidDimensions,
}
impl<T: Value> Grid<T> for ValueGrid<T> {
/// Sets the value of the cell at the specified position in the `ValueGrid.
/// ///
/// # Arguments /// # Arguments
/// ///
@ -281,7 +348,7 @@ impl<T: PrimitiveGridType> Grid<T> for PrimitiveGrid<T> {
} }
} }
impl<T: PrimitiveGridType> DataRef<T> for PrimitiveGrid<T> { impl<T: Value> DataRef<T> for ValueGrid<T> {
/// Get the underlying byte rows mutable /// Get the underlying byte rows mutable
fn data_ref_mut(&mut self) -> &mut [T] { fn data_ref_mut(&mut self) -> &mut [T] {
self.data.as_mut_slice() self.data.as_mut_slice()
@ -293,19 +360,20 @@ impl<T: PrimitiveGridType> DataRef<T> for PrimitiveGrid<T> {
} }
} }
impl<T: PrimitiveGridType> From<PrimitiveGrid<T>> for Vec<T> { impl<T: Value> From<ValueGrid<T>> for Vec<T> {
/// Turn into the underlying [`Vec<u8>`] containing the rows of bytes. /// Turn into the underlying [`Vec<u8>`] containing the rows of bytes.
fn from(value: PrimitiveGrid<T>) -> Self { fn from(value: ValueGrid<T>) -> Self {
value.data value.data
} }
} }
pub struct IterRows<'t, T: PrimitiveGridType> { /// An iterator iver the rows in a [ValueGrid]
byte_grid: &'t PrimitiveGrid<T>, pub struct IterGridRows<'t, T: Value> {
byte_grid: &'t ValueGrid<T>,
row: usize, 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>; type Item = Iter<'t, T>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
@ -323,11 +391,14 @@ impl<'t, T: PrimitiveGridType> Iterator for IterRows<'t, T> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{DataRef, Grid, PrimitiveGrid, SeriesError}; use crate::{
value_grid::{SetValueSeriesError, ValueGrid},
*,
};
#[test] #[test]
fn fill() { fn fill() {
let mut grid = PrimitiveGrid::<usize>::new(2, 2); let mut grid = ValueGrid::<usize>::new(2, 2);
assert_eq!(grid.data, [0x00, 0x00, 0x00, 0x00]); assert_eq!(grid.data, [0x00, 0x00, 0x00, 0x00]);
grid.fill(42); grid.fill(42);
@ -336,7 +407,7 @@ mod tests {
#[test] #[test]
fn get_set() { 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(0, 0), 0);
assert_eq!(grid.get(1, 1), 0); assert_eq!(grid.get(1, 1), 0);
@ -351,7 +422,7 @@ mod tests {
#[test] #[test]
fn load() { fn load() {
let mut grid = PrimitiveGrid::new(2, 3); let mut grid = ValueGrid::new(2, 3);
for x in 0..grid.width { for x in 0..grid.width {
for y in 0..grid.height { for y in 0..grid.height {
grid.set(x, y, (x + y) as u8); grid.set(x, y, (x + y) as u8);
@ -362,13 +433,13 @@ mod tests {
let data: Vec<u8> = grid.into(); let data: Vec<u8> = 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]); assert_eq!(grid.data, [0, 1, 1, 2, 2, 3]);
} }
#[test] #[test]
fn mut_data_ref() { 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(); let data_ref = vec.data_ref_mut();
data_ref.copy_from_slice(&[1, 2, 3, 4]); data_ref.copy_from_slice(&[1, 2, 3, 4]);
@ -379,7 +450,7 @@ mod tests {
#[test] #[test]
fn iter() { fn iter() {
let mut vec = PrimitiveGrid::new(2, 2); let mut vec = ValueGrid::new(2, 2);
vec.set(1, 1, 5); vec.set(1, 1, 5);
let mut iter = vec.iter(); let mut iter = vec.iter();
@ -391,7 +462,7 @@ mod tests {
#[test] #[test]
fn iter_mut() { 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() { for (index, cell) in vec.iter_mut().enumerate() {
*cell = index as u8; *cell = index as u8;
} }
@ -401,7 +472,7 @@ mod tests {
#[test] #[test]
fn iter_rows() { 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 (y, row) in vec.iter_rows().enumerate() {
for (x, val) in row.enumerate() { for (x, val) in row.enumerate() {
assert_eq!(*val, (x + y) as u8); assert_eq!(*val, (x + y) as u8);
@ -412,20 +483,20 @@ mod tests {
#[test] #[test]
#[should_panic] #[should_panic]
fn out_of_bounds_x() { 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); vec.set(2, 1, 5);
} }
#[test] #[test]
#[should_panic] #[should_panic]
fn out_of_bounds_y() { 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); vec.get(1, 2);
} }
#[test] #[test]
fn ref_mut() { 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); let top_left = vec.get_ref_mut(0, 0);
*top_left += 5; *top_left += 5;
@ -436,7 +507,7 @@ mod tests {
#[test] #[test]
fn optional() { 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(0, 0, 5);
grid.set_optional(-1, 0, 8); grid.set_optional(-1, 0, 8);
grid.set_optional(0, 8, 42); grid.set_optional(0, 8, 42);
@ -448,18 +519,18 @@ mod tests {
#[test] #[test]
fn col() { 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(0), Some(vec![0, 2, 4]));
assert_eq!(grid.get_col(1), Some(vec![1, 3, 5])); assert_eq!(grid.get_col(1), Some(vec![1, 3, 5]));
assert_eq!(grid.get_col(2), None); assert_eq!(grid.get_col(2), None);
assert_eq!(grid.set_col(0, &[5, 7, 9]), Ok(())); assert_eq!(grid.set_col(0, &[5, 7, 9]), Ok(()));
assert_eq!( assert_eq!(
grid.set_col(2, &[5, 7, 9]), grid.set_col(2, &[5, 7, 9]),
Err(SeriesError::OutOfBounds { size: 2, index: 2 }) Err(SetValueSeriesError::OutOfBounds { size: 2, index: 2 })
); );
assert_eq!( assert_eq!(
grid.set_col(0, &[5, 7]), grid.set_col(0, &[5, 7]),
Err(SeriesError::InvalidLength { Err(SetValueSeriesError::InvalidLength {
expected: 3, expected: 3,
actual: 2 actual: 2
}) })
@ -469,7 +540,7 @@ mod tests {
#[test] #[test]
fn row() { 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(0), Some(vec![0, 1]));
assert_eq!(grid.get_row(2), Some(vec![4, 5])); assert_eq!(grid.get_row(2), Some(vec![4, 5]));
assert_eq!(grid.get_row(3), None); assert_eq!(grid.get_row(3), None);
@ -477,14 +548,23 @@ mod tests {
assert_eq!(grid.get_row(0), Some(vec![5, 7])); assert_eq!(grid.get_row(0), Some(vec![5, 7]));
assert_eq!( assert_eq!(
grid.set_row(3, &[5, 7]), grid.set_row(3, &[5, 7]),
Err(SeriesError::OutOfBounds { size: 3, index: 3 }) Err(SetValueSeriesError::OutOfBounds { size: 3, index: 3 })
); );
assert_eq!( assert_eq!(
grid.set_row(2, &[5, 7, 3]), grid.set_row(2, &[5, 7, 3]),
Err(SeriesError::InvalidLength { Err(SetValueSeriesError::InvalidLength {
expected: 2, expected: 2,
actual: 3 actual: 3
}) })
); );
} }
#[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));
}
} }

View file

@ -17,7 +17,7 @@ crate-type = ["staticlib", "cdylib", "rlib"]
cbindgen = "0.27.0" cbindgen = "0.27.0"
[dependencies.servicepoint] [dependencies.servicepoint]
version = "0.12.0" version = "0.13.0"
path = "../servicepoint" path = "../servicepoint"
features = ["all_compressions"] features = ["all_compressions"]

View file

@ -8,7 +8,7 @@ publish = false
test = false test = false
[build-dependencies] [build-dependencies]
cc = "1.0" cc = "1.2"
[dependencies] [dependencies]
servicepoint_binding_c = { path = "../.." } servicepoint_binding_c = { path = "../.." }

View file

@ -14,12 +14,12 @@
#define SP_BRIGHTNESS_LEVELS 12 #define SP_BRIGHTNESS_LEVELS 12
/** /**
* see [Brightness::MAX] * see [servicepoint::Brightness::MAX]
*/ */
#define SP_BRIGHTNESS_MAX 11 #define SP_BRIGHTNESS_MAX 11
/** /**
* see [Brightness::MIN] * see [servicepoint::Brightness::MIN]
*/ */
#define SP_BRIGHTNESS_MIN 0 #define SP_BRIGHTNESS_MIN 0
@ -131,6 +131,25 @@ typedef struct SPBitmap SPBitmap;
*/ */
typedef struct SPBrightnessGrid SPBrightnessGrid; 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. * A low-level display command.
* *
@ -367,6 +386,20 @@ SPBitmap *sp_bitmap_load(size_t width,
SPBitmap *sp_bitmap_new(size_t width, SPBitmap *sp_bitmap_new(size_t width,
size_t height); 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]. * 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); 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. * 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); 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. * The passed [SPCp437Grid] gets consumed.
* *
@ -1201,6 +1424,30 @@ SPCommand *sp_command_hard_reset(void);
*/ */
SPCommand *sp_command_try_from_packet(SPPacket *packet); 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. * 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 * # Panics
* *
* - when `sp_packet_free` is NULL * - when `packet` is NULL
* *
* # Safety * # Safety
* *
@ -1560,6 +1807,40 @@ void sp_packet_free(SPPacket *packet);
*/ */
SPPacket *sp_packet_from_command(SPCommand *command); 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. * Tries to load a [SPPacket] from the passed array with the specified length.
* *

View file

@ -2,7 +2,7 @@
#include "servicepoint.h" #include "servicepoint.h"
int main(void) { int main(void) {
SPConnection *connection = sp_connection_open("172.23.42.29:2342"); SPConnection *connection = sp_connection_open("localhost:2342");
if (connection == NULL) if (connection == NULL)
return 1; return 1;
@ -10,9 +10,8 @@ int main(void) {
sp_bitmap_fill(pixels, true); sp_bitmap_fill(pixels, true);
SPCommand *command = sp_command_bitmap_linear_win(0, 0, pixels, SP_COMPRESSION_CODE_UNCOMPRESSED); 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); sp_connection_free(connection);
return 0; return 0;
} }

View file

@ -2,8 +2,8 @@
//! //!
//! prefix `sp_bitmap_` //! prefix `sp_bitmap_`
use std::ptr::NonNull;
use servicepoint::{DataRef, Grid}; use servicepoint::{DataRef, Grid};
use std::ptr::NonNull;
use crate::byte_slice::SPByteSlice; use crate::byte_slice::SPByteSlice;
@ -43,9 +43,23 @@ pub unsafe extern "C" fn sp_bitmap_new(
width: usize, width: usize,
height: usize, height: usize,
) -> NonNull<SPBitmap> { ) -> NonNull<SPBitmap> {
let result = Box::new(SPBitmap(servicepoint::Bitmap::new( let result = Box::new(SPBitmap(servicepoint::Bitmap::new(width, height)));
width, height, 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<SPBitmap> {
let result = Box::new(SPBitmap(servicepoint::Bitmap::max_sized()));
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
@ -80,9 +94,8 @@ pub unsafe extern "C" fn sp_bitmap_load(
) -> NonNull<SPBitmap> { ) -> NonNull<SPBitmap> {
assert!(!data.is_null()); assert!(!data.is_null());
let data = std::slice::from_raw_parts(data, data_length); let data = std::slice::from_raw_parts(data, data_length);
let result = Box::new(SPBitmap(servicepoint::Bitmap::load( let result =
width, height, data, Box::new(SPBitmap(servicepoint::Bitmap::load(width, height, data)));
)));
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }

View file

@ -2,9 +2,8 @@
//! //!
//! prefix `sp_bitvec_` //! prefix `sp_bitvec_`
use std::ptr::NonNull;
use crate::SPByteSlice; use crate::SPByteSlice;
use servicepoint::bitvec::prelude::{BitVec, Msb0}; use std::ptr::NonNull;
/// A vector of bits /// A vector of bits
/// ///
@ -14,15 +13,15 @@ use servicepoint::bitvec::prelude::{BitVec, Msb0};
/// sp_bitvec_set(vec, 5, true); /// sp_bitvec_set(vec, 5, true);
/// sp_bitvec_free(vec); /// sp_bitvec_free(vec);
/// ``` /// ```
pub struct SPBitVec(BitVec<u8, Msb0>); pub struct SPBitVec(servicepoint::BitVec);
impl From<BitVec<u8, Msb0>> for SPBitVec { impl From<servicepoint::BitVec> for SPBitVec {
fn from(actual: BitVec<u8, Msb0>) -> Self { fn from(actual: servicepoint::BitVec) -> Self {
Self(actual) Self(actual)
} }
} }
impl From<SPBitVec> for BitVec<u8, Msb0> { impl From<SPBitVec> for servicepoint::BitVec {
fn from(value: SPBitVec) -> Self { fn from(value: SPBitVec) -> Self {
value.0 value.0
} }
@ -54,7 +53,7 @@ impl Clone for SPBitVec {
/// by explicitly calling `sp_bitvec_free`. /// by explicitly calling `sp_bitvec_free`.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sp_bitvec_new(size: usize) -> NonNull<SPBitVec> { pub unsafe extern "C" fn sp_bitvec_new(size: usize) -> NonNull<SPBitVec> {
let result = Box::new(SPBitVec(BitVec::repeat(false, size))); let result = Box::new(SPBitVec(servicepoint::BitVec::repeat(false, size)));
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
@ -81,7 +80,7 @@ pub unsafe extern "C" fn sp_bitvec_load(
) -> NonNull<SPBitVec> { ) -> NonNull<SPBitVec> {
assert!(!data.is_null()); assert!(!data.is_null());
let data = std::slice::from_raw_parts(data, data_length); 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)) NonNull::from(Box::leak(result))
} }

View file

@ -3,14 +3,14 @@
//! prefix `sp_brightness_grid_` //! prefix `sp_brightness_grid_`
use crate::SPByteSlice; use crate::SPByteSlice;
use servicepoint::{Brightness, DataRef, Grid, PrimitiveGrid}; use servicepoint::{DataRef, Grid};
use std::convert::Into; use std::convert::Into;
use std::intrinsics::transmute; use std::intrinsics::transmute;
use std::ptr::NonNull; use std::ptr::NonNull;
/// see [Brightness::MIN] /// see [servicepoint::Brightness::MIN]
pub const SP_BRIGHTNESS_MIN: u8 = 0; pub const SP_BRIGHTNESS_MIN: u8 = 0;
/// see [Brightness::MAX] /// see [servicepoint::Brightness::MAX]
pub const SP_BRIGHTNESS_MAX: u8 = 11; pub const SP_BRIGHTNESS_MAX: u8 = 11;
/// Count of possible brightness values /// Count of possible brightness values
pub const SP_BRIGHTNESS_LEVELS: u8 = 12; pub const SP_BRIGHTNESS_LEVELS: u8 = 12;
@ -48,9 +48,9 @@ pub unsafe extern "C" fn sp_brightness_grid_new(
width: usize, width: usize,
height: usize, height: usize,
) -> NonNull<SPBrightnessGrid> { ) -> NonNull<SPBrightnessGrid> {
let result = Box::new(SPBrightnessGrid( let result = Box::new(SPBrightnessGrid(servicepoint::BrightnessGrid::new(
servicepoint::BrightnessGrid::new(width, height), width, height,
)); )));
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
@ -80,7 +80,7 @@ pub unsafe extern "C" fn sp_brightness_grid_load(
) -> NonNull<SPBrightnessGrid> { ) -> NonNull<SPBrightnessGrid> {
assert!(!data.is_null()); assert!(!data.is_null());
let data = std::slice::from_raw_parts(data, data_length); let data = std::slice::from_raw_parts(data, data_length);
let grid = PrimitiveGrid::load(width, height, data); let grid = servicepoint::ByteGrid::load(width, height, data);
let grid = servicepoint::BrightnessGrid::try_from(grid) let grid = servicepoint::BrightnessGrid::try_from(grid)
.expect("invalid brightness value"); .expect("invalid brightness value");
let result = Box::new(SPBrightnessGrid(grid)); let result = Box::new(SPBrightnessGrid(grid));
@ -203,8 +203,8 @@ pub unsafe extern "C" fn sp_brightness_grid_set(
value: u8, value: u8,
) { ) {
assert!(!brightness_grid.is_null()); assert!(!brightness_grid.is_null());
let brightness = let brightness = servicepoint::Brightness::try_from(value)
Brightness::try_from(value).expect("invalid brightness value"); .expect("invalid brightness value");
(*brightness_grid).0.set(x, y, brightness); (*brightness_grid).0.set(x, y, brightness);
} }
@ -232,8 +232,8 @@ pub unsafe extern "C" fn sp_brightness_grid_fill(
value: u8, value: u8,
) { ) {
assert!(!brightness_grid.is_null()); assert!(!brightness_grid.is_null());
let brightness = let brightness = servicepoint::Brightness::try_from(value)
Brightness::try_from(value).expect("invalid brightness value"); .expect("invalid brightness value");
(*brightness_grid).0.fill(brightness); (*brightness_grid).0.fill(brightness);
} }
@ -311,7 +311,7 @@ pub unsafe extern "C" fn sp_brightness_grid_unsafe_data_ref(
brightness_grid: *mut SPBrightnessGrid, brightness_grid: *mut SPBrightnessGrid,
) -> SPByteSlice { ) -> SPByteSlice {
assert!(!brightness_grid.is_null()); assert!(!brightness_grid.is_null());
assert_eq!(core::mem::size_of::<Brightness>(), 1); assert_eq!(core::mem::size_of::<servicepoint::Brightness>(), 1);
let data = (*brightness_grid).0.data_ref_mut(); let data = (*brightness_grid).0.data_ref_mut();
// this assumes more about the memory layout than rust guarantees. yikes! // this assumes more about the memory layout than rust guarantees. yikes!
let data: &mut [u8] = transmute(data); let data: &mut [u8] = transmute(data);

View file

@ -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<SPCharGrid> {
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<SPCharGrid> {
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<SPCharGrid> {
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()
}

View file

@ -4,11 +4,9 @@
use std::ptr::{null_mut, NonNull}; use std::ptr::{null_mut, NonNull};
use servicepoint::{Brightness, Origin};
use crate::{ use crate::{
SPBitVec, SPBitmap, SPBrightnessGrid, SPCompressionCode, SPCp437Grid, SPBitVec, SPBitmap, SPBrightnessGrid, SPCharGrid, SPCompressionCode,
SPPacket, SPCp437Grid, SPPacket,
}; };
/// A low-level display command. /// A low-level display command.
@ -164,11 +162,10 @@ pub unsafe extern "C" fn sp_command_fade_out() -> NonNull<SPCommand> {
pub unsafe extern "C" fn sp_command_brightness( pub unsafe extern "C" fn sp_command_brightness(
brightness: u8, brightness: u8,
) -> NonNull<SPCommand> { ) -> NonNull<SPCommand> {
let brightness = let brightness = servicepoint::Brightness::try_from(brightness)
Brightness::try_from(brightness).expect("invalid brightness"); .expect("invalid brightness");
let result = Box::new(SPCommand( let result =
servicepoint::Command::Brightness(brightness), Box::new(SPCommand(servicepoint::Command::Brightness(brightness)));
));
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
@ -198,9 +195,10 @@ pub unsafe extern "C" fn sp_command_char_brightness(
) -> NonNull<SPCommand> { ) -> NonNull<SPCommand> {
assert!(!grid.is_null()); assert!(!grid.is_null());
let byte_grid = *Box::from_raw(grid); let byte_grid = *Box::from_raw(grid);
let result = Box::new(SPCommand( let result = Box::new(SPCommand(servicepoint::Command::CharBrightness(
servicepoint::Command::CharBrightness(Origin::new(x, y), byte_grid.0), servicepoint::Origin::new(x, y),
)); byte_grid.0,
)));
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
@ -237,13 +235,11 @@ pub unsafe extern "C" fn sp_command_bitmap_linear(
) -> NonNull<SPCommand> { ) -> NonNull<SPCommand> {
assert!(!bit_vec.is_null()); assert!(!bit_vec.is_null());
let bit_vec = *Box::from_raw(bit_vec); let bit_vec = *Box::from_raw(bit_vec);
let result = Box::new(SPCommand( let result = Box::new(SPCommand(servicepoint::Command::BitmapLinear(
servicepoint::Command::BitmapLinear(
offset, offset,
bit_vec.into(), bit_vec.into(),
compression.try_into().expect("invalid compression code"), compression.try_into().expect("invalid compression code"),
), )));
));
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
@ -280,13 +276,11 @@ pub unsafe extern "C" fn sp_command_bitmap_linear_and(
) -> NonNull<SPCommand> { ) -> NonNull<SPCommand> {
assert!(!bit_vec.is_null()); assert!(!bit_vec.is_null());
let bit_vec = *Box::from_raw(bit_vec); let bit_vec = *Box::from_raw(bit_vec);
let result = Box::new(SPCommand( let result = Box::new(SPCommand(servicepoint::Command::BitmapLinearAnd(
servicepoint::Command::BitmapLinearAnd(
offset, offset,
bit_vec.into(), bit_vec.into(),
compression.try_into().expect("invalid compression code"), compression.try_into().expect("invalid compression code"),
), )));
));
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
@ -323,13 +317,11 @@ pub unsafe extern "C" fn sp_command_bitmap_linear_or(
) -> NonNull<SPCommand> { ) -> NonNull<SPCommand> {
assert!(!bit_vec.is_null()); assert!(!bit_vec.is_null());
let bit_vec = *Box::from_raw(bit_vec); let bit_vec = *Box::from_raw(bit_vec);
let result = Box::new(SPCommand( let result = Box::new(SPCommand(servicepoint::Command::BitmapLinearOr(
servicepoint::Command::BitmapLinearOr(
offset, offset,
bit_vec.into(), bit_vec.into(),
compression.try_into().expect("invalid compression code"), compression.try_into().expect("invalid compression code"),
), )));
));
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
@ -366,17 +358,15 @@ pub unsafe extern "C" fn sp_command_bitmap_linear_xor(
) -> NonNull<SPCommand> { ) -> NonNull<SPCommand> {
assert!(!bit_vec.is_null()); assert!(!bit_vec.is_null());
let bit_vec = *Box::from_raw(bit_vec); let bit_vec = *Box::from_raw(bit_vec);
let result = Box::new(SPCommand( let result = Box::new(SPCommand(servicepoint::Command::BitmapLinearXor(
servicepoint::Command::BitmapLinearXor(
offset, offset,
bit_vec.into(), bit_vec.into(),
compression.try_into().expect("invalid compression code"), compression.try_into().expect("invalid compression code"),
), )));
));
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
/// Show text on the screen. /// Show codepage 437 encoded text on the screen.
/// ///
/// The passed [SPCp437Grid] gets consumed. /// The passed [SPCp437Grid] gets consumed.
/// ///
@ -402,9 +392,43 @@ pub unsafe extern "C" fn sp_command_cp437_data(
) -> NonNull<SPCommand> { ) -> NonNull<SPCommand> {
assert!(!grid.is_null()); assert!(!grid.is_null());
let grid = *Box::from_raw(grid); let grid = *Box::from_raw(grid);
let result = Box::new(SPCommand( let result = Box::new(SPCommand(servicepoint::Command::Cp437Data(
servicepoint::Command::Cp437Data(Origin::new(x, y), grid.0), servicepoint::Origin::new(x, y),
)); grid.0,
)));
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<SPCommand> {
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)) NonNull::from(Box::leak(result))
} }
@ -437,15 +461,13 @@ pub unsafe extern "C" fn sp_command_bitmap_linear_win(
) -> NonNull<SPCommand> { ) -> NonNull<SPCommand> {
assert!(!bitmap.is_null()); assert!(!bitmap.is_null());
let byte_grid = (*Box::from_raw(bitmap)).0; let byte_grid = (*Box::from_raw(bitmap)).0;
let result = Box::new(SPCommand( let result = Box::new(SPCommand(servicepoint::Command::BitmapLinearWin(
servicepoint::Command::BitmapLinearWin( servicepoint::Origin::new(x, y),
Origin::new(x, y),
byte_grid, byte_grid,
compression_code compression_code
.try_into() .try_into()
.expect("invalid compression code"), .expect("invalid compression code"),
), )));
));
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }

View file

@ -2,9 +2,9 @@
//! //!
//! prefix `sp_cp437_grid_` //! prefix `sp_cp437_grid_`
use std::ptr::NonNull;
use crate::SPByteSlice; use crate::SPByteSlice;
use servicepoint::{DataRef, Grid}; use servicepoint::{DataRef, Grid};
use std::ptr::NonNull;
/// A C-wrapper for grid containing codepage 437 characters. /// A C-wrapper for grid containing codepage 437 characters.
/// ///
@ -41,9 +41,8 @@ pub unsafe extern "C" fn sp_cp437_grid_new(
width: usize, width: usize,
height: usize, height: usize,
) -> NonNull<SPCp437Grid> { ) -> NonNull<SPCp437Grid> {
let result = Box::new(SPCp437Grid( let result =
servicepoint::Cp437Grid::new(width, height), Box::new(SPCp437Grid(servicepoint::Cp437Grid::new(width, height)));
));
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }
@ -73,9 +72,9 @@ pub unsafe extern "C" fn sp_cp437_grid_load(
) -> NonNull<SPCp437Grid> { ) -> NonNull<SPCp437Grid> {
assert!(data.is_null()); assert!(data.is_null());
let data = std::slice::from_raw_parts(data, data_length); let data = std::slice::from_raw_parts(data, data_length);
let result = Box::new(SPCp437Grid( let result = Box::new(SPCp437Grid(servicepoint::Cp437Grid::load(
servicepoint::Cp437Grid::load(width, height, data), width, height, data,
)); )));
NonNull::from(Box::leak(result)) NonNull::from(Box::leak(result))
} }

View file

@ -25,20 +25,22 @@
//! } //! }
//! ``` //! ```
pub use crate::bitvec::*;
pub use crate::bitmap::*; pub use crate::bitmap::*;
pub use crate::bitvec::*;
pub use crate::brightness_grid::*; pub use crate::brightness_grid::*;
pub use crate::byte_slice::*; pub use crate::byte_slice::*;
pub use crate::char_grid::*;
pub use crate::command::*; pub use crate::command::*;
pub use crate::connection::*; pub use crate::connection::*;
pub use crate::constants::*; pub use crate::constants::*;
pub use crate::cp437_grid::*; pub use crate::cp437_grid::*;
pub use crate::packet::*; pub use crate::packet::*;
mod bitvec;
mod bitmap; mod bitmap;
mod bitvec;
mod brightness_grid; mod brightness_grid;
mod byte_slice; mod byte_slice;
mod char_grid;
mod command; mod command;
mod connection; mod connection;
mod constants; mod constants;

View file

@ -7,7 +7,7 @@ use std::ptr::{null_mut, NonNull};
use crate::SPCommand; use crate::SPCommand;
/// The raw packet /// The raw packet
pub struct SPPacket(pub(crate) servicepoint::packet::Packet); pub struct SPPacket(pub(crate) servicepoint::Packet);
/// Turns a [SPCommand] into a [SPPacket]. /// Turns a [SPCommand] into a [SPPacket].
/// The [SPCommand] gets consumed. /// The [SPCommand] gets consumed.
@ -59,12 +59,69 @@ pub unsafe extern "C" fn sp_packet_try_load(
) -> *mut SPPacket { ) -> *mut SPPacket {
assert!(!data.is_null()); assert!(!data.is_null());
let data = std::slice::from_raw_parts(data, length); 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(), Err(_) => null_mut(),
Ok(packet) => Box::into_raw(Box::new(SPPacket(packet))), Ok(packet) => Box::into_raw(Box::new(SPPacket(packet))),
} }
} }
/// 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<SPPacket> {
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 {
header: servicepoint::Header {
command_code,
a,
b,
c,
d,
},
payload,
};
let result = Box::new(SPPacket(packet));
NonNull::from(Box::leak(result))
}
/// Clones a [SPPacket]. /// Clones a [SPPacket].
/// ///
/// Will never return NULL. /// Will never return NULL.
@ -94,7 +151,7 @@ pub unsafe extern "C" fn sp_packet_clone(
/// ///
/// # Panics /// # Panics
/// ///
/// - when `sp_packet_free` is NULL /// - when `packet` is NULL
/// ///
/// # Safety /// # Safety
/// ///

View file

@ -20,7 +20,7 @@ uniffi = { version = "0.25.3" }
thiserror.workspace = true thiserror.workspace = true
[dependencies.servicepoint] [dependencies.servicepoint]
version = "0.12.0" version = "0.13.0"
path = "../servicepoint" path = "../servicepoint"
features = ["all_compressions"] features = ["all_compressions"]
@ -32,8 +32,8 @@ optional = true
[dependencies.uniffi-bindgen-go] [dependencies.uniffi-bindgen-go]
git = "https://github.com/NordSecurity/uniffi-bindgen-go.git" git = "https://github.com/NordSecurity/uniffi-bindgen-go.git"
# tag = "0.2.1+v0.25.0" # tag = "0.2.2+v0.25.0"
rev = "a77dc0462dc18d53846c758155ab4e0a42e5b240" rev = "ba23bab72f1a9bcc39ce81924d3d9265598e017c"
optional = true optional = true
[lints] [lints]

View file

@ -9,7 +9,7 @@
<PropertyGroup> <PropertyGroup>
<PackageId>ServicePoint</PackageId> <PackageId>ServicePoint</PackageId>
<Version>0.12.0</Version> <Version>0.13.0</Version>
<Authors>Repository Authors</Authors> <Authors>Repository Authors</Authors>
<Company>None</Company> <Company>None</Company>
<Product>ServicePoint</Product> <Product>ServicePoint</Product>

View file

@ -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 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")] [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 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( 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")] [DllImport("servicepoint_binding_uniffi")]
public static extern ushort uniffi_servicepoint_binding_uniffi_checksum_constructor_connection_new( 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}`"); 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(); var checksum = _UniFFILib.uniffi_servicepoint_binding_uniffi_checksum_constructor_connection_new();
if (checksum != 30445) { if (checksum != 30445) {
@ -2671,6 +2685,13 @@ public class Command: FFIObject<CommandSafeHandle>, 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)
));
}
} }

View file

@ -918,6 +918,9 @@ module UniFFILib
attach_function :uniffi_servicepoint_binding_uniffi_fn_constructor_command_hard_reset, attach_function :uniffi_servicepoint_binding_uniffi_fn_constructor_command_hard_reset,
[RustCallStatus.by_ref], [RustCallStatus.by_ref],
:pointer :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, attach_function :uniffi_servicepoint_binding_uniffi_fn_method_command_equals,
[:pointer, :pointer, RustCallStatus.by_ref], [:pointer, :pointer, RustCallStatus.by_ref],
:int8 :int8
@ -1188,6 +1191,9 @@ module UniFFILib
attach_function :uniffi_servicepoint_binding_uniffi_checksum_constructor_command_hard_reset, attach_function :uniffi_servicepoint_binding_uniffi_checksum_constructor_command_hard_reset,
[RustCallStatus.by_ref], [RustCallStatus.by_ref],
:uint16 :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, attach_function :uniffi_servicepoint_binding_uniffi_checksum_constructor_connection_new,
[RustCallStatus.by_ref], [RustCallStatus.by_ref],
:uint16 :uint16
@ -1817,6 +1823,15 @@ end
# and just create a new instance with the required pointer. # 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,)) return _uniffi_allocate(ServicepointBindingUniffi.rust_call(:uniffi_servicepoint_binding_uniffi_fn_constructor_command_hard_reset,))
end 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) def equals(other)

View file

@ -1,6 +1,6 @@
Gem::Specification.new do |s| Gem::Specification.new do |s|
s.name = "servicepoint" s.name = "servicepoint"
s.version = "0.12.0" s.version = "0.13.0"
s.summary = "" s.summary = ""
s.description = "" s.description = ""
s.authors = ["kaesaecracker"] s.authors = ["kaesaecracker"]

View file

@ -1,7 +1,7 @@
use servicepoint::{Grid, SeriesError}; use crate::cp437_grid::Cp437Grid;
use servicepoint::{Grid, SetValueSeriesError};
use std::convert::Into; use std::convert::Into;
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use crate::cp437_grid::Cp437Grid;
#[derive(uniffi::Object)] #[derive(uniffi::Object)]
pub struct CharGrid { pub struct CharGrid {
@ -107,7 +107,10 @@ impl CharGrid {
.unwrap() .unwrap()
.get_row(y as usize) .get_row(y as usize)
.map(String::from_iter) .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<String, CharGridError> { pub fn get_col(&self, x: u64) -> Result<String, CharGridError> {
@ -116,11 +119,16 @@ impl CharGrid {
.unwrap() .unwrap()
.get_col(x as usize) .get_col(x as usize)
.map(String::from_iter) .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> { pub fn to_cp437(&self) -> Arc<Cp437Grid> {
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<char, CharGridError> { fn str_to_char(value: String) -> Result<char, CharGridError> {
if value.len() != 1 { if value.len() != 1 {
return Err(CharGridError::StringNotOneChar { return Err(CharGridError::StringNotOneChar { value });
value,
});
} }
let value = value.chars().nth(0).unwrap(); let value = value.chars().nth(0).unwrap();
@ -143,16 +149,16 @@ impl CharGrid {
} }
} }
impl From<SeriesError> for CharGridError { impl From<SetValueSeriesError> for CharGridError {
fn from(e: SeriesError) -> Self { fn from(e: SetValueSeriesError) -> Self {
match e { match e {
SeriesError::OutOfBounds { index, size } => { SetValueSeriesError::OutOfBounds { index, size } => {
CharGridError::OutOfBounds { CharGridError::OutOfBounds {
index: index as u64, index: index as u64,
size: size as u64, size: size as u64,
} }
} }
SeriesError::InvalidLength { actual, expected } => { SetValueSeriesError::InvalidLength { actual, expected } => {
CharGridError::InvalidSeriesLength { CharGridError::InvalidSeriesLength {
actual: actual as u64, actual: actual as u64,
expected: expected as u64, expected: expected as u64,

View file

@ -1,6 +1,7 @@
use crate::bitmap::Bitmap; use crate::bitmap::Bitmap;
use crate::bitvec::BitVec; use crate::bitvec::BitVec;
use crate::brightness_grid::BrightnessGrid; use crate::brightness_grid::BrightnessGrid;
use crate::char_grid::CharGrid;
use crate::compression_code::CompressionCode; use crate::compression_code::CompressionCode;
use crate::cp437_grid::Cp437Grid; use crate::cp437_grid::Cp437Grid;
use crate::errors::ServicePointError; use crate::errors::ServicePointError;
@ -151,6 +152,18 @@ impl Command {
Self::internal_new(actual) Self::internal_new(actual)
} }
#[uniffi::constructor]
pub fn utf8_data(
offset_x: u64,
offset_y: u64,
grid: &Arc<CharGrid>,
) -> Arc<Self> {
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] #[uniffi::constructor]
pub fn clone(other: &Arc<Self>) -> Arc<Self> { pub fn clone(other: &Arc<Self>) -> Arc<Self> {
Self::internal_new(other.actual.clone()) Self::internal_new(other.actual.clone())

View file

@ -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 struct Constants {
pub tile_size: u64, pub tile_size: u64,
pub tile_width: u64, pub tile_width: u64,
@ -10,7 +12,7 @@ pub struct Constants {
#[uniffi::export] #[uniffi::export]
fn get_constants() -> Constants { fn get_constants() -> Constants {
Constants { Constants {
tile_size: servicepoint::TILE_SIZE as u64, tile_size: servicepoint::TILE_SIZE as u64,
tile_width: servicepoint::TILE_WIDTH as u64, tile_width: servicepoint::TILE_WIDTH as u64,
tile_height: servicepoint::TILE_HEIGHT as u64, tile_height: servicepoint::TILE_HEIGHT as u64,

View file

@ -1,6 +1,6 @@
use crate::char_grid::CharGrid;
use servicepoint::{DataRef, Grid}; use servicepoint::{DataRef, Grid};
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use crate::char_grid::CharGrid;
#[derive(uniffi::Object)] #[derive(uniffi::Object)]
pub struct Cp437Grid { pub struct Cp437Grid {
@ -72,6 +72,8 @@ impl Cp437Grid {
} }
pub fn to_utf8(&self) -> Arc<CharGrid> { pub fn to_utf8(&self) -> Arc<CharGrid> {
CharGrid::internal_new(servicepoint::CharGrid::from(&*self.actual.read().unwrap())) CharGrid::internal_new(servicepoint::CharGrid::from(
&*self.actual.read().unwrap(),
))
} }
} }

View file

@ -7,6 +7,6 @@ mod char_grid;
mod command; mod command;
mod compression_code; mod compression_code;
mod connection; mod connection;
mod constants;
mod cp437_grid; mod cp437_grid;
mod errors; mod errors;
mod constants;

View file

@ -7,11 +7,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1721727458, "lastModified": 1736429655,
"narHash": "sha256-r/xppY958gmZ4oTfLiHN0ZGuQ+RSTijDblVgVLFi1mw=", "narHash": "sha256-BwMekRuVlSB9C0QgwKMICiJ5EVbLGjfe4qyueyNQyGI=",
"owner": "nix-community", "owner": "nix-community",
"repo": "naersk", "repo": "naersk",
"rev": "3fb418eaf352498f6b6c30592e3beb63df42ef11", "rev": "0621e47bd95542b8e1ce2ee2d65d6a1f887a13ce",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -22,16 +22,16 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1730963269, "lastModified": 1736549401,
"narHash": "sha256-rz30HrFYCHiWEBCKHMffHbMdWJ35hEkcRVU0h7ms3x0=", "narHash": "sha256-ibkQrMHxF/7TqAYcQE+tOnIsSEzXmMegzyBWza6uHKM=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "83fb6c028368e465cd19bb127b86f971a5e41ebc", "rev": "1dab772dd4a68a7bba5d9460685547ff8e17d899",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "nixos", "owner": "nixos",
"ref": "nixos-24.05", "ref": "nixos-24.11",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }

View file

@ -2,7 +2,7 @@
description = "Flake for servicepoint-simulator"; description = "Flake for servicepoint-simulator";
inputs = { inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-24.05"; nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11";
naersk = { naersk = {
url = "github:nix-community/naersk"; url = "github:nix-community/naersk";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
@ -51,7 +51,7 @@
]; ];
buildInputs = with pkgs; [ buildInputs = with pkgs; [
xe xe
lzma xz
]; ];
makeExample = makeExample =
{ {