Merge branch 'utf8'
Some checks failed
Rust / build (push) Has been cancelled

This commit is contained in:
Vinzenz Schroeter 2025-01-16 21:33:01 +01:00
commit b6fa1b161e
52 changed files with 1927 additions and 789 deletions

View file

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

1
.gitignore vendored
View file

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

View file

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

View file

@ -16,12 +16,12 @@ crate-type = ["rlib"]
log = "0.4"
bitvec = "1.0"
flate2 = { version = "1.0", optional = true }
bzip2 = { version = "0.4", optional = true }
bzip2 = { version = "0.5", optional = true }
zstd = { version = "0.13", optional = true }
rust-lzma = { version = "0.6.0", optional = true }
rust-lzma = { version = "0.6", optional = true }
rand = { version = "0.8", optional = true }
tungstenite = { version = "0.24.0", optional = true }
once_cell = { version = "1.20.2", optional = true }
tungstenite = { version = "0.26", optional = true }
once_cell = { version = "1.20", optional = true }
thiserror.workspace = true
[features]

View file

@ -17,7 +17,7 @@ cargo add servicepoint
or
```toml
[dependencies]
servicepoint = "0.12.0"
servicepoint = "0.13.0"
```
## Examples
@ -39,8 +39,11 @@ Execute `cargo run --example` for a list of available examples and `cargo run --
## Note on stability
This library is still in early development.
You can absolutely use it, and it works, but expect minor breaking changes with every version bump.
This library can be used for creative project or just to play around with the display.
A decent coverage by unit tests prevents major problems and I also test this with my own projects, which mostly use up-to-date versions.
That being said, the API is still being worked on.
Expect minor breaking changes with every version bump.
Please specify the full version including patch in your Cargo.toml until 1.0 is released.
## Features

View file

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

View file

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

View file

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

View file

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

View file

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

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 bitvec::prelude::BitSlice;
use bitvec::slice::IterMut;
use crate::data_ref::DataRef;
use crate::BitVec;
use crate::*;
use ::bitvec::order::Msb0;
use ::bitvec::prelude::BitSlice;
use ::bitvec::slice::IterMut;
use crate::{BitVec, DataRef, Grid, PIXEL_HEIGHT, PIXEL_WIDTH};
/// A grid of pixels stored in packed bytes.
/// A fixed-size 2D grid of booleans.
///
/// The values are stored in packed bytes (8 values per byte) in the same order as used by the display for storing pixels.
/// This means that no conversion is necessary for sending the data to the display.
/// 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)]
pub struct Bitmap {
width: usize,
@ -26,7 +39,11 @@ impl Bitmap {
///
/// - when the width is not dividable by 8
pub fn new(width: usize, height: usize) -> Self {
assert_eq!(width % 8, 0);
assert_eq!(
width % 8,
0,
"width must be a multiple of 8, but is {width}"
);
Self {
width,
height,
@ -176,11 +193,38 @@ impl From<Bitmap> for Vec<u8> {
}
impl From<Bitmap> for BitVec {
/// Turns a [Bitmap] into the underlying [BitVec].
fn from(value: Bitmap) -> Self {
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> {
bitmap: &'t Bitmap,
row: usize,
@ -203,7 +247,7 @@ impl<'t> Iterator for IterRows<'t> {
#[cfg(test)]
mod tests {
use crate::{Bitmap, DataRef, Grid};
use crate::{BitVec, Bitmap, DataRef, Grid, ValueGrid};
#[test]
fn fill() {
@ -295,4 +339,24 @@ mod tests {
data[1] = 0x0F;
assert!(grid.get(7, 1));
}
#[test]
fn to_bitvec() {
let mut grid = Bitmap::new(8, 2);
grid.set(0, 0, true);
let bitvec: BitVec = grid.into();
assert_eq!(bitvec.as_raw_slice(), [0x80, 0x00]);
}
#[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")]
use rand::{
distributions::{Distribution, Standard},
@ -22,28 +20,6 @@ use rand::{
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)]
pub struct Brightness(u8);
/// A grid containing brightness values.
///
/// # Examples
///
/// ```rust
/// # use servicepoint::{Brightness, BrightnessGrid, Command, Connection, Grid, Origin};
/// let mut grid = BrightnessGrid::new(2,2);
/// grid.set(0, 0, Brightness::MIN);
/// grid.set(1, 1, Brightness::MIN);
///
/// # let connection = Connection::open("127.0.0.1:2342").unwrap();
/// connection.send(Command::CharBrightness(Origin::new(3, 7), grid)).unwrap()
/// ```
pub type BrightnessGrid = PrimitiveGrid<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 {
fn from(brightness: Brightness) -> Self {
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")]
impl Distribution<Brightness> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Brightness {
@ -137,7 +78,6 @@ impl Distribution<Brightness> for Standard {
#[cfg(test)]
mod tests {
use super::*;
use crate::DataRef;
#[test]
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]
fn saturating_convert() {
assert_eq!(Brightness::MAX, Brightness::saturating_from(100));
assert_eq!(Brightness(5), Brightness::saturating_from(5));
}
#[test]
fn saturating_load() {
assert_eq!(BrightnessGrid::load(2,2, &[Brightness::MAX, Brightness::MAX, Brightness::MIN, Brightness::MAX]),
BrightnessGrid::saturating_load(2,2, &[255u8, 23, 0, 42]));
}
}

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, PrimitiveGrid};
use crate::{Grid, SetValueSeriesError, TryLoadValueGridError, ValueGrid};
use std::string::FromUtf8Error;
/// 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 {
/// 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.
///
/// 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> {
Some(String::from_iter(self.get_col(x)?))
}
@ -15,42 +73,91 @@ impl CharGrid {
/// Copies a row from the grid as a String.
///
/// Returns [None] if y is out of bounds.
///
/// # Examples
///
/// ```
/// # use servicepoint::CharGrid;
/// let grid = CharGrid::from("ab\ncd");
/// let row = grid.get_row_str(0).unwrap(); // "ab"
/// ```
pub fn get_row_str(&self, y: usize) -> Option<String> {
Some(String::from_iter(self.get_row(y)?))
}
/// 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(
&mut self,
y: usize,
value: &str,
) -> Result<(), SeriesError> {
) -> Result<(), SetValueSeriesError> {
self.set_row(y, value.chars().collect::<Vec<_>>().as_ref())
}
/// Overwrites a column in the grid with a str.
///
/// Returns [SeriesError] if y is out of bounds or `row` is not of the correct size.
/// Returns [SetValueSeriesError] if y is out of bounds or `row` is not of the correct size.
///
/// # Examples
///
/// ```
/// # use servicepoint::CharGrid;
/// let mut grid = CharGrid::from("ab\ncd");
/// grid.set_col_str(0, "ef").unwrap();
/// ```
pub fn set_col_str(
&mut self,
x: usize,
value: &str,
) -> Result<(), SeriesError> {
) -> Result<(), SetValueSeriesError> {
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 {
fn from(value: &str) -> Self {
let value = value.replace("\r\n", "\n");
let mut lines = value
.split('\n')
.map(move |line| line.trim_end())
.collect::<Vec<_>>();
let width =
lines.iter().fold(0, move |a, x| std::cmp::max(a, x.len()));
let mut lines = value.split('\n').collect::<Vec<_>>();
let width = lines
.iter()
.fold(0, move |a, x| std::cmp::max(a, x.chars().count()));
while lines.last().is_some_and(move |line| line.is_empty()) {
_ = lines.pop();
@ -73,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 {
/// Converts a [CharGrid] into a [String].
///
/// Rows are separated by '\n'.
///
/// # Examples
///
/// ```rust
/// # use servicepoint::CharGrid;
/// let grid = CharGrid::from("ab\ncd");
/// let string = String::from(grid);
/// let grid = CharGrid::from(string);
/// ```
fn from(value: &CharGrid) -> Self {
value
.iter_rows()
.map(move |chars| {
chars
.collect::<String>()
.replace('\0', " ")
.trim_end()
.to_string()
})
.collect::<Vec<_>>()
.map(String::from_iter)
.collect::<Vec<String>>()
.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)]
mod test {
use super::*;
use crate::Grid;
#[test]
fn col_str() {
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.set_row_str(1, "abc"),
Err(SeriesError::InvalidLength {
Err(SetValueSeriesError::InvalidLength {
expected: 2,
actual: 3
})
@ -120,10 +264,35 @@ mod test {
#[test]
fn str_to_char_grid() {
let original = "Hello\r\nWorld!\n...\n";
// conversion with .to_string() covers one more line
let original = "Hello\r\nWorld!\n...\n".to_string();
let grid = CharGrid::from(original);
assert_eq!(3, grid.height());
let actual = String::from(&grid);
assert_eq!("Hello\nWorld!\n...", actual);
assert_eq!("Hello\0\nWorld!\n...\0\0\0", String::from(grid));
}
#[test]
fn round_trip_bytes() {
let grid = CharGrid::from("Hello\0\nWorld!\n...\0\0\0");
let bytes: Vec<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::{
command_code::CommandCode,
compression::into_decompressed,
packet::{Header, Packet},
Bitmap, Brightness, BrightnessGrid, CompressionCode, Cp437Grid, Origin,
Pixels, PrimitiveGrid, BitVec, Tiles, TILE_SIZE,
};
use crate::command_code::CommandCode;
use crate::compression::into_decompressed;
use crate::value_grid::ValueGrid;
use crate::*;
/// Type alias for documenting the meaning of the u16 in enum values
pub type Offset = usize;
@ -41,7 +38,7 @@ pub type Offset = usize;
/// # Examples
///
/// ```rust
/// # use servicepoint::{Brightness, Command, Connection, packet::Packet};
/// use servicepoint::{Brightness, Command, Connection, Packet};
/// #
/// // create command
/// let command = Command::Brightness(Brightness::MAX);
@ -74,14 +71,29 @@ pub enum Command {
/// 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
///
/// ```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;
/// use servicepoint::{CharGrid, Cp437Grid};
/// let grid = CharGrid::from("Hello,\nWorld!");
/// let grid = Cp437Grid::from(&grid);
/// connection.send(Command::Cp437Data(Origin::ZERO, grid)).expect("send failed");
@ -234,6 +246,8 @@ pub enum TryFromPacketError {
/// The given brightness value is out of bounds
#[error("The given brightness value {0} is out of bounds.")]
InvalidBrightness(u8),
#[error(transparent)]
InvalidUtf8(#[from] std::string::FromUtf8Error),
}
impl TryFrom<Packet> for Command {
@ -269,6 +283,7 @@ impl TryFrom<Packet> for Command {
CommandCode::CharBrightness => {
Self::packet_into_char_brightness(&packet)
}
CommandCode::Utf8Data => Self::packet_into_utf8(&packet),
#[allow(deprecated)]
CommandCode::BitmapLegacy => Ok(Command::BitmapLegacy),
CommandCode::BitmapLinear => {
@ -426,8 +441,7 @@ impl Command {
payload,
} = packet;
let grid =
PrimitiveGrid::load(*width as usize, *height as usize, payload);
let grid = ValueGrid::load(*width as usize, *height as usize, payload);
let grid = match BrightnessGrid::try_from(grid) {
Ok(grid) => grid,
Err(val) => return Err(TryFromPacketError::InvalidBrightness(val)),
@ -489,18 +503,37 @@ impl Command {
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)]
mod tests {
use crate::command::TryFromPacketError;
use crate::command_code::CommandCode;
use crate::{
bitvec::prelude::BitVec,
command::TryFromPacketError,
command_code::CommandCode,
origin::Pixels,
packet::{Header, Packet},
Bitmap, Brightness, BrightnessGrid, Command, CompressionCode, Origin,
PrimitiveGrid,
BitVec, Bitmap, Brightness, BrightnessGrid, CharGrid, Command,
CompressionCode, Cp437Grid, Header, Origin, Packet, Pixels,
};
fn round_trip(original: Command) {
@ -556,16 +589,18 @@ mod tests {
fn round_trip_char_brightness() {
round_trip(Command::CharBrightness(
Origin::new(5, 2),
PrimitiveGrid::new(7, 5),
BrightnessGrid::new(7, 5),
));
}
#[test]
fn round_trip_cp437_data() {
round_trip(Command::Cp437Data(
Origin::new(5, 2),
PrimitiveGrid::new(7, 5),
));
round_trip(Command::Cp437Data(Origin::new(5, 2), Cp437Grid::new(7, 5)));
}
#[test]
fn round_trip_utf8_data() {
round_trip(Command::Utf8Data(Origin::new(5, 2), CharGrid::new(7, 5)));
}
#[test]

View file

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

View file

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

View file

@ -107,9 +107,7 @@ impl Connection {
let request = ClientRequestBuilder::new(uri).into_client_request()?;
let (sock, _) = connect(request)?;
Ok(Self::WebSocket(std::sync::Mutex::new(
sock,
)))
Ok(Self::WebSocket(std::sync::Mutex::new(sock)))
}
/// Send something packet-like to the display. Usually this is in the form of a Command.
@ -144,7 +142,7 @@ impl Connection {
Connection::WebSocket(socket) => {
let mut socket = socket.lock().unwrap();
socket
.send(tungstenite::Message::Binary(data))
.send(tungstenite::Message::Binary(data.into()))
.map_err(SendError::WebsocketError)
}
Connection::Fake => {
@ -159,9 +157,7 @@ impl Drop for Connection {
fn drop(&mut self) {
#[cfg(feature = "protocol_websocket")]
if let Connection::WebSocket(sock) = self {
_ = sock
.try_lock()
.map(move |mut sock| sock.close(None));
_ = sock.try_lock().map(move |mut sock| sock.close(None));
}
}
}
@ -169,7 +165,6 @@ impl Drop for Connection {
#[cfg(test)]
mod tests {
use super::*;
use crate::packet::*;
#[test]
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,153 +1,54 @@
//! 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;
/// A grid containing codepage 437 characters.
/// Contains functions to convert between UTF-8 and Codepage 437.
///
/// The encoding is currently not enforced.
pub type Cp437Grid = PrimitiveGrid<u8>;
/// See <https://en.wikipedia.org/wiki/Code_page_437#Character_set>
pub struct Cp437Converter;
/// 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)
}
}
#[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', '♂', '♀', '♪', '♫', '☼',
/* 1X */ '►', '◄', '↕', '‼', '¶', '§', '▬', '↨', '↑', '↓', '→', '←', '∟', '↔', '▲', '▼',
/* 2X */ ' ', '!', '"', '#', '$', '%', '&', '\'','(', ')', '*', '+', ',', '-', '.', '/',
/* 3X */ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?',
/* 4X */ '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
/* 5X */ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\',']', '^', '_',
/* 6X */ '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
/* 7X */ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', '⌂',
/* 8X */ 'Ç', 'ü', 'é', 'â', 'ä', 'à', 'å', 'ç', 'ê', 'ë', 'è', 'ï', 'î', 'ì', 'Ä', 'Å',
/* 9X */ 'É', 'æ', 'Æ', 'ô', 'ö', 'ò', 'û', 'ù', 'ÿ', 'Ö', 'Ü', '¢', '£', '¥', '₧', 'ƒ',
/* AX */ 'á', 'í', 'ó', 'ú', 'ñ', 'Ñ', 'ª', 'º', '¿', '⌐', '¬', '½', '¼', '¡', '«', '»',
/* BX */ '░', '▒', '▓', '│', '┤', '╡', '╢', '╖', '╕', '╣', '║', '╗', '╝', '╜', '╛', '┐',
/* CX */ '└', '┴', '┬', '├', '─', '┼', '╞', '╟', '╚', '╔', '╩', '╦', '╠', '═', '╬', '╧',
/* DX */ '╨', '╤', '╥', '╙', '╘', '╒', '╓', '╫', '╪', '┘', '┌', '█', '▄', '▌', '▐', '▀',
/* EX */ 'α', 'ß', 'Γ', 'π', 'Σ', 'σ', 'µ', 'τ', 'Φ', 'Θ', 'Ω', 'δ', '∞', 'φ', 'ε', '∩',
/* FX */ '≡', '±', '≥', '≤', '⌠', '⌡', '÷', '≈', '°', '∙', '·', '√', 'ⁿ', '²', '■', ' ',
];
static UTF8_TO_CP437: once_cell::sync::Lazy<HashMap<char, u8>> =
once_cell::sync::Lazy::new(|| {
let pairs = CP437_TO_UTF8
.iter()
.enumerate()
.map(move |(index, char)| (*char, index as u8));
HashMap::from_iter(pairs)
});
/// An array of 256 elements, mapping most of the CP437 values to UTF-8 characters
///
/// Mostly follows CP437, except 0x0A, which is kept for use as line ending.
///
/// See <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]
const CP437_TO_UTF8: [char; 256] = [
/* 0X */ '\0', '☺', '☻', '♥', '♦', '♣', '♠', '•', '◘', '○', '\n', '♂', '♀', '♪', '♫', '☼',
/* 1X */ '►', '◄', '↕', '‼', '¶', '§', '▬', '↨', '↑', '↓', '→', '←', '∟', '↔', '▲', '▼',
/* 2X */ ' ', '!', '"', '#', '$', '%', '&', '\'','(', ')', '*', '+', ',', '-', '.', '/',
/* 3X */ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?',
/* 4X */ '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
/* 5X */ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\',']', '^', '_',
/* 6X */ '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
/* 7X */ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', '⌂',
/* 8X */ 'Ç', 'ü', 'é', 'â', 'ä', 'à', 'å', 'ç', 'ê', 'ë', 'è', 'ï', 'î', 'ì', 'Ä', 'Å',
/* 9X */ 'É', 'æ', 'Æ', 'ô', 'ö', 'ò', 'û', 'ù', 'ÿ', 'Ö', 'Ü', '¢', '£', '¥', '₧', 'ƒ',
/* AX */ 'á', 'í', 'ó', 'ú', 'ñ', 'Ñ', 'ª', 'º', '¿', '⌐', '¬', '½', '¼', '¡', '«', '»',
/* BX */ '░', '▒', '▓', '│', '┤', '╡', '╢', '╖', '╕', '╣', '║', '╗', '╝', '╜', '╛', '┐',
/* CX */ '└', '┴', '┬', '├', '─', '┼', '╞', '╟', '╚', '╔', '╩', '╦', '╠', '═', '╬', '╧',
/* DX */ '╨', '╤', '╥', '╙', '╘', '╒', '╓', '╫', '╪', '┘', '┌', '█', '▄', '▌', '▐', '▀',
/* EX */ 'α', 'ß', 'Γ', 'π', 'Σ', 'σ', 'µ', 'τ', 'Φ', 'Θ', 'Ω', 'δ', '∞', 'φ', 'ε', '∩',
/* FX */ '≡', '±', '≥', '≤', '⌠', '⌡', '÷', '≈', '°', '∙', '·', '√', 'ⁿ', '²', '■', ' ',
];
static UTF8_TO_CP437: once_cell::sync::Lazy<HashMap<char, u8>> =
once_cell::sync::Lazy::new(|| {
let pairs = CP437_TO_UTF8
.iter()
.enumerate()
.map(move |(index, char)| (*char, index as u8));
HashMap::from_iter(pairs)
});
impl Cp437Converter {
const MISSING_CHAR_CP437: u8 = 0x3F; // '?'
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.
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.
@ -159,66 +60,20 @@ mod feature_cp437 {
///
/// Characters that are not available are mapped to '?'.
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.
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)]
mod tests {
use super::*;
#[test]
fn load_ascii_nowrap() {
let chars = ['H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd']
.map(move |c| c as u8);
let expected = Cp437Grid::load(5, 2, &chars);
let actual = Cp437Grid::load_ascii("Hello,\nWorld!", 5, false).unwrap();
// comma will be removed because line is too long and wrap is off
assert_eq!(actual, expected);
}
#[test]
fn load_ascii_wrap() {
let chars = ['H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd']
.map(move |c| c as u8);
let expected = Cp437Grid::load(5, 2, &chars);
let actual = Cp437Grid::load_ascii("HelloWorld", 5, true).unwrap();
// line break will be added
assert_eq!(actual, expected);
}
#[test]
fn load_ascii_invalid() {
assert_eq!(
Err(InvalidCharError {
char: '🥶',
index: 2
}),
Cp437Grid::load_ascii("?#🥶42", 3, false)
);
}
}
#[cfg(test)]
#[cfg(feature = "cp437")]
mod tests_feature_cp437 {
use super::*;
use crate::CharGrid;
#[test]
fn round_trip_cp437() {
let utf8 = CharGrid::load(2, 2, &['Ä', 'x', '\n', '$']);
let cp437 = Cp437Grid::from(&utf8);
let actual = CharGrid::from(&cp437);
assert_eq!(actual, utf8);
}
#[test]
fn convert_str() {
@ -245,13 +100,16 @@ mod tests_feature_cp437 {
dx Σ x²·δx
"#;
let cp437 = str_to_cp437(utf8);
let actual = cp437_to_str(&*cp437);
let cp437 = Cp437Converter::str_to_cp437(utf8);
let actual = Cp437Converter::cp437_to_str(&*cp437);
assert_eq!(utf8, actual)
}
#[test]
fn convert_invalid() {
assert_eq!(cp437_to_char(char_to_cp437('😜')), '?');
assert_eq!(
Cp437Converter::cp437_to_char(Cp437Converter::char_to_cp437('😜')),
'?'
);
}
}

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.
//! With a connection, you can send [Command]s.
//! When received, the display will update the state of the pixels.
//! When received, the display will update the state of its pixels.
//!
//! # Examples
//!
//! ```rust
//! use servicepoint::{Command, CompressionCode, Grid, Bitmap};
//! ### Clear display
//!
//! let connection = servicepoint::Connection::open("127.0.0.1:2342")
//! ```rust
//! use servicepoint::{Connection, Command};
//!
//! // establish a connection
//! let connection = Connection::open("127.0.0.1:2342")
//! .expect("connection failed");
//!
//! // turn off all pixels on display
@ -17,6 +20,8 @@
//! .expect("send failed");
//! ```
//!
//! ### Set all pixels to on
//!
//! ```rust
//! # use servicepoint::{Command, CompressionCode, Grid, Bitmap};
//! # let connection = servicepoint::Connection::open("127.0.0.1:2342").expect("connection failed");
@ -34,114 +39,63 @@
//! // send command to display
//! connection.send(command).expect("send failed");
//! ```
//!
//! ### Send text
//!
//! ```rust
//! # use servicepoint::{Command, CompressionCode, Grid, Bitmap, CharGrid};
//! # let connection = servicepoint::Connection::open("127.0.0.1:2342").expect("connection failed");
//! // create a text grid
//! let mut grid = CharGrid::from("Hello\nCCCB?");
//! // modify the grid
//! grid.set(grid.width() - 1, 1, '!');
//! // create the command to send the data
//! let command = Command::Utf8Data(servicepoint::Origin::ZERO, grid);
//! // send command to display
//! connection.send(command).expect("send failed");
//! ```
use std::time::Duration;
pub use bitvec;
pub use crate::bit_vec::{bitvec, BitVec};
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::command::{Command, Offset};
pub use crate::compression_code::CompressionCode;
pub use crate::connection::Connection;
pub use crate::cp437::Cp437Grid;
pub use crate::constants::*;
pub use crate::cp437::Cp437Converter;
pub use crate::cp437_grid::Cp437Grid;
pub use crate::data_ref::DataRef;
pub use crate::grid::Grid;
pub use crate::origin::{Origin, Pixels, Tiles};
pub use crate::primitive_grid::{PrimitiveGrid, SeriesError};
/// An alias for the specific type of [bitvec::prelude::BitVec] used.
pub type BitVec = bitvec::prelude::BitVec<u8, bitvec::prelude::Msb0>;
pub use crate::packet::{Header, Packet, Payload};
pub use crate::value_grid::{
IterGridRows, SetValueSeriesError, TryLoadValueGridError, Value, ValueGrid,
};
mod bit_vec;
mod bitmap;
mod brightness;
mod brightness_grid;
mod byte_grid;
mod char_grid;
mod command;
mod command_code;
mod compression;
mod compression_code;
mod connection;
pub mod cp437;
mod constants;
mod cp437_grid;
mod data_ref;
mod grid;
mod origin;
pub mod packet;
mod primitive_grid;
mod packet;
mod value_grid;
/// size of a single tile in one dimension
pub const TILE_SIZE: usize = 8;
/// Display tile count in the x-direction
///
/// # Examples
///
/// ```rust
/// # use servicepoint::{Cp437Grid, TILE_HEIGHT, TILE_WIDTH};
/// let grid = Cp437Grid::new(TILE_WIDTH, TILE_HEIGHT);
/// ```
pub const TILE_WIDTH: usize = 56;
/// Display tile count in the y-direction
///
/// # Examples
///
/// ```rust
/// # use servicepoint::{Cp437Grid, TILE_HEIGHT, TILE_WIDTH};
/// let grid = Cp437Grid::new(TILE_WIDTH, TILE_HEIGHT);
/// ```
pub const TILE_HEIGHT: usize = 20;
/// Display width in pixels
///
/// # Examples
///
/// ```rust
/// # use servicepoint::{PIXEL_HEIGHT, PIXEL_WIDTH, Bitmap};
/// let grid = Bitmap::new(PIXEL_WIDTH, PIXEL_HEIGHT);
/// ```
pub const PIXEL_WIDTH: usize = TILE_WIDTH * TILE_SIZE;
/// Display height in pixels
///
/// # Examples
///
/// ```rust
/// # use servicepoint::{PIXEL_HEIGHT, PIXEL_WIDTH, Bitmap};
/// let grid = Bitmap::new(PIXEL_WIDTH, PIXEL_HEIGHT);
/// ```
pub const PIXEL_HEIGHT: usize = TILE_HEIGHT * TILE_SIZE;
/// pixel count on whole screen
pub const PIXEL_COUNT: usize = PIXEL_WIDTH * PIXEL_HEIGHT;
/// Actual hardware limit is around 28-29ms/frame. Rounded up for less dropped packets.
///
/// # Examples
///
/// ```rust
/// # use std::time::Instant;
/// # use servicepoint::{Command, CompressionCode, FRAME_PACING, Origin, Bitmap};
/// # let connection = servicepoint::Connection::Fake;
/// # let pixels = Bitmap::max_sized();
/// loop {
/// let start = Instant::now();
///
/// // Change pixels here
///
/// connection.send(Command::BitmapLinearWin(
/// Origin::new(0,0),
/// pixels,
/// CompressionCode::Lzma
/// ))
/// .expect("send failed");
///
/// // warning: will crash if resulting duration is negative, e.g. when resuming from standby
/// std::thread::sleep(FRAME_PACING - start.elapsed());
/// # break; // prevent doctest from hanging
/// }
/// ```
pub const FRAME_PACING: Duration = Duration::from_millis(30);
#[cfg(feature = "cp437")]
mod cp437;
// include README.md in doctest
#[doc = include_str!("../README.md")]

View file

@ -7,7 +7,7 @@
//! Converting a packet to a command and back:
//!
//! ```rust
//! use servicepoint::{Command, packet::Packet};
//! use servicepoint::{Command, Packet};
//! # let command = Command::Clear;
//! let packet: Packet = command.into();
//! let command: Command = Command::try_from(packet).expect("could not read command from packet");
@ -16,20 +16,20 @@
//! Converting a packet to bytes and back:
//!
//! ```rust
//! use servicepoint::{Command, packet::Packet};
//! use servicepoint::{Command, Packet};
//! # let command = Command::Clear;
//! # let packet: Packet = command.into();
//! let bytes: Vec<u8> = packet.into();
//! let packet = Packet::try_from(bytes).expect("could not read packet from bytes");
//! ```
use std::mem::size_of;
use crate::command_code::CommandCode;
use crate::compression::into_compressed;
use crate::{
command_code::CommandCode, Bitmap, Command, CompressionCode, Grid, Offset,
Origin, Pixels, Tiles, TILE_SIZE,
Bitmap, Command, CompressionCode, Grid, Offset, Origin, Pixels, Tiles,
TILE_SIZE,
};
use std::mem::size_of;
/// A raw header.
///
@ -209,6 +209,9 @@ impl From<Command> for Packet {
grid,
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 crate::{DataRef, Grid};
use crate::*;
pub trait PrimitiveGridType: Sized + Default + Copy + Clone {}
impl<T: Sized + Default + Copy + Clone> PrimitiveGridType for T {}
/// A type that can be stored in a [ValueGrid], e.g. [char], [u8].
pub trait Value: Sized + Default + Copy + Clone + Debug {}
impl<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)]
pub struct PrimitiveGrid<T: PrimitiveGridType> {
pub struct ValueGrid<T: Value> {
width: usize,
height: usize,
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
#[derive(thiserror::Error, Debug, PartialEq)]
pub enum SeriesError {
pub enum SetValueSeriesError {
#[error("The index {index} was out of bounds for size {size}")]
/// The index {index} was out of bounds for size {size}
OutOfBounds {
@ -34,15 +41,15 @@ pub enum SeriesError {
},
}
impl<T: PrimitiveGridType> PrimitiveGrid<T> {
/// Creates a new [PrimitiveGrid] with the specified dimensions.
impl<T: Value> ValueGrid<T> {
/// Creates a new [ValueGrid] with the specified dimensions.
///
/// # Arguments
///
/// - width: size in x-direction
/// - height: size in y-direction
///
/// returns: [PrimitiveGrid] initialized to default value.
/// returns: [ValueGrid] initialized to default value.
pub fn new(width: usize, height: usize) -> Self {
Self {
data: vec![Default::default(); width * height],
@ -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
///
/// - when the dimensions and data size do not match exactly.
#[must_use]
pub fn load(width: usize, height: usize, data: &[T]) -> Self {
assert_eq!(width * height, data.len());
assert_eq!(
width * height,
data.len(),
"dimension mismatch for data {data:?}"
);
Self {
data: Vec::from(data),
width,
@ -68,12 +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:
/// ```
/// # use servicepoint::{PrimitiveGrid, Grid};
/// # let grid = PrimitiveGrid::<u8>::new(2,2);
/// # use servicepoint::{ByteGrid, Grid};
/// # let grid = ByteGrid::new(2,2);
/// for y in 0..grid.height() {
/// for x in 0..grid.width() {
/// grid.get(x, y);
@ -84,9 +135,9 @@ impl<T: PrimitiveGridType> PrimitiveGrid<T> {
self.data.iter()
}
/// Iterate over all rows in [PrimitiveGrid] top to bottom.
pub fn iter_rows(&self) -> IterRows<T> {
IterRows {
/// Iterate over all rows in [ValueGrid] top to bottom.
pub fn iter_rows(&self) -> IterGridRows<T> {
IterGridRows {
byte_grid: self,
row: 0,
}
@ -132,7 +183,7 @@ impl<T: PrimitiveGridType> PrimitiveGrid<T> {
}
}
/// Convert between PrimitiveGrid types.
/// Convert between ValueGrid types.
///
/// 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].
/// ```
/// # fn foo(grid: &mut PrimitiveGrid<u8>) {}
/// # use servicepoint::{Brightness, BrightnessGrid, Command, Origin, PrimitiveGrid, TILE_HEIGHT, TILE_WIDTH};
/// let mut grid: PrimitiveGrid<u8> = PrimitiveGrid::new(TILE_WIDTH, TILE_HEIGHT);
/// # fn foo(grid: &mut ByteGrid) {}
/// # use servicepoint::{Brightness, BrightnessGrid, ByteGrid, Command, Origin, TILE_HEIGHT, TILE_WIDTH};
/// let mut grid: ByteGrid = ByteGrid::new(TILE_WIDTH, TILE_HEIGHT);
/// foo(&mut grid);
/// let grid: BrightnessGrid = grid.map(Brightness::saturating_from);
/// let command = Command::CharBrightness(Origin::ZERO, grid);
/// ```
/// [Brightness]: [crate::Brightness]
/// [Command]: [crate::Command]
pub fn map<TConverted, F>(&self, f: F) -> PrimitiveGrid<TConverted>
pub fn map<TConverted, F>(&self, f: F) -> ValueGrid<TConverted>
where
TConverted: PrimitiveGridType,
TConverted: Value,
F: Fn(T) -> TConverted,
{
let data = self
@ -159,7 +210,7 @@ impl<T: PrimitiveGridType> PrimitiveGrid<T> {
.iter()
.map(|elem| f(*elem))
.collect::<Vec<_>>();
PrimitiveGrid::load(self.width(), self.height(), &data)
ValueGrid::load(self.width(), self.height(), &data)
}
/// Copies a row from the grid.
@ -185,9 +236,13 @@ impl<T: PrimitiveGridType> PrimitiveGrid<T> {
/// Overwrites a column in the grid.
///
/// Returns [Err] if x is out of bounds or `col` is not of the correct size.
pub fn set_col(&mut self, x: usize, col: &[T]) -> Result<(), SeriesError> {
pub fn set_col(
&mut self,
x: usize,
col: &[T],
) -> Result<(), SetValueSeriesError> {
if col.len() != self.height() {
return Err(SeriesError::InvalidLength {
return Err(SetValueSeriesError::InvalidLength {
expected: self.height(),
actual: col.len(),
});
@ -204,7 +259,7 @@ impl<T: PrimitiveGridType> PrimitiveGrid<T> {
{
Ok(())
} else {
Err(SeriesError::OutOfBounds {
Err(SetValueSeriesError::OutOfBounds {
index: x,
size: width,
})
@ -214,10 +269,14 @@ impl<T: PrimitiveGridType> PrimitiveGrid<T> {
/// Overwrites a row in the grid.
///
/// Returns [Err] if y is out of bounds or `row` is not of the correct size.
pub fn set_row(&mut self, y: usize, row: &[T]) -> Result<(), SeriesError> {
pub fn set_row(
&mut self,
y: usize,
row: &[T],
) -> Result<(), SetValueSeriesError> {
let width = self.width();
if row.len() != width {
return Err(SeriesError::InvalidLength {
return Err(SetValueSeriesError::InvalidLength {
expected: width,
actual: row.len(),
});
@ -226,7 +285,7 @@ impl<T: PrimitiveGridType> PrimitiveGrid<T> {
let chunk = match self.data.chunks_exact_mut(width).nth(y) {
Some(row) => row,
None => {
return Err(SeriesError::OutOfBounds {
return Err(SetValueSeriesError::OutOfBounds {
size: self.height(),
index: y,
})
@ -238,8 +297,16 @@ impl<T: PrimitiveGridType> PrimitiveGrid<T> {
}
}
impl<T: PrimitiveGridType> Grid<T> for PrimitiveGrid<T> {
/// Sets the value of the cell at the specified position in the `PrimitiveGrid.
/// Errors that can occur when loading a grid
#[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
///
@ -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
fn data_ref_mut(&mut self) -> &mut [T] {
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.
fn from(value: PrimitiveGrid<T>) -> Self {
fn from(value: ValueGrid<T>) -> Self {
value.data
}
}
pub struct IterRows<'t, T: PrimitiveGridType> {
byte_grid: &'t PrimitiveGrid<T>,
/// An iterator iver the rows in a [ValueGrid]
pub struct IterGridRows<'t, T: Value> {
byte_grid: &'t ValueGrid<T>,
row: usize,
}
impl<'t, T: PrimitiveGridType> Iterator for IterRows<'t, T> {
impl<'t, T: Value> Iterator for IterGridRows<'t, T> {
type Item = Iter<'t, T>;
fn next(&mut self) -> Option<Self::Item> {
@ -323,11 +391,14 @@ impl<'t, T: PrimitiveGridType> Iterator for IterRows<'t, T> {
#[cfg(test)]
mod tests {
use crate::{DataRef, Grid, PrimitiveGrid, SeriesError};
use crate::{
value_grid::{SetValueSeriesError, ValueGrid},
*,
};
#[test]
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]);
grid.fill(42);
@ -336,7 +407,7 @@ mod tests {
#[test]
fn get_set() {
let mut grid = PrimitiveGrid::new(2, 2);
let mut grid = ValueGrid::new(2, 2);
assert_eq!(grid.get(0, 0), 0);
assert_eq!(grid.get(1, 1), 0);
@ -351,7 +422,7 @@ mod tests {
#[test]
fn load() {
let mut grid = PrimitiveGrid::new(2, 3);
let mut grid = ValueGrid::new(2, 3);
for x in 0..grid.width {
for y in 0..grid.height {
grid.set(x, y, (x + y) as u8);
@ -362,13 +433,13 @@ mod tests {
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]);
}
#[test]
fn mut_data_ref() {
let mut vec = PrimitiveGrid::new(2, 2);
let mut vec = ValueGrid::new(2, 2);
let data_ref = vec.data_ref_mut();
data_ref.copy_from_slice(&[1, 2, 3, 4]);
@ -379,7 +450,7 @@ mod tests {
#[test]
fn iter() {
let mut vec = PrimitiveGrid::new(2, 2);
let mut vec = ValueGrid::new(2, 2);
vec.set(1, 1, 5);
let mut iter = vec.iter();
@ -391,7 +462,7 @@ mod tests {
#[test]
fn iter_mut() {
let mut vec = PrimitiveGrid::new(2, 3);
let mut vec = ValueGrid::new(2, 3);
for (index, cell) in vec.iter_mut().enumerate() {
*cell = index as u8;
}
@ -401,7 +472,7 @@ mod tests {
#[test]
fn iter_rows() {
let vec = PrimitiveGrid::load(2, 3, &[0, 1, 1, 2, 2, 3]);
let vec = ValueGrid::load(2, 3, &[0, 1, 1, 2, 2, 3]);
for (y, row) in vec.iter_rows().enumerate() {
for (x, val) in row.enumerate() {
assert_eq!(*val, (x + y) as u8);
@ -412,20 +483,20 @@ mod tests {
#[test]
#[should_panic]
fn out_of_bounds_x() {
let mut vec = PrimitiveGrid::load(2, 2, &[0, 1, 2, 3]);
let mut vec = ValueGrid::load(2, 2, &[0, 1, 2, 3]);
vec.set(2, 1, 5);
}
#[test]
#[should_panic]
fn out_of_bounds_y() {
let vec = PrimitiveGrid::load(2, 2, &[0, 1, 2, 3]);
let vec = ValueGrid::load(2, 2, &[0, 1, 2, 3]);
vec.get(1, 2);
}
#[test]
fn ref_mut() {
let mut vec = PrimitiveGrid::load(2, 2, &[0, 1, 2, 3]);
let mut vec = ValueGrid::load(2, 2, &[0, 1, 2, 3]);
let top_left = vec.get_ref_mut(0, 0);
*top_left += 5;
@ -436,7 +507,7 @@ mod tests {
#[test]
fn optional() {
let mut grid = PrimitiveGrid::load(2, 2, &[0, 1, 2, 3]);
let mut grid = ValueGrid::load(2, 2, &[0, 1, 2, 3]);
grid.set_optional(0, 0, 5);
grid.set_optional(-1, 0, 8);
grid.set_optional(0, 8, 42);
@ -448,18 +519,18 @@ mod tests {
#[test]
fn col() {
let mut grid = PrimitiveGrid::load(2, 3, &[0, 1, 2, 3, 4, 5]);
let mut grid = ValueGrid::load(2, 3, &[0, 1, 2, 3, 4, 5]);
assert_eq!(grid.get_col(0), Some(vec![0, 2, 4]));
assert_eq!(grid.get_col(1), Some(vec![1, 3, 5]));
assert_eq!(grid.get_col(2), None);
assert_eq!(grid.set_col(0, &[5, 7, 9]), Ok(()));
assert_eq!(
grid.set_col(2, &[5, 7, 9]),
Err(SeriesError::OutOfBounds { size: 2, index: 2 })
Err(SetValueSeriesError::OutOfBounds { size: 2, index: 2 })
);
assert_eq!(
grid.set_col(0, &[5, 7]),
Err(SeriesError::InvalidLength {
Err(SetValueSeriesError::InvalidLength {
expected: 3,
actual: 2
})
@ -469,7 +540,7 @@ mod tests {
#[test]
fn row() {
let mut grid = PrimitiveGrid::load(2, 3, &[0, 1, 2, 3, 4, 5]);
let mut grid = ValueGrid::load(2, 3, &[0, 1, 2, 3, 4, 5]);
assert_eq!(grid.get_row(0), Some(vec![0, 1]));
assert_eq!(grid.get_row(2), Some(vec![4, 5]));
assert_eq!(grid.get_row(3), None);
@ -477,14 +548,23 @@ mod tests {
assert_eq!(grid.get_row(0), Some(vec![5, 7]));
assert_eq!(
grid.set_row(3, &[5, 7]),
Err(SeriesError::OutOfBounds { size: 3, index: 3 })
Err(SetValueSeriesError::OutOfBounds { size: 3, index: 3 })
);
assert_eq!(
grid.set_row(2, &[5, 7, 3]),
Err(SeriesError::InvalidLength {
Err(SetValueSeriesError::InvalidLength {
expected: 2,
actual: 3
})
);
}
#[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"
[dependencies.servicepoint]
version = "0.12.0"
version = "0.13.0"
path = "../servicepoint"
features = ["all_compressions"]

View file

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

View file

@ -14,12 +14,12 @@
#define SP_BRIGHTNESS_LEVELS 12
/**
* see [Brightness::MAX]
* see [servicepoint::Brightness::MAX]
*/
#define SP_BRIGHTNESS_MAX 11
/**
* see [Brightness::MIN]
* see [servicepoint::Brightness::MIN]
*/
#define SP_BRIGHTNESS_MIN 0
@ -131,6 +131,25 @@ typedef struct SPBitmap SPBitmap;
*/
typedef struct SPBrightnessGrid SPBrightnessGrid;
/**
* A C-wrapper for grid containing UTF-8 characters.
*
* As the rust [char] type is not FFI-safe, characters are passed in their UTF-32 form as 32bit unsigned integers.
*
* The encoding is enforced in most cases by the rust standard library
* and will panic when provided with illegal characters.
*
* # Examples
*
* ```C
* CharGrid grid = sp_char_grid_new(4, 3);
* sp_char_grid_fill(grid, '?');
* sp_char_grid_set(grid, 0, 0, '!');
* sp_char_grid_free(grid);
* ```
*/
typedef struct SPCharGrid SPCharGrid;
/**
* A low-level display command.
*
@ -367,6 +386,20 @@ SPBitmap *sp_bitmap_load(size_t width,
SPBitmap *sp_bitmap_new(size_t width,
size_t height);
/**
* Creates a new [SPBitmap] with a size matching the screen.
*
* returns: [SPBitmap] initialized to all pixels off. Will never return NULL.
*
* # Safety
*
* The caller has to make sure that:
*
* - the returned instance is freed in some way, either by using a consuming function or
* by explicitly calling [sp_bitmap_free].
*/
SPBitmap *sp_bitmap_new_screen_sized(void);
/**
* Sets the value of the specified position in the [SPBitmap].
*
@ -865,6 +898,196 @@ SPByteSlice sp_brightness_grid_unsafe_data_ref(SPBrightnessGrid *brightness_grid
*/
size_t sp_brightness_grid_width(const SPBrightnessGrid *brightness_grid);
/**
* Clones a [SPCharGrid].
*
* Will never return NULL.
*
* # Panics
*
* - when `char_grid` is NULL
*
* # Safety
*
* The caller has to make sure that:
*
* - `char_grid` points to a valid [SPCharGrid]
* - `char_grid` is not written to concurrently
* - the returned instance is freed in some way, either by using a consuming function or
* by explicitly calling `sp_char_grid_free`.
*/
SPCharGrid *sp_char_grid_clone(const SPCharGrid *char_grid);
/**
* Sets the value of all cells in the [SPCharGrid].
*
* # Arguments
*
* - `char_grid`: instance to write to
* - `value`: the value to set all cells to
*
* # Panics
*
* - when `char_grid` is NULL
*
* # Safety
*
* The caller has to make sure that:
*
* - `char_grid` points to a valid [SPCharGrid]
* - `char_grid` is not written to or read from concurrently
*/
void sp_char_grid_fill(SPCharGrid *char_grid, uint32_t value);
/**
* Deallocates a [SPCharGrid].
*
* # Panics
*
* - when `char_grid` is NULL
*
* # Safety
*
* The caller has to make sure that:
*
* - `char_grid` points to a valid [SPCharGrid]
* - `char_grid` is not used concurrently or after char_grid call
* - `char_grid` was not passed to another consuming function, e.g. to create a [SPCommand]
*
* [SPCommand]: [crate::SPCommand]
*/
void sp_char_grid_free(SPCharGrid *char_grid);
/**
* Gets the current value at the specified position.
*
* # Arguments
*
* - `char_grid`: instance to read from
* - `x` and `y`: position of the cell to read
*
* # Panics
*
* - when `char_grid` is NULL
* - when accessing `x` or `y` out of bounds
*
* # Safety
*
* The caller has to make sure that:
*
* - `char_grid` points to a valid [SPCharGrid]
* - `char_grid` is not written to concurrently
*/
uint32_t sp_char_grid_get(const SPCharGrid *char_grid, size_t x, size_t y);
/**
* Gets the height of the [SPCharGrid] instance.
*
* # Arguments
*
* - `char_grid`: instance to read from
*
* # Panics
*
* - when `char_grid` is NULL
*
* # Safety
*
* The caller has to make sure that:
*
* - `char_grid` points to a valid [SPCharGrid]
*/
size_t sp_char_grid_height(const SPCharGrid *char_grid);
/**
* Loads a [SPCharGrid] with the specified dimensions from the provided data.
*
* Will never return NULL.
*
* # Panics
*
* - when `data` is NULL
* - when the provided `data_length` does not match `height` and `width`
* - when `data` is not valid UTF-8
*
* # Safety
*
* The caller has to make sure that:
*
* - `data` points to a valid memory location of at least `data_length`
* bytes in size.
* - the returned instance is freed in some way, either by using a consuming function or
* by explicitly calling `sp_char_grid_free`.
*/
SPCharGrid *sp_char_grid_load(size_t width,
size_t height,
const uint8_t *data,
size_t data_length);
/**
* Creates a new [SPCharGrid] with the specified dimensions.
*
* returns: [SPCharGrid] initialized to 0. Will never return NULL.
*
* # Safety
*
* The caller has to make sure that:
*
* - the returned instance is freed in some way, either by using a consuming function or
* by explicitly calling `sp_char_grid_free`.
*/
SPCharGrid *sp_char_grid_new(size_t width,
size_t height);
/**
* Sets the value of the specified position in the [SPCharGrid].
*
* # Arguments
*
* - `char_grid`: instance to write to
* - `x` and `y`: position of the cell
* - `value`: the value to write to the cell
*
* returns: old value of the cell
*
* # Panics
*
* - when `char_grid` is NULL
* - when accessing `x` or `y` out of bounds
*
* # Safety
*
* The caller has to make sure that:
*
* - `char_grid` points to a valid [SPBitVec]
* - `char_grid` is not written to or read from concurrently
*
* [SPBitVec]: [crate::SPBitVec]
*/
void sp_char_grid_set(SPCharGrid *char_grid,
size_t x,
size_t y,
uint32_t value);
/**
* Gets the width of the [SPCharGrid] instance.
*
* # Arguments
*
* - `char_grid`: instance to read from
*
* # Panics
*
* - when `char_grid` is NULL
*
* # Safety
*
* The caller has to make sure that:
*
* - `char_grid` points to a valid [SPCharGrid]
*/
size_t sp_char_grid_width(const SPCharGrid *char_grid);
/**
* Set pixel data starting at the pixel offset on screen.
*
@ -1101,7 +1324,7 @@ SPCommand *sp_command_clear(void);
SPCommand *sp_command_clone(const SPCommand *command);
/**
* Show text on the screen.
* Show codepage 437 encoded text on the screen.
*
* The passed [SPCp437Grid] gets consumed.
*
@ -1201,6 +1424,30 @@ SPCommand *sp_command_hard_reset(void);
*/
SPCommand *sp_command_try_from_packet(SPPacket *packet);
/**
* Show UTF-8 encoded text on the screen.
*
* The passed [SPCharGrid] gets consumed.
*
* Returns: a new [servicepoint::Command::Utf8Data] instance. Will never return NULL.
*
* # Panics
*
* - when `grid` is null
*
* # Safety
*
* The caller has to make sure that:
*
* - `grid` points to a valid instance of [SPCharGrid]
* - `grid` is not used concurrently or after this call
* - the returned [SPCommand] instance is freed in some way, either by using a consuming function or
* by explicitly calling `sp_command_free`.
*/
SPCommand *sp_command_utf8_data(size_t x,
size_t y,
SPCharGrid *grid);
/**
* Creates a new instance of [SPConnection] for testing that does not actually send anything.
*
@ -1528,7 +1775,7 @@ SPPacket *sp_packet_clone(const SPPacket *packet);
*
* # Panics
*
* - when `sp_packet_free` is NULL
* - when `packet` is NULL
*
* # Safety
*
@ -1560,6 +1807,40 @@ void sp_packet_free(SPPacket *packet);
*/
SPPacket *sp_packet_from_command(SPCommand *command);
/**
* Creates a raw [SPPacket] from parts.
*
* # Arguments
*
* - `command_code` specifies which command this packet contains
* - `a`, `b`, `c` and `d` are command-specific header values
* - `payload` is the optional data that is part of the command
* - `payload_len` is the size of the payload
*
* returns: new instance. Will never return null.
*
* # Panics
*
* - when `payload` is null, but `payload_len` is not zero
* - when `payload_len` is zero, but `payload` is nonnull
*
* # Safety
*
* The caller has to make sure that:
*
* - `payload` points to a valid memory region of at least `payload_len` bytes
* - `payload` is not written to concurrently
* - the returned [SPPacket] instance is freed in some way, either by using a consuming function or
* by explicitly calling [sp_packet_free].
*/
SPPacket *sp_packet_from_parts(uint16_t command_code,
uint16_t a,
uint16_t b,
uint16_t c,
uint16_t d,
const uint8_t *payload,
size_t payload_len);
/**
* Tries to load a [SPPacket] from the passed array with the specified length.
*

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

@ -9,7 +9,7 @@
<PropertyGroup>
<PackageId>ServicePoint</PackageId>
<Version>0.12.0</Version>
<Version>0.13.0</Version>
<Authors>Repository Authors</Authors>
<Company>None</Company>
<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
);
[DllImport("servicepoint_binding_uniffi")]
public static extern CommandSafeHandle uniffi_servicepoint_binding_uniffi_fn_constructor_command_utf8_data(ulong @offsetX,ulong @offsetY,CharGridSafeHandle @grid,ref RustCallStatus _uniffi_out_err
);
[DllImport("servicepoint_binding_uniffi")]
public static extern sbyte uniffi_servicepoint_binding_uniffi_fn_method_command_equals(CommandSafeHandle @ptr,CommandSafeHandle @other,ref RustCallStatus _uniffi_out_err
);
@ -1255,6 +1259,10 @@ static class _UniFFILib {
public static extern ushort uniffi_servicepoint_binding_uniffi_checksum_constructor_command_hard_reset(
);
[DllImport("servicepoint_binding_uniffi")]
public static extern ushort uniffi_servicepoint_binding_uniffi_checksum_constructor_command_utf8_data(
);
[DllImport("servicepoint_binding_uniffi")]
public static extern ushort uniffi_servicepoint_binding_uniffi_checksum_constructor_connection_new(
);
@ -1697,6 +1705,12 @@ static class _UniFFILib {
throw new UniffiContractChecksumException($"ServicePoint: uniffi bindings expected function `uniffi_servicepoint_binding_uniffi_checksum_constructor_command_hard_reset` checksum `62130`, library returned `{checksum}`");
}
}
{
var checksum = _UniFFILib.uniffi_servicepoint_binding_uniffi_checksum_constructor_command_utf8_data();
if (checksum != 2263) {
throw new UniffiContractChecksumException($"ServicePoint: uniffi bindings expected function `uniffi_servicepoint_binding_uniffi_checksum_constructor_command_utf8_data` checksum `2263`, library returned `{checksum}`");
}
}
{
var checksum = _UniFFILib.uniffi_servicepoint_binding_uniffi_checksum_constructor_connection_new();
if (checksum != 30445) {
@ -2671,6 +2685,13 @@ public class Command: FFIObject<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,
[RustCallStatus.by_ref],
:pointer
attach_function :uniffi_servicepoint_binding_uniffi_fn_constructor_command_utf8_data,
[:uint64, :uint64, :pointer, RustCallStatus.by_ref],
:pointer
attach_function :uniffi_servicepoint_binding_uniffi_fn_method_command_equals,
[:pointer, :pointer, RustCallStatus.by_ref],
:int8
@ -1188,6 +1191,9 @@ module UniFFILib
attach_function :uniffi_servicepoint_binding_uniffi_checksum_constructor_command_hard_reset,
[RustCallStatus.by_ref],
:uint16
attach_function :uniffi_servicepoint_binding_uniffi_checksum_constructor_command_utf8_data,
[RustCallStatus.by_ref],
:uint16
attach_function :uniffi_servicepoint_binding_uniffi_checksum_constructor_connection_new,
[RustCallStatus.by_ref],
:uint16
@ -1817,6 +1823,15 @@ end
# and just create a new instance with the required pointer.
return _uniffi_allocate(ServicepointBindingUniffi.rust_call(:uniffi_servicepoint_binding_uniffi_fn_constructor_command_hard_reset,))
end
def self.utf8_data(offset_x, offset_y, grid)
offset_x = ServicepointBindingUniffi::uniffi_in_range(offset_x, "u64", 0, 2**64)
offset_y = ServicepointBindingUniffi::uniffi_in_range(offset_y, "u64", 0, 2**64)
grid = grid
# Call the (fallible) function before creating any half-baked object instances.
# Lightly yucky way to bypass the usual "initialize" logic
# and just create a new instance with the required pointer.
return _uniffi_allocate(ServicepointBindingUniffi.rust_call(:uniffi_servicepoint_binding_uniffi_fn_constructor_command_utf8_data,offset_x,offset_y,(CharGrid._uniffi_lower grid)))
end
def equals(other)

View file

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

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

View file

@ -1,6 +1,7 @@
use crate::bitmap::Bitmap;
use crate::bitvec::BitVec;
use crate::brightness_grid::BrightnessGrid;
use crate::char_grid::CharGrid;
use crate::compression_code::CompressionCode;
use crate::cp437_grid::Cp437Grid;
use crate::errors::ServicePointError;
@ -151,6 +152,18 @@ impl Command {
Self::internal_new(actual)
}
#[uniffi::constructor]
pub fn utf8_data(
offset_x: u64,
offset_y: u64,
grid: &Arc<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]
pub fn clone(other: &Arc<Self>) -> Arc<Self> {
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 tile_size: u64,
pub tile_width: u64,
@ -10,7 +12,7 @@ pub struct Constants {
#[uniffi::export]
fn get_constants() -> Constants {
Constants {
Constants {
tile_size: servicepoint::TILE_SIZE as u64,
tile_width: servicepoint::TILE_WIDTH as u64,
tile_height: servicepoint::TILE_HEIGHT as u64,

View file

@ -1,6 +1,6 @@
use crate::char_grid::CharGrid;
use servicepoint::{DataRef, Grid};
use std::sync::{Arc, RwLock};
use crate::char_grid::CharGrid;
#[derive(uniffi::Object)]
pub struct Cp437Grid {
@ -72,6 +72,8 @@ impl Cp437Grid {
}
pub fn to_utf8(&self) -> Arc<CharGrid> {
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 compression_code;
mod connection;
mod constants;
mod cp437_grid;
mod errors;
mod constants;

View file

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

View file

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