Merge branch 'easier-text'
This commit is contained in:
		
						commit
						3a605da0d5
					
				
					 25 changed files with 951 additions and 192 deletions
				
			
		
							
								
								
									
										246
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										246
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							|  | @ -84,12 +84,27 @@ dependencies = [ | ||||||
|  "wyz", |  "wyz", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "block-buffer" | ||||||
|  | version = "0.10.4" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" | ||||||
|  | dependencies = [ | ||||||
|  |  "generic-array", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "byteorder" | name = "byteorder" | ||||||
| version = "1.5.0" | version = "1.5.0" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "bytes" | ||||||
|  | version = "1.7.2" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "bzip2" | name = "bzip2" | ||||||
| version = "0.4.4" | version = "0.4.4" | ||||||
|  | @ -132,9 +147,9 @@ dependencies = [ | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "cc" | name = "cc" | ||||||
| version = "1.1.18" | version = "1.1.29" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" | checksum = "58e804ac3194a48bb129643eb1d62fcc20d18c6b8c181704489353d13120bcd1" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "jobserver", |  "jobserver", | ||||||
|  "libc", |  "libc", | ||||||
|  | @ -149,9 +164,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "clap" | name = "clap" | ||||||
| version = "4.5.17" | version = "4.5.20" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" | checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "clap_builder", |  "clap_builder", | ||||||
|  "clap_derive", |  "clap_derive", | ||||||
|  | @ -159,9 +174,9 @@ dependencies = [ | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "clap_builder" | name = "clap_builder" | ||||||
| version = "4.5.17" | version = "4.5.20" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" | checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "anstream", |  "anstream", | ||||||
|  "anstyle", |  "anstyle", | ||||||
|  | @ -171,9 +186,9 @@ dependencies = [ | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "clap_derive" | name = "clap_derive" | ||||||
| version = "4.5.13" | version = "4.5.18" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "heck 0.5.0", |  "heck 0.5.0", | ||||||
|  "proc-macro2", |  "proc-macro2", | ||||||
|  | @ -193,6 +208,15 @@ version = "1.0.2" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" | checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "cpufeatures" | ||||||
|  | version = "0.2.14" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" | ||||||
|  | dependencies = [ | ||||||
|  |  "libc", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "crc32fast" | name = "crc32fast" | ||||||
| version = "1.4.2" | version = "1.4.2" | ||||||
|  | @ -202,6 +226,16 @@ dependencies = [ | ||||||
|  "cfg-if", |  "cfg-if", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "crypto-common" | ||||||
|  | version = "0.1.6" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" | ||||||
|  | dependencies = [ | ||||||
|  |  "generic-array", | ||||||
|  |  "typenum", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "csbindgen" | name = "csbindgen" | ||||||
| version = "1.9.3" | version = "1.9.3" | ||||||
|  | @ -212,6 +246,22 @@ dependencies = [ | ||||||
|  "syn", |  "syn", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "data-encoding" | ||||||
|  | version = "2.6.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "digest" | ||||||
|  | version = "0.10.7" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" | ||||||
|  | dependencies = [ | ||||||
|  |  "block-buffer", | ||||||
|  |  "crypto-common", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "equivalent" | name = "equivalent" | ||||||
| version = "1.0.1" | version = "1.0.1" | ||||||
|  | @ -236,20 +286,36 @@ checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "flate2" | name = "flate2" | ||||||
| version = "1.0.33" | version = "1.0.34" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" | checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "crc32fast", |  "crc32fast", | ||||||
|  "miniz_oxide", |  "miniz_oxide", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "fnv" | ||||||
|  | version = "1.0.7" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "funty" | name = "funty" | ||||||
| version = "2.0.0" | version = "2.0.0" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" | checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "generic-array" | ||||||
|  | version = "0.14.7" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" | ||||||
|  | dependencies = [ | ||||||
|  |  "typenum", | ||||||
|  |  "version_check", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "getrandom" | name = "getrandom" | ||||||
| version = "0.2.15" | version = "0.2.15" | ||||||
|  | @ -263,9 +329,9 @@ dependencies = [ | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "hashbrown" | name = "hashbrown" | ||||||
| version = "0.14.5" | version = "0.15.0" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" | checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "heck" | name = "heck" | ||||||
|  | @ -280,10 +346,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "indexmap" | name = "http" | ||||||
| version = "2.5.0" | version = "1.1.0" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" | checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" | ||||||
|  | dependencies = [ | ||||||
|  |  "bytes", | ||||||
|  |  "fnv", | ||||||
|  |  "itoa", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "httparse" | ||||||
|  | version = "1.9.5" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "indexmap" | ||||||
|  | version = "2.6.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "equivalent", |  "equivalent", | ||||||
|  "hashbrown", |  "hashbrown", | ||||||
|  | @ -320,9 +403,9 @@ dependencies = [ | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "libc" | name = "libc" | ||||||
| version = "0.2.158" | version = "0.2.159" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" | checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "linux-raw-sys" | name = "linux-raw-sys" | ||||||
|  | @ -353,15 +436,15 @@ dependencies = [ | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "once_cell" | name = "once_cell" | ||||||
| version = "1.19.0" | version = "1.20.2" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "pkg-config" | name = "pkg-config" | ||||||
| version = "0.3.30" | version = "0.3.31" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "ppv-lite86" | name = "ppv-lite86" | ||||||
|  | @ -374,9 +457,9 @@ dependencies = [ | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "proc-macro2" | name = "proc-macro2" | ||||||
| version = "1.0.86" | version = "1.0.87" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" | checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "unicode-ident", |  "unicode-ident", | ||||||
| ] | ] | ||||||
|  | @ -428,9 +511,9 @@ dependencies = [ | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "regex" | name = "regex" | ||||||
| version = "1.10.6" | version = "1.11.0" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" | checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "aho-corasick", |  "aho-corasick", | ||||||
|  "memchr", |  "memchr", | ||||||
|  | @ -440,9 +523,9 @@ dependencies = [ | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "regex-automata" | name = "regex-automata" | ||||||
| version = "0.4.7" | version = "0.4.8" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" | checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "aho-corasick", |  "aho-corasick", | ||||||
|  "memchr", |  "memchr", | ||||||
|  | @ -451,9 +534,9 @@ dependencies = [ | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "regex-syntax" | name = "regex-syntax" | ||||||
| version = "0.8.4" | version = "0.8.5" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "rust-lzma" | name = "rust-lzma" | ||||||
|  | @ -467,9 +550,9 @@ dependencies = [ | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "rustix" | name = "rustix" | ||||||
| version = "0.38.36" | version = "0.38.37" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36" | checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "bitflags", |  "bitflags", | ||||||
|  "errno", |  "errno", | ||||||
|  | @ -518,30 +601,32 @@ dependencies = [ | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "serde_spanned" | name = "serde_spanned" | ||||||
| version = "0.6.7" | version = "0.6.8" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" | checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "serde", |  "serde", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "servicepoint" | name = "servicepoint" | ||||||
| version = "0.8.0" | version = "0.9.0" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "bitvec", |  "bitvec", | ||||||
|  "bzip2", |  "bzip2", | ||||||
|  "clap", |  "clap", | ||||||
|  "flate2", |  "flate2", | ||||||
|  "log", |  "log", | ||||||
|  |  "once_cell", | ||||||
|  "rand", |  "rand", | ||||||
|  "rust-lzma", |  "rust-lzma", | ||||||
|  |  "tungstenite", | ||||||
|  "zstd", |  "zstd", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "servicepoint_binding_c" | name = "servicepoint_binding_c" | ||||||
| version = "0.8.0" | version = "0.9.0" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "cbindgen", |  "cbindgen", | ||||||
|  "servicepoint", |  "servicepoint", | ||||||
|  | @ -549,13 +634,24 @@ dependencies = [ | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "servicepoint_binding_cs" | name = "servicepoint_binding_cs" | ||||||
| version = "0.8.0" | version = "0.9.0" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "csbindgen", |  "csbindgen", | ||||||
|  "servicepoint", |  "servicepoint", | ||||||
|  "servicepoint_binding_c", |  "servicepoint_binding_c", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "sha1" | ||||||
|  | version = "0.10.6" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" | ||||||
|  | dependencies = [ | ||||||
|  |  "cfg-if", | ||||||
|  |  "cpufeatures", | ||||||
|  |  "digest", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "shlex" | name = "shlex" | ||||||
| version = "1.3.0" | version = "1.3.0" | ||||||
|  | @ -570,9 +666,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "syn" | name = "syn" | ||||||
| version = "2.0.77" | version = "2.0.79" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" | checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "proc-macro2", |  "proc-macro2", | ||||||
|  "quote", |  "quote", | ||||||
|  | @ -587,9 +683,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "tempfile" | name = "tempfile" | ||||||
| version = "3.12.0" | version = "3.13.0" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" | checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "cfg-if", |  "cfg-if", | ||||||
|  "fastrand", |  "fastrand", | ||||||
|  | @ -598,6 +694,26 @@ dependencies = [ | ||||||
|  "windows-sys 0.59.0", |  "windows-sys 0.59.0", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "thiserror" | ||||||
|  | version = "1.0.64" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" | ||||||
|  | dependencies = [ | ||||||
|  |  "thiserror-impl", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "thiserror-impl" | ||||||
|  | version = "1.0.64" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" | ||||||
|  | dependencies = [ | ||||||
|  |  "proc-macro2", | ||||||
|  |  "quote", | ||||||
|  |  "syn", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "toml" | name = "toml" | ||||||
| version = "0.8.19" | version = "0.8.19" | ||||||
|  | @ -621,9 +737,9 @@ dependencies = [ | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "toml_edit" | name = "toml_edit" | ||||||
| version = "0.22.20" | version = "0.22.22" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" | checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "indexmap", |  "indexmap", | ||||||
|  "serde", |  "serde", | ||||||
|  | @ -633,10 +749,40 @@ dependencies = [ | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "unicode-ident" | name = "tungstenite" | ||||||
| version = "1.0.12" | version = "0.24.0" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" | checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" | ||||||
|  | dependencies = [ | ||||||
|  |  "byteorder", | ||||||
|  |  "bytes", | ||||||
|  |  "data-encoding", | ||||||
|  |  "http", | ||||||
|  |  "httparse", | ||||||
|  |  "log", | ||||||
|  |  "rand", | ||||||
|  |  "sha1", | ||||||
|  |  "thiserror", | ||||||
|  |  "utf-8", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "typenum" | ||||||
|  | version = "1.17.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "unicode-ident" | ||||||
|  | version = "1.0.13" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "utf-8" | ||||||
|  | version = "0.7.6" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "utf8parse" | name = "utf8parse" | ||||||
|  | @ -650,6 +796,12 @@ version = "0.2.15" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "version_check" | ||||||
|  | version = "0.9.5" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "wasi" | name = "wasi" | ||||||
| version = "0.11.0+wasi-snapshot-preview1" | version = "0.11.0+wasi-snapshot-preview1" | ||||||
|  | @ -740,9 +892,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "winnow" | name = "winnow" | ||||||
| version = "0.6.18" | version = "0.6.20" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" | checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "memchr", |  "memchr", | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  | @ -6,7 +6,7 @@ members = [ | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [workspace.package] | [workspace.package] | ||||||
| version = "0.8.0" | version = "0.9.0" | ||||||
| 
 | 
 | ||||||
| [workspace.lints.rust] | [workspace.lints.rust] | ||||||
| missing-docs = "warn" | missing-docs = "warn" | ||||||
|  |  | ||||||
|  | @ -20,24 +20,36 @@ bzip2 = { version = "0.4", optional = true } | ||||||
| zstd = { version = "0.13", optional = true } | zstd = { version = "0.13", optional = true } | ||||||
| rust-lzma = { version = "0.6.0", optional = true } | rust-lzma = { version = "0.6.0", optional = true } | ||||||
| rand = { version = "0.8", optional = true } | rand = { version = "0.8", optional = true } | ||||||
|  | tungstenite = { version = "0.24.0", optional = true } | ||||||
|  | once_cell = { version = "1.20.2", optional = true } | ||||||
| 
 | 
 | ||||||
| [features] | [features] | ||||||
| default = ["compression_lzma"] | default = ["compression_lzma", "protocol_udp", "cp437"] | ||||||
| compression_zlib = ["dep:flate2"] | compression_zlib = ["dep:flate2"] | ||||||
| compression_bzip2 = ["dep:bzip2"] | compression_bzip2 = ["dep:bzip2"] | ||||||
| compression_lzma = ["dep:rust-lzma"] | compression_lzma = ["dep:rust-lzma"] | ||||||
| compression_zstd = ["dep:zstd"] | compression_zstd = ["dep:zstd"] | ||||||
| all_compressions = ["compression_zlib", "compression_bzip2", "compression_lzma", "compression_zstd"] | all_compressions = ["compression_zlib", "compression_bzip2", "compression_lzma", "compression_zstd"] | ||||||
| rand = ["dep:rand"] | rand = ["dep:rand"] | ||||||
|  | protocol_udp = [] | ||||||
|  | protocol_websocket = ["dep:tungstenite"] | ||||||
|  | cp437 = ["dep:once_cell"] | ||||||
| 
 | 
 | ||||||
| [[example]] | [[example]] | ||||||
| name = "random_brightness" | name = "random_brightness" | ||||||
| required-features = ["rand"] | required-features = ["rand"] | ||||||
| 
 | 
 | ||||||
|  | [[example]] | ||||||
|  | name = "game_of_life" | ||||||
|  | required-features = ["rand"] | ||||||
|  | 
 | ||||||
|  | [[example]] | ||||||
|  | name = "websocket" | ||||||
|  | required-features = ["protocol_websocket"] | ||||||
|  | 
 | ||||||
| [dev-dependencies] | [dev-dependencies] | ||||||
| # for examples | # for examples | ||||||
| clap = { version = "4.5", features = ["derive"] } | clap = { version = "4.5", features = ["derive"] } | ||||||
| rand = "0.8" |  | ||||||
| 
 | 
 | ||||||
| [lints] | [lints] | ||||||
| workspace = true | workspace = true | ||||||
|  | @ -9,6 +9,17 @@ In [CCCB](https://berlin.ccc.de/), there is a big pixel matrix hanging on the wa | ||||||
| Display" or "Airport Display". | Display" or "Airport Display". | ||||||
| This crate contains a library for parsing, encoding and sending packets to this display via UDP. | This crate contains a library for parsing, encoding and sending packets to this display via UDP. | ||||||
| 
 | 
 | ||||||
|  | ## Installation | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | cargo add servicepoint | ||||||
|  | ``` | ||||||
|  | or | ||||||
|  | ```toml | ||||||
|  | [dependencies] | ||||||
|  | servicepoint = "0.9.0" | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| ## Examples | ## Examples | ||||||
| 
 | 
 | ||||||
| ```rust | ```rust | ||||||
|  | @ -23,7 +34,7 @@ fn main() { | ||||||
| } | } | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| More examples are available in the crate.  | More examples are available in the crate. | ||||||
| Execute `cargo run --example` for a list of available examples and `cargo run --example <name>` to run one. | Execute `cargo run --example` for a list of available examples and `cargo run --example <name>` to run one. | ||||||
| 
 | 
 | ||||||
| ## Note on stability | ## Note on stability | ||||||
|  | @ -32,22 +43,21 @@ This library is still in early development. | ||||||
| You can absolutely use it, and it works, but expect minor breaking changes with every version bump. | You can absolutely use it, and it works, but expect minor breaking changes with every version bump. | ||||||
| Please specify the full version including patch in your Cargo.toml until 1.0 is released. | Please specify the full version including patch in your Cargo.toml until 1.0 is released. | ||||||
| 
 | 
 | ||||||
| ## Installation |  | ||||||
| 
 |  | ||||||
| ```bash |  | ||||||
| cargo add servicepoint |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| ## Features | ## Features | ||||||
| 
 | 
 | ||||||
| This library has multiple compression libraries as optional dependencies. | This library has multiple optional dependencies. | ||||||
| If you do not need compression/decompression support you can disable those features. | You can choose to (not) include them by toggling the related features. | ||||||
| In the likely case you only need one of them, you can include that one specifically. |  | ||||||
| 
 | 
 | ||||||
| ```toml | | Name               | Default | Description                                | | ||||||
| [dependencies] | |--------------------|---------|--------------------------------------------| | ||||||
| servicepoint = { version = "0.8.0", default-features = false, features = ["compression-bz"] } | | compression_zlib   | false   | Enable additional compression algo         | | ||||||
| ``` | | compression_bzip2  | false   | Enable additional compression algo         | | ||||||
|  | | compression_lzma   | true    | Enable additional compression algo         | | ||||||
|  | | compression_zstd   | false   | Enable additional compression algo         | | ||||||
|  | | protocol_udp       | true    | Connection::Udp                            | | ||||||
|  | | protocol_websocket | false   | Connection::WebSocket                      | | ||||||
|  | | rand               | false   | impl Distribution<Brightness> for Standard | | ||||||
|  | | cp437              | true    | Conversion to and from CP-437              | | ||||||
| 
 | 
 | ||||||
| ## Everything else | ## Everything else | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ | ||||||
| 
 | 
 | ||||||
| use clap::Parser; | use clap::Parser; | ||||||
| 
 | 
 | ||||||
| use servicepoint::{Command, Connection, Cp437Grid, Grid, Origin}; | use servicepoint::{CharGrid, Command, Connection, Cp437Grid, Origin}; | ||||||
| 
 | 
 | ||||||
| #[derive(Parser, Debug)] | #[derive(Parser, Debug)] | ||||||
| struct Cli { | struct Cli { | ||||||
|  | @ -31,19 +31,15 @@ fn main() { | ||||||
|             .expect("sending clear failed"); |             .expect("sending clear failed"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     let max_width = cli.text.iter().map(|t| t.len()).max().unwrap(); |     let text = cli.text.iter().fold(String::new(), move |str, line| { | ||||||
|  |         let is_first = str.is_empty(); | ||||||
|  |         str + if is_first { "" } else { "\n" } + line | ||||||
|  |     }); | ||||||
| 
 | 
 | ||||||
|     let mut chars = Cp437Grid::new(max_width, cli.text.len()); |     let grid = CharGrid::from(&*text); | ||||||
|     for y in 0..cli.text.len() { |     let cp437_grid = Cp437Grid::from(&grid); | ||||||
|         let row = &cli.text[y]; |  | ||||||
| 
 |  | ||||||
|         for (x, char) in row.chars().enumerate() { |  | ||||||
|             let char = char.try_into().expect("invalid input char"); |  | ||||||
|             chars.set(x, y, char); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     connection |     connection | ||||||
|         .send(Command::Cp437Data(Origin::new(0, 0), chars)) |         .send(Command::Cp437Data(Origin::new(0, 0), cp437_grid)) | ||||||
|         .expect("sending text failed"); |         .expect("sending text failed"); | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										27
									
								
								crates/servicepoint/examples/websocket.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								crates/servicepoint/examples/websocket.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | ||||||
|  | //! Example for how to use the WebSocket connection
 | ||||||
|  | 
 | ||||||
|  | use servicepoint::{ | ||||||
|  |     Command, CompressionCode, Connection, Grid, Origin, PixelGrid, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | fn main() { | ||||||
|  |     // make connection mut
 | ||||||
|  |     let mut connection = | ||||||
|  |         Connection::open_websocket("ws://localhost:8080".parse().unwrap()) | ||||||
|  |             .unwrap(); | ||||||
|  | 
 | ||||||
|  |     // use send_mut instead of send
 | ||||||
|  |     connection.send_mut(Command::Clear).unwrap(); | ||||||
|  | 
 | ||||||
|  |     let mut pixels = PixelGrid::max_sized(); | ||||||
|  |     pixels.fill(true); | ||||||
|  | 
 | ||||||
|  |     // use send_mut instead of send
 | ||||||
|  |     connection | ||||||
|  |         .send_mut(Command::BitmapLinearWin( | ||||||
|  |             Origin::ZERO, | ||||||
|  |             pixels, | ||||||
|  |             CompressionCode::Lzma, | ||||||
|  |         )) | ||||||
|  |         .unwrap(); | ||||||
|  | } | ||||||
|  | @ -77,8 +77,8 @@ impl From<BrightnessGrid> for Vec<u8> { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl From<BrightnessGrid> for PrimitiveGrid<u8> { | impl From<&BrightnessGrid> for PrimitiveGrid<u8> { | ||||||
|     fn from(value: PrimitiveGrid<Brightness>) -> Self { |     fn from(value: &PrimitiveGrid<Brightness>) -> Self { | ||||||
|         let u8s = value |         let u8s = value | ||||||
|             .iter() |             .iter() | ||||||
|             .map(|brightness| (*brightness).into()) |             .map(|brightness| (*brightness).into()) | ||||||
|  | @ -109,3 +109,33 @@ impl Distribution<Brightness> for Standard { | ||||||
|         Brightness(rng.gen_range(Brightness::MIN.0..=Brightness::MAX.0)) |         Brightness(rng.gen_range(Brightness::MIN.0..=Brightness::MAX.0)) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | #[cfg(test)] | ||||||
|  | mod tests { | ||||||
|  |     use super::*; | ||||||
|  |     use crate::DataRef; | ||||||
|  | 
 | ||||||
|  |     #[test] | ||||||
|  |     fn brightness_from_u8() { | ||||||
|  |         assert_eq!(Err(100), Brightness::try_from(100)); | ||||||
|  |         assert_eq!(Ok(Brightness(1)), Brightness::try_from(1)) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[test] | ||||||
|  |     #[cfg(feature = "rand")] | ||||||
|  |     fn rand_brightness() { | ||||||
|  |         let mut rng = rand::thread_rng(); | ||||||
|  |         for _ in 0..100 { | ||||||
|  |             let _: Brightness = rng.gen(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[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]); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -1,30 +1,50 @@ | ||||||
| use bitvec::prelude::BitVec; | use bitvec::prelude::BitVec; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
|     command_code::CommandCode, compression::into_decompressed, Brightness, |     command_code::CommandCode, | ||||||
|     BrightnessGrid, CompressionCode, Header, Origin, Packet, PixelGrid, Pixels, |     compression::into_decompressed, | ||||||
|     PrimitiveGrid, SpBitVec, Tiles, TILE_SIZE, |     packet::{Header, Packet}, | ||||||
|  |     Brightness, BrightnessGrid, CompressionCode, Cp437Grid, Origin, PixelGrid, | ||||||
|  |     Pixels, PrimitiveGrid, SpBitVec, Tiles, TILE_SIZE, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /// Type alias for documenting the meaning of the u16 in enum values
 | /// Type alias for documenting the meaning of the u16 in enum values
 | ||||||
| pub type Offset = usize; | pub type Offset = usize; | ||||||
| 
 | 
 | ||||||
| /// A grid containing codepage 437 characters.
 |  | ||||||
| ///
 |  | ||||||
| /// The encoding is currently not enforced.
 |  | ||||||
| pub type Cp437Grid = PrimitiveGrid<u8>; |  | ||||||
| 
 |  | ||||||
| /// A low-level display command.
 | /// A low-level display command.
 | ||||||
| ///
 | ///
 | ||||||
| /// This struct and associated functions implement the UDP protocol for the display.
 | /// This struct and associated functions implement the UDP protocol for the display.
 | ||||||
| ///
 | ///
 | ||||||
| /// To send a `Command`, use a `Connection`.
 | /// To send a [Command], use a [connection][crate::Connection].
 | ||||||
|  | ///
 | ||||||
|  | /// # Available commands
 | ||||||
|  | ///
 | ||||||
|  | /// To send text, take a look at [Command::Cp437Data].
 | ||||||
|  | ///
 | ||||||
|  | /// To draw pixels, the easiest command to use is [Command::BitmapLinearWin].
 | ||||||
|  | ///
 | ||||||
|  | /// The other BitmapLinear-Commands operate on a region of pixel memory directly.
 | ||||||
|  | /// [Command::BitmapLinear] overwrites a region.
 | ||||||
|  | /// [Command::BitmapLinearOr], [Command::BitmapLinearAnd] and [Command::BitmapLinearXor] apply logical operations per pixel.
 | ||||||
|  | ///
 | ||||||
|  | /// Out of bounds operations may be truncated or ignored by the display.
 | ||||||
|  | ///
 | ||||||
|  | /// # Compression
 | ||||||
|  | ///
 | ||||||
|  | /// Some commands can contain compressed payloads.
 | ||||||
|  | /// To get started, use [CompressionCode::Uncompressed].
 | ||||||
|  | ///
 | ||||||
|  | /// If you want to archive the best performance (e.g. latency),
 | ||||||
|  | /// you can try the different compression algorithms for your hardware and use case.
 | ||||||
|  | ///
 | ||||||
|  | /// In memory, the payload is not compressed in the [Command].
 | ||||||
|  | /// Payload (de-)compression happens when converting the [Command] into a [Packet] or vice versa.
 | ||||||
| ///
 | ///
 | ||||||
| /// # Examples
 | /// # Examples
 | ||||||
| ///
 | ///
 | ||||||
| /// ```rust
 | /// ```rust
 | ||||||
| /// # use servicepoint::{Brightness, Command, Connection, Packet};
 | /// # use servicepoint::{Brightness, Command, Connection, packet::Packet};
 | ||||||
| ///
 | /// #
 | ||||||
| /// // create command
 | /// // create command
 | ||||||
| /// let command = Command::Brightness(Brightness::MAX);
 | /// let command = Command::Brightness(Brightness::MAX);
 | ||||||
| ///
 | ///
 | ||||||
|  | @ -56,6 +76,8 @@ pub enum Command { | ||||||
| 
 | 
 | ||||||
|     /// Show text on the screen.
 |     /// Show text on the screen.
 | ||||||
|     ///
 |     ///
 | ||||||
|  |     /// The text is sent in the form of a 2D grid of characters.
 | ||||||
|  |     ///
 | ||||||
|     /// <div class="warning">
 |     /// <div class="warning">
 | ||||||
|     ///     The library does not currently convert between UTF-8 and CP-437.
 |     ///     The library does not currently convert between UTF-8 and CP-437.
 | ||||||
|     ///     Because Rust expects UTF-8 strings, it might be necessary to only send ASCII for now.
 |     ///     Because Rust expects UTF-8 strings, it might be necessary to only send ASCII for now.
 | ||||||
|  | @ -64,15 +86,45 @@ pub enum Command { | ||||||
|     /// # Examples
 |     /// # Examples
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// ```rust
 |     /// ```rust
 | ||||||
|  |     /// # use servicepoint::{Command, Connection, Origin};
 | ||||||
|  |     /// # 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");
 | ||||||
|  |     /// ```
 | ||||||
|  |     ///
 | ||||||
|  |     /// ```rust
 | ||||||
|     /// # use servicepoint::{Command, Connection, Cp437Grid, Origin};
 |     /// # use servicepoint::{Command, Connection, Cp437Grid, Origin};
 | ||||||
|     /// # let connection = Connection::open("127.0.0.1:2342").unwrap();
 |     /// # let connection = Connection::Fake;
 | ||||||
|     /// let chars = ['H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd'].map(move |c| c as u8);
 |     /// let grid = Cp437Grid::load_ascii("Hello\nWorld", 5, false).unwrap();
 | ||||||
|     /// let grid = Cp437Grid::load(5, 2, &chars);
 |  | ||||||
|     /// connection.send(Command::Cp437Data(Origin::new(2, 2), grid)).unwrap();
 |     /// connection.send(Command::Cp437Data(Origin::new(2, 2), grid)).unwrap();
 | ||||||
|     /// ```
 |     /// ```
 | ||||||
|     Cp437Data(Origin<Tiles>, Cp437Grid), |     Cp437Data(Origin<Tiles>, Cp437Grid), | ||||||
| 
 | 
 | ||||||
|     /// Sets a window of pixels to the specified values
 |     /// Overwrites a rectangular region of pixels.
 | ||||||
|  |     ///
 | ||||||
|  |     /// Origin coordinates must be divisible by 8.
 | ||||||
|  |     ///
 | ||||||
|  |     /// # Examples
 | ||||||
|  |     ///
 | ||||||
|  |     /// ```rust
 | ||||||
|  |     /// # use servicepoint::{Command, CompressionCode, Grid, PixelGrid};
 | ||||||
|  |     /// # let connection = servicepoint::Connection::Fake;
 | ||||||
|  |     /// #
 | ||||||
|  |     /// let mut pixels = PixelGrid::max_sized();
 | ||||||
|  |     /// // draw something to the pixels here
 | ||||||
|  |     /// # pixels.set(2, 5, true);
 | ||||||
|  |     ///
 | ||||||
|  |     /// // create command to send pixels
 | ||||||
|  |     /// let command = Command::BitmapLinearWin(
 | ||||||
|  |     ///    servicepoint::Origin::new(0, 0),
 | ||||||
|  |     ///    pixels,
 | ||||||
|  |     ///    CompressionCode::Uncompressed
 | ||||||
|  |     /// );
 | ||||||
|  |     ///
 | ||||||
|  |     /// connection.send(command).expect("send failed");
 | ||||||
|  |     /// ```
 | ||||||
|     BitmapLinearWin(Origin<Pixels>, PixelGrid, CompressionCode), |     BitmapLinearWin(Origin<Pixels>, PixelGrid, CompressionCode), | ||||||
| 
 | 
 | ||||||
|     /// Set the brightness of all tiles to the same value.
 |     /// Set the brightness of all tiles to the same value.
 | ||||||
|  | @ -95,7 +147,7 @@ pub enum Command { | ||||||
|     /// The screen will continuously overwrite more pixel data without regarding the offset, meaning
 |     /// The screen will continuously overwrite more pixel data without regarding the offset, meaning
 | ||||||
|     /// once the starting row is full, overwriting will continue on column 0.
 |     /// once the starting row is full, overwriting will continue on column 0.
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// The contained `BitVec` is always uncompressed.
 |     /// The contained [BitVec] is always uncompressed.
 | ||||||
|     BitmapLinear(Offset, SpBitVec, CompressionCode), |     BitmapLinear(Offset, SpBitVec, CompressionCode), | ||||||
| 
 | 
 | ||||||
|     /// Set pixel data according to an and-mask starting at the offset.
 |     /// Set pixel data according to an and-mask starting at the offset.
 | ||||||
|  | @ -103,7 +155,7 @@ pub enum Command { | ||||||
|     /// The screen will continuously overwrite more pixel data without regarding the offset, meaning
 |     /// The screen will continuously overwrite more pixel data without regarding the offset, meaning
 | ||||||
|     /// once the starting row is full, overwriting will continue on column 0.
 |     /// once the starting row is full, overwriting will continue on column 0.
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// The contained `BitVec` is always uncompressed.
 |     /// The contained [BitVec] is always uncompressed.
 | ||||||
|     BitmapLinearAnd(Offset, SpBitVec, CompressionCode), |     BitmapLinearAnd(Offset, SpBitVec, CompressionCode), | ||||||
| 
 | 
 | ||||||
|     /// Set pixel data according to an or-mask starting at the offset.
 |     /// Set pixel data according to an or-mask starting at the offset.
 | ||||||
|  | @ -111,7 +163,7 @@ pub enum Command { | ||||||
|     /// The screen will continuously overwrite more pixel data without regarding the offset, meaning
 |     /// The screen will continuously overwrite more pixel data without regarding the offset, meaning
 | ||||||
|     /// once the starting row is full, overwriting will continue on column 0.
 |     /// once the starting row is full, overwriting will continue on column 0.
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// The contained `BitVec` is always uncompressed.
 |     /// The contained [BitVec] is always uncompressed.
 | ||||||
|     BitmapLinearOr(Offset, SpBitVec, CompressionCode), |     BitmapLinearOr(Offset, SpBitVec, CompressionCode), | ||||||
| 
 | 
 | ||||||
|     /// Set pixel data according to a xor-mask starting at the offset.
 |     /// Set pixel data according to a xor-mask starting at the offset.
 | ||||||
|  | @ -119,7 +171,7 @@ pub enum Command { | ||||||
|     /// The screen will continuously overwrite more pixel data without regarding the offset, meaning
 |     /// The screen will continuously overwrite more pixel data without regarding the offset, meaning
 | ||||||
|     /// once the starting row is full, overwriting will continue on column 0.
 |     /// once the starting row is full, overwriting will continue on column 0.
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// The contained `BitVec` is always uncompressed.
 |     /// The contained [BitVec] is always uncompressed.
 | ||||||
|     BitmapLinearXor(Offset, SpBitVec, CompressionCode), |     BitmapLinearXor(Offset, SpBitVec, CompressionCode), | ||||||
| 
 | 
 | ||||||
|     /// Kills the udp daemon on the display, which usually results in a restart.
 |     /// Kills the udp daemon on the display, which usually results in a restart.
 | ||||||
|  | @ -166,7 +218,7 @@ pub enum Command { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| /// Err values for `Command::try_from`.
 | /// Err values for [Command::try_from].
 | ||||||
| #[derive(PartialEq)] | #[derive(PartialEq)] | ||||||
| pub enum TryFromPacketError { | pub enum TryFromPacketError { | ||||||
|     /// the contained command code does not correspond to a known command
 |     /// the contained command code does not correspond to a known command
 | ||||||
|  | @ -188,7 +240,7 @@ pub enum TryFromPacketError { | ||||||
| impl TryFrom<Packet> for Command { | impl TryFrom<Packet> for Command { | ||||||
|     type Error = TryFromPacketError; |     type Error = TryFromPacketError; | ||||||
| 
 | 
 | ||||||
|     /// Try to interpret the `Packet` as one containing a `Command`
 |     /// Try to interpret the [Packet] as one containing a [Command]
 | ||||||
|     fn try_from(packet: Packet) -> Result<Self, Self::Error> { |     fn try_from(packet: Packet) -> Result<Self, Self::Error> { | ||||||
|         let Packet { |         let Packet { | ||||||
|             header: Header { |             header: Header { | ||||||
|  | @ -443,9 +495,12 @@ impl Command { | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mod tests { | mod tests { | ||||||
|     use crate::{ |     use crate::{ | ||||||
|         bitvec::prelude::BitVec, command::TryFromPacketError, |         bitvec::prelude::BitVec, | ||||||
|         command_code::CommandCode, origin::Pixels, Brightness, Command, |         command::TryFromPacketError, | ||||||
|         CompressionCode, Header, Origin, Packet, PixelGrid, PrimitiveGrid, |         command_code::CommandCode, | ||||||
|  |         origin::Pixels, | ||||||
|  |         packet::{Header, Packet}, | ||||||
|  |         Brightness, Command, CompressionCode, Origin, PixelGrid, PrimitiveGrid, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     fn round_trip(original: Command) { |     fn round_trip(original: Command) { | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| /// The u16 command codes used for the `Commands`.
 | /// The u16 command codes used for the [Command]s.
 | ||||||
| #[repr(u16)] | #[repr(u16)] | ||||||
| #[derive(Debug, Copy, Clone)] | #[derive(Debug, Copy, Clone)] | ||||||
| pub(crate) enum CommandCode { | pub(crate) enum CommandCode { | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ use flate2::{FlushCompress, FlushDecompress, Status}; | ||||||
| #[cfg(feature = "compression_zstd")] | #[cfg(feature = "compression_zstd")] | ||||||
| use zstd::{Decoder as ZstdDecoder, Encoder as ZstdEncoder}; | use zstd::{Decoder as ZstdDecoder, Encoder as ZstdEncoder}; | ||||||
| 
 | 
 | ||||||
| use crate::{CompressionCode, Payload}; | use crate::{packet::Payload, CompressionCode}; | ||||||
| 
 | 
 | ||||||
| pub(crate) fn into_decompressed( | pub(crate) fn into_decompressed( | ||||||
|     kind: CompressionCode, |     kind: CompressionCode, | ||||||
|  |  | ||||||
|  | @ -1,29 +1,61 @@ | ||||||
| use std::fmt::Debug; | use std::fmt::Debug; | ||||||
| use std::net::{ToSocketAddrs, UdpSocket}; |  | ||||||
| 
 | 
 | ||||||
| use log::{debug, info}; | use crate::packet::Packet; | ||||||
| 
 |  | ||||||
| use crate::Packet; |  | ||||||
| 
 | 
 | ||||||
| /// A connection to the display.
 | /// A connection to the display.
 | ||||||
| ///
 | ///
 | ||||||
|  | /// Used to send [Packets][Packet] or [Commands][crate::Command].
 | ||||||
|  | ///
 | ||||||
| /// # Examples
 | /// # Examples
 | ||||||
| /// ```rust
 | /// ```rust
 | ||||||
| /// let connection = servicepoint::Connection::open("172.23.42.29:2342")
 | /// let connection = servicepoint::Connection::open("127.0.0.1:2342")
 | ||||||
| ///     .expect("connection failed");
 | ///     .expect("connection failed");
 | ||||||
| ///  connection.send(servicepoint::Command::Clear)
 | ///  connection.send(servicepoint::Command::Clear)
 | ||||||
| ///     .expect("send failed");
 | ///     .expect("send failed");
 | ||||||
| /// ```
 | /// ```
 | ||||||
|  | #[derive(Debug)] | ||||||
| pub enum Connection { | pub enum Connection { | ||||||
|     /// A real connection using the UDP protocol
 |     /// A connection using the UDP protocol.
 | ||||||
|     Udp(UdpSocket), |     ///
 | ||||||
|     /// A fake connection for testing that does not actually send anything
 |     /// Use this when sending commands directly to the display.
 | ||||||
|  |     ///
 | ||||||
|  |     /// Requires the feature "protocol_udp" which is enabled by default.
 | ||||||
|  |     #[cfg(feature = "protocol_udp")] | ||||||
|  |     Udp(std::net::UdpSocket), | ||||||
|  | 
 | ||||||
|  |     /// A connection using the WebSocket protocol.
 | ||||||
|  |     ///
 | ||||||
|  |     /// Note that you will need to forward the WebSocket messages via UDP to the display.
 | ||||||
|  |     /// You can use [servicepoint-websocket-relay] for this.
 | ||||||
|  |     ///
 | ||||||
|  |     /// To create a new WebSocket automatically, use [Connection::open_websocket].
 | ||||||
|  |     ///
 | ||||||
|  |     /// Requires the feature "protocol_websocket" which is disabled by default.
 | ||||||
|  |     ///
 | ||||||
|  |     /// [servicepoint-websocket-relay]: https://github.com/kaesaecracker/servicepoint-websocket-relay
 | ||||||
|  |     #[cfg(feature = "protocol_websocket")] | ||||||
|  |     WebSocket( | ||||||
|  |         tungstenite::WebSocket< | ||||||
|  |             tungstenite::stream::MaybeTlsStream<std::net::TcpStream>, | ||||||
|  |         >, | ||||||
|  |     ), | ||||||
|  | 
 | ||||||
|  |     /// A fake connection for testing that does not actually send anything.
 | ||||||
|  |     ///
 | ||||||
|  |     /// This variant allows immutable send.
 | ||||||
|     Fake, |     Fake, | ||||||
|  | 
 | ||||||
|  |     /// A fake connection for testing that does not actually send anything.
 | ||||||
|  |     ///
 | ||||||
|  |     /// This variant does not allow immutable send.
 | ||||||
|  |     FakeMutableSend, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| pub enum SendError { | pub enum SendError { | ||||||
|     IoError(std::io::Error), |     IoError(std::io::Error), | ||||||
|  |     #[cfg(feature = "protocol_websocket")] | ||||||
|  |     WebsocketError(tungstenite::Error), | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl Connection { | impl Connection { | ||||||
|  | @ -31,24 +63,110 @@ impl Connection { | ||||||
|     ///
 |     ///
 | ||||||
|     /// Note that this is UDP, which means that the open call can succeed even if the display is unreachable.
 |     /// Note that this is UDP, which means that the open call can succeed even if the display is unreachable.
 | ||||||
|     ///
 |     ///
 | ||||||
|  |     /// The address of the display in CCCB is `172.23.42.29:2342`.
 | ||||||
|  |     ///
 | ||||||
|     /// # Errors
 |     /// # Errors
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// Any errors resulting from binding the udp socket.
 |     /// Any errors resulting from binding the udp socket.
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// # Examples
 |     /// # Examples
 | ||||||
|     /// ```rust
 |     /// ```rust
 | ||||||
|     ///  let connection = servicepoint::Connection::open("172.23.42.29:2342")
 |     ///  let connection = servicepoint::Connection::open("127.0.0.1:2342")
 | ||||||
|     ///     .expect("connection failed");
 |     ///     .expect("connection failed");
 | ||||||
|     /// ```
 |     /// ```
 | ||||||
|     pub fn open(addr: impl ToSocketAddrs + Debug) -> std::io::Result<Self> { |     #[cfg(feature = "protocol_udp")] | ||||||
|         info!("connecting to {addr:?}"); |     pub fn open( | ||||||
|         let socket = UdpSocket::bind("0.0.0.0:0")?; |         addr: impl std::net::ToSocketAddrs + Debug, | ||||||
|  |     ) -> std::io::Result<Self> { | ||||||
|  |         log::info!("connecting to {addr:?}"); | ||||||
|  |         let socket = std::net::UdpSocket::bind("0.0.0.0:0")?; | ||||||
|         socket.connect(addr)?; |         socket.connect(addr)?; | ||||||
|         Ok(Self::Udp(socket)) |         Ok(Self::Udp(socket)) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /// Open a new WebSocket and connect to the provided host.
 | ||||||
|  |     ///
 | ||||||
|  |     /// Requires the feature "protocol_websocket" which is disabled by default.
 | ||||||
|  |     ///
 | ||||||
|  |     /// # Examples
 | ||||||
|  |     ///
 | ||||||
|  |     /// ```no_run
 | ||||||
|  |     /// use tungstenite::http::Uri;
 | ||||||
|  |     /// use servicepoint::{Command, Connection};
 | ||||||
|  |     /// let uri = "ws://localhost:8080".parse().unwrap();
 | ||||||
|  |     /// let mut connection = Connection::open_websocket(uri)
 | ||||||
|  |     ///     .expect("could not connect");
 | ||||||
|  |     /// connection.send_mut(Command::Clear)
 | ||||||
|  |     ///     .expect("send failed");
 | ||||||
|  |     /// ```
 | ||||||
|  |     #[cfg(feature = "protocol_websocket")] | ||||||
|  |     pub fn open_websocket( | ||||||
|  |         uri: tungstenite::http::Uri, | ||||||
|  |     ) -> tungstenite::Result<Self> { | ||||||
|  |         use tungstenite::{ | ||||||
|  |             client::IntoClientRequest, connect, ClientRequestBuilder, | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         log::info!("connecting to {uri:?}"); | ||||||
|  | 
 | ||||||
|  |         let request = ClientRequestBuilder::new(uri).into_client_request()?; | ||||||
|  |         let (sock, _) = connect(request)?; | ||||||
|  |         Ok(Self::WebSocket(sock)) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /// Send something packet-like to the display. Usually this is in the form of a Command.
 |     /// Send something packet-like to the display. Usually this is in the form of a Command.
 | ||||||
|     ///
 |     ///
 | ||||||
|  |     /// This variant can only be used for connections that support immutable send, e.g. [Connection::Udp].
 | ||||||
|  |     ///
 | ||||||
|  |     /// If you want to be able to switch the protocol, you should use [Self::send_mut] instead.
 | ||||||
|  |     ///
 | ||||||
|  |     /// # Arguments
 | ||||||
|  |     ///
 | ||||||
|  |     /// - `packet`: the packet-like to send
 | ||||||
|  |     ///
 | ||||||
|  |     /// returns: true if packet was sent, otherwise false
 | ||||||
|  |     ///
 | ||||||
|  |     /// # Panics
 | ||||||
|  |     ///
 | ||||||
|  |     /// If the connection does not support immutable send, e.g. for [Connection::WebSocket].
 | ||||||
|  |     ///
 | ||||||
|  |     /// # Examples
 | ||||||
|  |     ///
 | ||||||
|  |     /// ```rust
 | ||||||
|  |     ///  let connection = servicepoint::Connection::Fake;
 | ||||||
|  |     ///  // turn off all pixels on display
 | ||||||
|  |     ///  connection.send(servicepoint::Command::Clear)
 | ||||||
|  |     ///      .expect("send failed");
 | ||||||
|  |     /// ```
 | ||||||
|  |     pub fn send(&self, packet: impl Into<Packet>) -> Result<(), SendError> { | ||||||
|  |         let packet = packet.into(); | ||||||
|  |         log::debug!("sending {packet:?}"); | ||||||
|  |         let data: Vec<u8> = packet.into(); | ||||||
|  |         match self { | ||||||
|  |             #[cfg(feature = "protocol_udp")] | ||||||
|  |             Connection::Udp(socket) => { | ||||||
|  |                 socket | ||||||
|  |                     .send(&data) | ||||||
|  |                     .map_err(SendError::IoError) | ||||||
|  |                     .map(move |_| ()) // ignore Ok value
 | ||||||
|  |             } | ||||||
|  |             Connection::Fake => { | ||||||
|  |                 let _ = data; | ||||||
|  |                 Ok(()) | ||||||
|  |             } | ||||||
|  |             #[allow(unreachable_patterns)] // depends on features
 | ||||||
|  |             _ => { | ||||||
|  |                 panic!("Connection {:?} does not support immutable send", self) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Send something packet-like to the display. Usually this is in the form of a Command.
 | ||||||
|  |     ///
 | ||||||
|  |     /// This variant has to be used for connections that do not support immutable send, e.g. [Connection::WebSocket].
 | ||||||
|  |     ///
 | ||||||
|  |     /// If you want to be able to switch the protocol, you should use this variant.
 | ||||||
|  |     ///
 | ||||||
|     /// # Arguments
 |     /// # Arguments
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// - `packet`: the packet-like to send
 |     /// - `packet`: the packet-like to send
 | ||||||
|  | @ -58,23 +176,70 @@ impl Connection { | ||||||
|     /// # Examples
 |     /// # Examples
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// ```rust
 |     /// ```rust
 | ||||||
|     /// # let connection = servicepoint::Connection::Fake;
 |     ///  let mut connection = servicepoint::Connection::FakeMutableSend;
 | ||||||
|     ///  // turn off all pixels on display
 |     ///  // turn off all pixels on display
 | ||||||
|     ///  connection.send(servicepoint::Command::Clear)
 |     ///  connection.send_mut(servicepoint::Command::Clear)
 | ||||||
|     ///      .expect("send failed");
 |     ///      .expect("send failed");
 | ||||||
|     /// ```
 |     /// ```
 | ||||||
|     pub fn send(&self, packet: impl Into<Packet>) -> Result<(), SendError> { |     pub fn send_mut( | ||||||
|         let packet = packet.into(); |         &mut self, | ||||||
|         debug!("sending {packet:?}"); |         packet: impl Into<Packet>, | ||||||
|         let data: Vec<u8> = packet.into(); |     ) -> Result<(), SendError> { | ||||||
|         match self { |         match self { | ||||||
|             Connection::Udp(socket) => { |             #[cfg(feature = "protocol_websocket")] | ||||||
|  |             Connection::WebSocket(socket) => { | ||||||
|  |                 let packet = packet.into(); | ||||||
|  |                 log::debug!("sending {packet:?}"); | ||||||
|  |                 let data: Vec<u8> = packet.into(); | ||||||
|                 socket |                 socket | ||||||
|                     .send(&data) |                     .send(tungstenite::Message::Binary(data)) | ||||||
|                     .map_err(SendError::IoError) |                     .map_err(SendError::WebsocketError) | ||||||
|                     .map(move |_| ()) // ignore Ok value
 |  | ||||||
|             } |             } | ||||||
|             Connection::Fake => Ok(()), |             Connection::FakeMutableSend => { | ||||||
|  |                 let packet = packet.into(); | ||||||
|  |                 log::debug!("sending {packet:?}"); | ||||||
|  |                 let data: Vec<u8> = packet.into(); | ||||||
|  |                 let _ = data; | ||||||
|  |                 Ok(()) | ||||||
|  |             } | ||||||
|  |             _ => self.send(packet), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | impl Drop for Connection { | ||||||
|  |     fn drop(&mut self) { | ||||||
|  |         #[cfg(feature = "protocol_websocket")] | ||||||
|  |         if let Connection::WebSocket(sock) = self { | ||||||
|  |             _ = sock.close(None); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[cfg(test)] | ||||||
|  | mod tests { | ||||||
|  |     use super::*; | ||||||
|  |     use crate::packet::*; | ||||||
|  | 
 | ||||||
|  |     #[test] | ||||||
|  |     fn send_fake() { | ||||||
|  |         let data: &[u8] = &[0u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; | ||||||
|  |         let packet = Packet::try_from(data).unwrap(); | ||||||
|  |         Connection::Fake.send(packet).unwrap() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[test] | ||||||
|  |     fn send_fake_mutable() { | ||||||
|  |         let data: &[u8] = &[0u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; | ||||||
|  |         let packet = Packet::try_from(data).unwrap(); | ||||||
|  |         Connection::FakeMutableSend.send_mut(packet).unwrap() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[test] | ||||||
|  |     #[should_panic] | ||||||
|  |     fn send_fake_mutable_panic() { | ||||||
|  |         let data: &[u8] = &[0u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; | ||||||
|  |         let packet = Packet::try_from(data).unwrap(); | ||||||
|  |         Connection::FakeMutableSend.send(packet).unwrap() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										221
									
								
								crates/servicepoint/src/cp437.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										221
									
								
								crates/servicepoint/src/cp437.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,221 @@ | ||||||
|  | use crate::cp437::Cp437LoadError::InvalidChar; | ||||||
|  | use crate::{Grid, PrimitiveGrid}; | ||||||
|  | use std::collections::HashMap; | ||||||
|  | 
 | ||||||
|  | /// A grid containing codepage 437 characters.
 | ||||||
|  | ///
 | ||||||
|  | /// The encoding is currently not enforced.
 | ||||||
|  | pub type Cp437Grid = PrimitiveGrid<u8>; | ||||||
|  | 
 | ||||||
|  | /// A grid containing UTF-8 characters.
 | ||||||
|  | pub type CharGrid = PrimitiveGrid<char>; | ||||||
|  | 
 | ||||||
|  | #[derive(Debug)] | ||||||
|  | pub enum Cp437LoadError { | ||||||
|  |     InvalidChar { index: usize, 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, Cp437LoadError> { | ||||||
|  |         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(InvalidChar { 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::*; | ||||||
|  | 
 | ||||||
|  |     /// An array of 256 elements, mapping most of the CP437 values to UTF-8 characters
 | ||||||
|  |     ///
 | ||||||
|  |     /// Mostly follows CP437, except for:
 | ||||||
|  |     ///  * 0x0A & 0x0D are kept for use as line endings.
 | ||||||
|  |     ///  * 0x1A is used for SAUCE.
 | ||||||
|  |     ///  * 0x1B is used for ANSI escape sequences.
 | ||||||
|  |     ///
 | ||||||
|  |     /// These exclusions should be fine since most programs can't even use them
 | ||||||
|  |     /// without issues. And this makes rendering simpler too.
 | ||||||
|  |     ///
 | ||||||
|  |     /// See <https://en.wikipedia.org/wiki/Code_page_437#Character_set>
 | ||||||
|  |     ///
 | ||||||
|  |     /// Copied from https://github.com/kip93/cp437-tools. License: GPL-3.0
 | ||||||
|  |     #[rustfmt::skip] | ||||||
|  |     const CP437_TO_UTF8: [char; 256] = [ | ||||||
|  |         /* 0X */ '\0', '☺', '☻', '♥', '♦', '♣', '♠', '•', '◘', '○', '\n', '♂', '♀', '\r', '♫', '☼', | ||||||
|  |         /* 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 */ '≡', '±', '≥', '≤', '⌠', '⌡', '÷', '≈', '°', '∙', '·', '√', 'ⁿ', '²', '■', ' ', | ||||||
|  |     ]; | ||||||
|  | 
 | ||||||
|  |     const 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) | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     const MISSING_CHAR_CP437: u8 = 0x3F; | ||||||
|  | 
 | ||||||
|  |     impl From<&Cp437Grid> for CharGrid { | ||||||
|  |         fn from(value: &Cp437Grid) -> Self { | ||||||
|  |             let mut grid = Self::new(value.width(), value.height()); | ||||||
|  | 
 | ||||||
|  |             for y in 0..grid.height() { | ||||||
|  |                 for x in 0..grid.width() { | ||||||
|  |                     let converted = CP437_TO_UTF8[value.get(x, y) as usize]; | ||||||
|  |                     grid.set(x, y, converted); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             grid | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     impl From<&CharGrid> for Cp437Grid { | ||||||
|  |         fn from(value: &CharGrid) -> Self { | ||||||
|  |             let mut grid = Self::new(value.width(), value.height()); | ||||||
|  | 
 | ||||||
|  |             for y in 0..grid.height() { | ||||||
|  |                 for x in 0..grid.width() { | ||||||
|  |                     let char = value.get(x, y); | ||||||
|  |                     let converted = *UTF8_TO_CP437 | ||||||
|  |                         .get(&char) | ||||||
|  |                         .unwrap_or(&MISSING_CHAR_CP437); | ||||||
|  |                     grid.set(x, y, converted); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             grid | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     impl From<&str> for CharGrid { | ||||||
|  |         fn from(value: &str) -> Self { | ||||||
|  |             let value = value.replace("\r\n", "\n"); | ||||||
|  |             let lines = value.split('\n').collect::<Vec<_>>(); | ||||||
|  |             let width = | ||||||
|  |                 lines.iter().fold(0, move |a, x| std::cmp::max(a, x.len())); | ||||||
|  | 
 | ||||||
|  |             let mut grid = Self::new(width, lines.len()); | ||||||
|  |             for (y, line) in lines.iter().enumerate() { | ||||||
|  |                 for (x, char) in line.chars().enumerate() { | ||||||
|  |                     grid.set(x, y, char); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             grid | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[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); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[cfg(test)] | ||||||
|  | #[cfg(feature = "cp437")] | ||||||
|  | mod tests_feature_cp437 { | ||||||
|  |     use crate::{CharGrid, Cp437Grid}; | ||||||
|  | 
 | ||||||
|  |     #[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); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -79,12 +79,12 @@ pub trait Grid<T> { | ||||||
|         assert!( |         assert!( | ||||||
|             x < self.width(), |             x < self.width(), | ||||||
|             "cannot access index [{x}, {y}] because x is outside of bounds 0..{}", |             "cannot access index [{x}, {y}] because x is outside of bounds 0..{}", | ||||||
|             self.width() |             self.width() - 1 | ||||||
|         ); |         ); | ||||||
|         assert!( |         assert!( | ||||||
|             y < self.height(), |             y < self.height(), | ||||||
|             "cannot access byte [{x}, {y}] because y is outside of bounds 0..{}", |             "cannot access index [{x}, {y}] because y is outside of bounds 0..{}", | ||||||
|             self.height() |             self.height() - 1 | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,9 @@ | ||||||
| //! Abstractions for the UDP protocol of the CCCB servicepoint display.
 | //! Abstractions for the UDP protocol of the CCCB servicepoint display.
 | ||||||
| //!
 | //!
 | ||||||
|  | //! 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.
 | ||||||
|  | //!
 | ||||||
| //! # Examples
 | //! # Examples
 | ||||||
| //!
 | //!
 | ||||||
| //! ```rust
 | //! ```rust
 | ||||||
|  | @ -37,13 +41,13 @@ pub use bitvec; | ||||||
| use bitvec::prelude::{BitVec, Msb0}; | use bitvec::prelude::{BitVec, Msb0}; | ||||||
| 
 | 
 | ||||||
| pub use crate::brightness::{Brightness, BrightnessGrid}; | pub use crate::brightness::{Brightness, BrightnessGrid}; | ||||||
| pub use crate::command::{Command, Cp437Grid, Offset}; | pub use crate::command::{Command, Offset}; | ||||||
| pub use crate::compression_code::CompressionCode; | pub use crate::compression_code::CompressionCode; | ||||||
| pub use crate::connection::Connection; | pub use crate::connection::Connection; | ||||||
|  | pub use crate::cp437::{CharGrid, Cp437Grid}; | ||||||
| pub use crate::data_ref::DataRef; | pub use crate::data_ref::DataRef; | ||||||
| pub use crate::grid::Grid; | pub use crate::grid::Grid; | ||||||
| pub use crate::origin::{Origin, Pixels, Tiles}; | pub use crate::origin::{Origin, Pixels, Tiles}; | ||||||
| pub use crate::packet::{Header, Packet, Payload}; |  | ||||||
| pub use crate::pixel_grid::PixelGrid; | pub use crate::pixel_grid::PixelGrid; | ||||||
| pub use crate::primitive_grid::PrimitiveGrid; | pub use crate::primitive_grid::PrimitiveGrid; | ||||||
| 
 | 
 | ||||||
|  | @ -55,10 +59,11 @@ mod command_code; | ||||||
| mod compression; | mod compression; | ||||||
| mod compression_code; | mod compression_code; | ||||||
| mod connection; | mod connection; | ||||||
|  | mod cp437; | ||||||
| mod data_ref; | mod data_ref; | ||||||
| mod grid; | mod grid; | ||||||
| mod origin; | mod origin; | ||||||
| mod packet; | pub mod packet; | ||||||
| mod pixel_grid; | mod pixel_grid; | ||||||
| mod primitive_grid; | mod primitive_grid; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | use crate::TILE_SIZE; | ||||||
| use std::marker::PhantomData; | use std::marker::PhantomData; | ||||||
| 
 | 
 | ||||||
| /// An origin marks the top left position of a window sent to the display.
 | /// An origin marks the top left position of a window sent to the display.
 | ||||||
|  | @ -11,7 +12,14 @@ pub struct Origin<Unit: DisplayUnit> { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl<Unit: DisplayUnit> Origin<Unit> { | impl<Unit: DisplayUnit> Origin<Unit> { | ||||||
|     /// Create a new `Origin` instance for the provided position.
 |     /// Top-left. Equivalent to `Origin::new(0, 0)`.
 | ||||||
|  |     pub const ZERO: Self = Self { | ||||||
|  |         x: 0, | ||||||
|  |         y: 0, | ||||||
|  |         phantom_data: PhantomData, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     /// Create a new [Origin] instance for the provided position.
 | ||||||
|     pub fn new(x: usize, y: usize) -> Self { |     pub fn new(x: usize, y: usize) -> Self { | ||||||
|         Self { |         Self { | ||||||
|             x, |             x, | ||||||
|  | @ -46,3 +54,69 @@ pub struct Tiles(); | ||||||
| impl DisplayUnit for Pixels {} | impl DisplayUnit for Pixels {} | ||||||
| 
 | 
 | ||||||
| impl DisplayUnit for Tiles {} | impl DisplayUnit for Tiles {} | ||||||
|  | 
 | ||||||
|  | impl From<&Origin<Tiles>> for Origin<Pixels> { | ||||||
|  |     fn from(value: &Origin<Tiles>) -> Self { | ||||||
|  |         Self { | ||||||
|  |             x: value.x * TILE_SIZE, | ||||||
|  |             y: value.y * TILE_SIZE, | ||||||
|  |             phantom_data: PhantomData, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl TryFrom<&Origin<Pixels>> for Origin<Tiles> { | ||||||
|  |     type Error = (); | ||||||
|  | 
 | ||||||
|  |     fn try_from(value: &Origin<Pixels>) -> Result<Self, Self::Error> { | ||||||
|  |         let (x, x_rem) = (value.x / TILE_SIZE, value.x % TILE_SIZE); | ||||||
|  |         if x_rem != 0 { | ||||||
|  |             return Err(()); | ||||||
|  |         } | ||||||
|  |         let (y, y_rem) = (value.y / TILE_SIZE, value.y % TILE_SIZE); | ||||||
|  |         if y_rem != 0 { | ||||||
|  |             return Err(()); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         Ok(Self { | ||||||
|  |             x, | ||||||
|  |             y, | ||||||
|  |             phantom_data: PhantomData, | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[cfg(test)] | ||||||
|  | mod tests { | ||||||
|  |     use super::*; | ||||||
|  | 
 | ||||||
|  |     #[test] | ||||||
|  |     fn origin_tile_to_pixel() { | ||||||
|  |         let tile: Origin<Tiles> = Origin::new(1, 2); | ||||||
|  |         let actual: Origin<Pixels> = Origin::from(&tile); | ||||||
|  |         let expected: Origin<Pixels> = Origin::new(8, 16); | ||||||
|  |         assert_eq!(actual, expected); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[test] | ||||||
|  |     fn origin_pixel_to_tile() { | ||||||
|  |         let pixel: Origin<Pixels> = Origin::new(8, 16); | ||||||
|  |         let actual: Origin<Tiles> = Origin::try_from(&pixel).unwrap(); | ||||||
|  |         let expected: Origin<Tiles> = Origin::new(1, 2); | ||||||
|  |         assert_eq!(actual, expected); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[test] | ||||||
|  |     #[should_panic] | ||||||
|  |     fn origin_pixel_to_tile_fail_y() { | ||||||
|  |         let pixel: Origin<Pixels> = Origin::new(8, 15); | ||||||
|  |         let _: Origin<Tiles> = Origin::try_from(&pixel).unwrap(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[test] | ||||||
|  |     #[should_panic] | ||||||
|  |     fn origin_pixel_to_tile_fail_x() { | ||||||
|  |         let pixel: Origin<Pixels> = Origin::new(7, 16); | ||||||
|  |         let _: Origin<Tiles> = Origin::try_from(&pixel).unwrap(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -1,10 +1,34 @@ | ||||||
|  | //! Raw packet manipulation.
 | ||||||
|  | //!
 | ||||||
|  | //! Should probably only be used directly to use features not exposed by the library.
 | ||||||
|  | //!
 | ||||||
|  | //! # Examples
 | ||||||
|  | //!
 | ||||||
|  | //! Converting a packet to a command and back:
 | ||||||
|  | //!
 | ||||||
|  | //! ```rust
 | ||||||
|  | //! use servicepoint::{Command, packet::Packet};
 | ||||||
|  | //! # let command = Command::Clear;
 | ||||||
|  | //! let packet: Packet = command.into();
 | ||||||
|  | //! let command: Command = Command::try_from(packet).expect("could not read command from packet");
 | ||||||
|  | //! ```
 | ||||||
|  | //!
 | ||||||
|  | //! Converting a packet to bytes and back:
 | ||||||
|  | //!
 | ||||||
|  | //! ```rust
 | ||||||
|  | //! use servicepoint::{Command, packet::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 std::mem::size_of; | ||||||
| 
 | 
 | ||||||
| use crate::command_code::CommandCode; |  | ||||||
| use crate::compression::into_compressed; | use crate::compression::into_compressed; | ||||||
| use crate::{ | use crate::{ | ||||||
|     Command, CompressionCode, Grid, Offset, Origin, PixelGrid, Pixels, Tiles, |     command_code::CommandCode, Command, CompressionCode, Grid, Offset, Origin, | ||||||
|     TILE_SIZE, |     PixelGrid, Pixels, Tiles, TILE_SIZE, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /// A raw header.
 | /// A raw header.
 | ||||||
|  | @ -13,8 +37,6 @@ use crate::{ | ||||||
| /// payload, where applicable.
 | /// payload, where applicable.
 | ||||||
| ///
 | ///
 | ||||||
| /// Because the meaning of most fields depend on the command, there are no speaking names for them.
 | /// Because the meaning of most fields depend on the command, there are no speaking names for them.
 | ||||||
| ///
 |  | ||||||
| /// Should probably only be used directly to use features not exposed by the library.
 |  | ||||||
| #[derive(Copy, Clone, Debug, PartialEq)] | #[derive(Copy, Clone, Debug, PartialEq)] | ||||||
| pub struct Header { | pub struct Header { | ||||||
|     /// The first two bytes specify which command this packet represents.
 |     /// The first two bytes specify which command this packet represents.
 | ||||||
|  | @ -38,26 +60,8 @@ pub type Payload = Vec<u8>; | ||||||
| ///
 | ///
 | ||||||
| /// Contents should probably only be used directly to use features not exposed by the library.
 | /// Contents should probably only be used directly to use features not exposed by the library.
 | ||||||
| ///
 | ///
 | ||||||
| /// # Examples
 | /// You may want to use [Command] instead.
 | ||||||
| ///
 | ///
 | ||||||
| /// Converting a packet to a command and back:
 |  | ||||||
| ///
 |  | ||||||
| /// ```rust
 |  | ||||||
| /// # use servicepoint::{Command, Packet};
 |  | ||||||
| /// # let command = Command::Clear;
 |  | ||||||
| /// let packet: Packet = command.into();
 |  | ||||||
| /// let command: Command = Command::try_from(packet).expect("could not read packet");
 |  | ||||||
| /// ```
 |  | ||||||
| ///
 |  | ||||||
| /// Converting a packet to bytes and back:
 |  | ||||||
| ///
 |  | ||||||
| /// ```rust
 |  | ||||||
| /// # 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");
 |  | ||||||
| /// ```
 |  | ||||||
| ///
 | ///
 | ||||||
| #[derive(Clone, Debug, PartialEq)] | #[derive(Clone, Debug, PartialEq)] | ||||||
| pub struct Packet { | pub struct Packet { | ||||||
|  | @ -98,9 +102,9 @@ impl From<Packet> for Vec<u8> { | ||||||
| impl TryFrom<&[u8]> for Packet { | impl TryFrom<&[u8]> for Packet { | ||||||
|     type Error = (); |     type Error = (); | ||||||
| 
 | 
 | ||||||
|     /// Tries to interpret the bytes as a `Packet`.
 |     /// Tries to interpret the bytes as a [Packet].
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// returns: `Error` if slice is not long enough to be a `Packet`
 |     /// returns: `Error` if slice is not long enough to be a [Packet]
 | ||||||
|     fn try_from(value: &[u8]) -> Result<Self, Self::Error> { |     fn try_from(value: &[u8]) -> Result<Self, Self::Error> { | ||||||
|         if value.len() < size_of::<Header>() { |         if value.len() < size_of::<Header>() { | ||||||
|             return Err(()); |             return Err(()); | ||||||
|  | @ -135,7 +139,7 @@ impl TryFrom<Vec<u8>> for Packet { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl From<Command> for Packet { | impl From<Command> for Packet { | ||||||
|     /// Move the `Command` into a `Packet` instance for sending.
 |     /// Move the [Command] into a [Packet] instance for sending.
 | ||||||
|     #[allow(clippy::cast_possible_truncation)] |     #[allow(clippy::cast_possible_truncation)] | ||||||
|     fn from(value: Command) -> Self { |     fn from(value: Command) -> Self { | ||||||
|         match value { |         match value { | ||||||
|  | @ -210,7 +214,7 @@ impl From<Command> for Packet { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl Packet { | impl Packet { | ||||||
|     /// Helper method for `BitMapLinear*`-Commands into `Packet`
 |     /// Helper method for `BitMapLinear*`-Commands into [Packet]
 | ||||||
|     #[allow(clippy::cast_possible_truncation)] |     #[allow(clippy::cast_possible_truncation)] | ||||||
|     fn bitmap_linear_into_packet( |     fn bitmap_linear_into_packet( | ||||||
|         command: CommandCode, |         command: CommandCode, | ||||||
|  | @ -312,7 +316,7 @@ impl Packet { | ||||||
| 
 | 
 | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mod tests { | mod tests { | ||||||
|     use crate::{Header, Packet}; |     use super::*; | ||||||
| 
 | 
 | ||||||
|     #[test] |     #[test] | ||||||
|     fn round_trip() { |     fn round_trip() { | ||||||
|  | @ -327,7 +331,7 @@ mod tests { | ||||||
|             payload: vec![42u8; 23], |             payload: vec![42u8; 23], | ||||||
|         }; |         }; | ||||||
|         let data: Vec<u8> = p.into(); |         let data: Vec<u8> = p.into(); | ||||||
|         let p = Packet::try_from(&*data).unwrap(); |         let p = Packet::try_from(data).unwrap(); | ||||||
|         assert_eq!( |         assert_eq!( | ||||||
|             p, |             p, | ||||||
|             Packet { |             Packet { | ||||||
|  |  | ||||||
|  | @ -13,14 +13,14 @@ pub struct PixelGrid { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl PixelGrid { | impl PixelGrid { | ||||||
|     /// Creates a new `PixelGrid` with the specified dimensions.
 |     /// Creates a new [PixelGrid] with the specified dimensions.
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// # Arguments
 |     /// # Arguments
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// - `width`: size in pixels in x-direction
 |     /// - `width`: size in pixels in x-direction
 | ||||||
|     /// - `height`: size in pixels in y-direction
 |     /// - `height`: size in pixels in y-direction
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// returns: `PixelGrid` initialized to all pixels off
 |     /// returns: [PixelGrid] initialized to all pixels off
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// # Panics
 |     /// # Panics
 | ||||||
|     ///
 |     ///
 | ||||||
|  | @ -40,14 +40,14 @@ impl PixelGrid { | ||||||
|         Self::new(PIXEL_WIDTH, PIXEL_HEIGHT) |         Self::new(PIXEL_WIDTH, PIXEL_HEIGHT) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Loads a `PixelGrid` with the specified dimensions from the provided data.
 |     /// Loads a [PixelGrid] with the specified dimensions from the provided data.
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// # Arguments
 |     /// # Arguments
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// - `width`: size in pixels in x-direction
 |     /// - `width`: size in pixels in x-direction
 | ||||||
|     /// - `height`: size in pixels in y-direction
 |     /// - `height`: size in pixels in y-direction
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// returns: `PixelGrid` that contains a copy of the provided data
 |     /// returns: [PixelGrid] that contains a copy of the provided data
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// # Panics
 |     /// # Panics
 | ||||||
|     ///
 |     ///
 | ||||||
|  | @ -64,7 +64,7 @@ impl PixelGrid { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Iterate over all cells in `PixelGrid`.
 |     /// Iterate over all cells in [PixelGrid].
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// Order is equivalent to the following loop:
 |     /// Order is equivalent to the following loop:
 | ||||||
|     /// ```
 |     /// ```
 | ||||||
|  | @ -80,7 +80,7 @@ impl PixelGrid { | ||||||
|         self.bit_vec.iter().by_refs() |         self.bit_vec.iter().by_refs() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Iterate over all cells in `PixelGrid` mutably.
 |     /// Iterate over all cells in [PixelGrid] mutably.
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// Order is equivalent to the following loop:
 |     /// Order is equivalent to the following loop:
 | ||||||
|     /// ```
 |     /// ```
 | ||||||
|  | @ -107,7 +107,7 @@ impl PixelGrid { | ||||||
|         self.bit_vec.iter_mut() |         self.bit_vec.iter_mut() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Iterate over all rows in `PixelGrid` top to bottom.
 |     /// Iterate over all rows in [PixelGrid] top to bottom.
 | ||||||
|     pub fn iter_rows(&self) -> IterRows { |     pub fn iter_rows(&self) -> IterRows { | ||||||
|         IterRows { |         IterRows { | ||||||
|             pixel_grid: self, |             pixel_grid: self, | ||||||
|  | @ -117,7 +117,7 @@ impl PixelGrid { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl Grid<bool> for PixelGrid { | impl Grid<bool> for PixelGrid { | ||||||
|     /// Sets the value of the specified position in the `PixelGrid`.
 |     /// Sets the value of the specified position in the [PixelGrid].
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// # Arguments
 |     /// # Arguments
 | ||||||
|     ///
 |     ///
 | ||||||
|  | @ -139,7 +139,7 @@ impl Grid<bool> for PixelGrid { | ||||||
|         self.bit_vec[x + y * self.width] |         self.bit_vec[x + y * self.width] | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Sets the state of all pixels in the `PixelGrid`.
 |     /// Sets the state of all pixels in the [PixelGrid].
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// # Arguments
 |     /// # Arguments
 | ||||||
|     ///
 |     ///
 | ||||||
|  | @ -169,7 +169,7 @@ impl DataRef<u8> for PixelGrid { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl From<PixelGrid> for Vec<u8> { | impl From<PixelGrid> for Vec<u8> { | ||||||
|     /// Turns a `PixelGrid` into the underlying `Vec<u8>`.
 |     /// Turns a [PixelGrid] into the underlying [`Vec<u8>`].
 | ||||||
|     fn from(value: PixelGrid) -> Self { |     fn from(value: PixelGrid) -> Self { | ||||||
|         value.bit_vec.into() |         value.bit_vec.into() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -14,14 +14,14 @@ pub struct PrimitiveGrid<T: PrimitiveGridType> { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl<T: PrimitiveGridType> PrimitiveGrid<T> { | impl<T: PrimitiveGridType> PrimitiveGrid<T> { | ||||||
|     /// Creates a new `PrimitiveGrid` with the specified dimensions.
 |     /// Creates a new [PrimitiveGrid] with the specified dimensions.
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// # Arguments
 |     /// # Arguments
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// - width: size in x-direction
 |     /// - width: size in x-direction
 | ||||||
|     /// - height: size in y-direction
 |     /// - height: size in y-direction
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// returns: `PrimitiveGrid` initialized to default value.
 |     /// returns: [PrimitiveGrid] initialized to default value.
 | ||||||
|     pub fn new(width: usize, height: usize) -> Self { |     pub fn new(width: usize, height: usize) -> Self { | ||||||
|         Self { |         Self { | ||||||
|             data: vec![Default::default(); width * height], |             data: vec![Default::default(); width * height], | ||||||
|  | @ -30,9 +30,9 @@ impl<T: PrimitiveGridType> PrimitiveGrid<T> { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Loads a `PrimitiveGrid` with the specified dimensions from the provided data.
 |     /// Loads a [PrimitiveGrid] with the specified dimensions from the provided data.
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// returns: `PrimitiveGrid` that contains a copy of the provided data
 |     /// returns: [PrimitiveGrid] that contains a copy of the provided data
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// # Panics
 |     /// # Panics
 | ||||||
|     ///
 |     ///
 | ||||||
|  | @ -47,7 +47,7 @@ impl<T: PrimitiveGridType> PrimitiveGrid<T> { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Iterate over all cells in `PrimitiveGrid`.
 |     /// Iterate over all cells in [PrimitiveGrid].
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// Order is equivalent to the following loop:
 |     /// Order is equivalent to the following loop:
 | ||||||
|     /// ```
 |     /// ```
 | ||||||
|  | @ -63,7 +63,7 @@ impl<T: PrimitiveGridType> PrimitiveGrid<T> { | ||||||
|         self.data.iter() |         self.data.iter() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Iterate over all rows in `PrimitiveGrid` top to bottom.
 |     /// Iterate over all rows in [PrimitiveGrid] top to bottom.
 | ||||||
|     pub fn iter_rows(&self) -> IterRows<T> { |     pub fn iter_rows(&self) -> IterRows<T> { | ||||||
|         IterRows { |         IterRows { | ||||||
|             byte_grid: self, |             byte_grid: self, | ||||||
|  | @ -168,7 +168,7 @@ impl<T: PrimitiveGridType> DataRef<T> for PrimitiveGrid<T> { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl<T: PrimitiveGridType> From<PrimitiveGrid<T>> for Vec<T> { | impl<T: PrimitiveGridType> From<PrimitiveGrid<T>> for Vec<T> { | ||||||
|     /// Turn into the underlying `Vec<u8>` containing the rows of bytes.
 |     /// Turn into the underlying [`Vec<u8>`] containing the rows of bytes.
 | ||||||
|     fn from(value: PrimitiveGrid<T>) -> Self { |     fn from(value: PrimitiveGrid<T>) -> Self { | ||||||
|         value.data |         value.data | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -17,9 +17,12 @@ crate-type = ["staticlib", "cdylib", "rlib"] | ||||||
| cbindgen = "0.27.0" | cbindgen = "0.27.0" | ||||||
| 
 | 
 | ||||||
| [dependencies.servicepoint] | [dependencies.servicepoint] | ||||||
| version = "0.8.0" | version = "0.9.0" | ||||||
| path = "../servicepoint" | path = "../servicepoint" | ||||||
| features = ["all_compressions"] | features = ["all_compressions"] | ||||||
| 
 | 
 | ||||||
| [lints] | [lints] | ||||||
| workspace = true | workspace = true | ||||||
|  | 
 | ||||||
|  | [package.metadata.docs.rs] | ||||||
|  | all-features = true | ||||||
|  |  | ||||||
|  | @ -12,5 +12,6 @@ fn main() { | ||||||
|     let mut cc = cc::Build::new(); |     let mut cc = cc::Build::new(); | ||||||
|     cc.file("src/main.c"); |     cc.file("src/main.c"); | ||||||
|     cc.include(&sp_include); |     cc.include(&sp_include); | ||||||
|  |     cc.opt_level(2); | ||||||
|     cc.compile("lang_c"); |     cc.compile("lang_c"); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| /* Generated with cbindgen:0.26.0 */ | /* Generated with cbindgen:0.27.0 */ | ||||||
| 
 | 
 | ||||||
| /* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */ | /* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */ | ||||||
| 
 | 
 | ||||||
|  | @ -1355,5 +1355,5 @@ struct SPByteSlice sp_pixel_grid_unsafe_data_ref(struct SPPixelGrid *pixel_grid) | ||||||
| size_t sp_pixel_grid_width(const struct SPPixelGrid *pixel_grid); | size_t sp_pixel_grid_width(const struct SPPixelGrid *pixel_grid); | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| } // extern "C"
 | }  // extern "C"
 | ||||||
| #endif // __cplusplus
 | #endif  // __cplusplus
 | ||||||
|  |  | ||||||
|  | @ -40,7 +40,9 @@ pub unsafe extern "C" fn sp_cp437_grid_new( | ||||||
|     width: usize, |     width: usize, | ||||||
|     height: usize, |     height: usize, | ||||||
| ) -> *mut SPCp437Grid { | ) -> *mut SPCp437Grid { | ||||||
|     Box::into_raw(Box::new(SPCp437Grid(servicepoint::Cp437Grid::new(width, height)))) |     Box::into_raw(Box::new(SPCp437Grid(servicepoint::Cp437Grid::new( | ||||||
|  |         width, height, | ||||||
|  |     )))) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Loads a `SPCp437Grid` with the specified dimensions from the provided data.
 | /// Loads a `SPCp437Grid` with the specified dimensions from the provided data.
 | ||||||
|  | @ -67,7 +69,9 @@ pub unsafe extern "C" fn sp_cp437_grid_load( | ||||||
|     data_length: usize, |     data_length: usize, | ||||||
| ) -> *mut SPCp437Grid { | ) -> *mut SPCp437Grid { | ||||||
|     let data = std::slice::from_raw_parts(data, data_length); |     let data = std::slice::from_raw_parts(data, data_length); | ||||||
|     Box::into_raw(Box::new(SPCp437Grid(servicepoint::Cp437Grid::load(width, height, data)))) |     Box::into_raw(Box::new(SPCp437Grid(servicepoint::Cp437Grid::load( | ||||||
|  |         width, height, data, | ||||||
|  |     )))) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Clones a `SPCp437Grid`.
 | /// Clones a `SPCp437Grid`.
 | ||||||
|  |  | ||||||
|  | @ -7,7 +7,7 @@ use std::ptr::null_mut; | ||||||
| use crate::SPCommand; | use crate::SPCommand; | ||||||
| 
 | 
 | ||||||
| /// The raw packet
 | /// The raw packet
 | ||||||
| pub struct SPPacket(pub(crate) servicepoint::Packet); | pub struct SPPacket(pub(crate) servicepoint::packet::Packet); | ||||||
| 
 | 
 | ||||||
| /// Turns a `SPCommand` into a `SPPacket`.
 | /// Turns a `SPCommand` into a `SPPacket`.
 | ||||||
| /// The `SPCommand` gets consumed.
 | /// The `SPCommand` gets consumed.
 | ||||||
|  | @ -49,7 +49,7 @@ pub unsafe extern "C" fn sp_packet_try_load( | ||||||
|     length: usize, |     length: usize, | ||||||
| ) -> *mut SPPacket { | ) -> *mut SPPacket { | ||||||
|     let data = std::slice::from_raw_parts(data, length); |     let data = std::slice::from_raw_parts(data, length); | ||||||
|     match servicepoint::Packet::try_from(data) { |     match servicepoint::packet::Packet::try_from(data) { | ||||||
|         Err(_) => null_mut(), |         Err(_) => null_mut(), | ||||||
|         Ok(packet) => Box::into_raw(Box::new(SPPacket(packet))), |         Ok(packet) => Box::into_raw(Box::new(SPPacket(packet))), | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -13,8 +13,8 @@ test = false | ||||||
| csbindgen = "1.9.3" | csbindgen = "1.9.3" | ||||||
| 
 | 
 | ||||||
| [dependencies] | [dependencies] | ||||||
| servicepoint_binding_c = { version = "0.8.0", path = "../servicepoint_binding_c" } | servicepoint_binding_c = { version = "0.9.0", path = "../servicepoint_binding_c" } | ||||||
| servicepoint = { version = "0.8.0", path = "../servicepoint" } | servicepoint = { version = "0.9.0", path = "../servicepoint" } | ||||||
| 
 | 
 | ||||||
| [lints] | [lints] | ||||||
| workspace = true | workspace = true | ||||||
|  |  | ||||||
|  | @ -11,7 +11,7 @@ | ||||||
| 
 | 
 | ||||||
|     <PropertyGroup> |     <PropertyGroup> | ||||||
|         <PackageId>ServicePoint</PackageId> |         <PackageId>ServicePoint</PackageId> | ||||||
|         <Version>0.8.0</Version> |         <Version>0.9.0</Version> | ||||||
|         <Authors>Repository Authors</Authors> |         <Authors>Repository Authors</Authors> | ||||||
|         <Company>None</Company> |         <Company>None</Company> | ||||||
|         <Product>ServicePoint</Product> |         <Product>ServicePoint</Product> | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Vinzenz Schroeter
						Vinzenz Schroeter