diff --git a/Cargo.lock b/Cargo.lock index b6baa49..9b365db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + [[package]] name = "adler2" version = "2.0.1" @@ -46,6 +55,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.5.0" @@ -75,6 +90,88 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "axum" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" +dependencies = [ + "axum-core", + "axum-macros", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "multer", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + [[package]] name = "bit_field" version = "0.10.3" @@ -118,10 +215,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] -name = "cc" -version = "1.2.38" +name = "bytes" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cc" +version = "1.2.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f" dependencies = [ "find-msvc-tools", "jobserver", @@ -197,6 +300,15 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "equator" version = "0.4.2" @@ -283,6 +395,60 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + [[package]] name = "getrandom" version = "0.2.16" @@ -316,6 +482,31 @@ dependencies = [ "weezl", ] +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "h2" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "2.6.0" @@ -338,6 +529,90 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", +] + +[[package]] +name = "hyper-util" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "hyper", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "image" version = "0.25.8" @@ -374,9 +649,9 @@ dependencies = [ [[package]] name = "imgref" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" +checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" [[package]] name = "indexmap" @@ -399,6 +674,17 @@ dependencies = [ "syn", ] +[[package]] +name = "io-uring" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + [[package]] name = "itertools" version = "0.12.1" @@ -408,6 +694,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + [[package]] name = "jobserver" version = "0.1.34" @@ -426,9 +718,9 @@ checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" [[package]] name = "libc" -version = "0.2.175" +version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" [[package]] name = "libfuzzer-sys" @@ -440,6 +732,16 @@ dependencies = [ "cc", ] +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.28" @@ -455,6 +757,12 @@ dependencies = [ "imgref", ] +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + [[package]] name = "maybe-rayon" version = "0.1.1" @@ -467,9 +775,15 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "minimal-lexical" @@ -487,6 +801,17 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys", +] + [[package]] name = "moxcms" version = "0.7.5" @@ -497,6 +822,23 @@ dependencies = [ "pxfm", ] +[[package]] +name = "multer" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "memchr", + "mime", + "spin", + "version_check", +] + [[package]] name = "new_debug_unreachable" version = "1.0.6" @@ -569,18 +911,68 @@ dependencies = [ "autocfg", ] +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkg-config" version = "0.3.32" @@ -613,7 +1005,10 @@ dependencies = [ name = "printserv" version = "0.1.0" dependencies = [ + "axum", "image", + "tokio", + "tower-http", ] [[package]] @@ -783,12 +1178,27 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "redox_syscall" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +dependencies = [ + "bitflags", +] + [[package]] name = "rgb" version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + [[package]] name = "rustversion" version = "1.0.22" @@ -796,34 +1206,70 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] -name = "serde" -version = "1.0.226" +name = "ryu" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.227" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80ece43fc6fbed4eb5392ab50c07334d3e577cbf40997ee896fe7af40bba4245" dependencies = [ "serde_core", ] [[package]] name = "serde_core" -version = "1.0.226" +version = "1.0.227" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" +checksum = "7a576275b607a2c86ea29e410193df32bc680303c82f31e275bbfcafe8b33be5" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.226" +version = "1.0.227" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" +checksum = "51e694923b8824cf0e9b382adf0f60d4e05f348f357b38833a3fa5ed7c2ede04" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + [[package]] name = "serde_spanned" version = "0.6.9" @@ -833,12 +1279,33 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +dependencies = [ + "libc", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -854,12 +1321,34 @@ dependencies = [ "quote", ] +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "syn" version = "2.0.106" @@ -871,6 +1360,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + [[package]] name = "system-deps" version = "6.2.2" @@ -924,6 +1419,50 @@ dependencies = [ "zune-jpeg", ] +[[package]] +name = "tokio" +version = "1.47.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "slab", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-util" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "0.8.23" @@ -958,6 +1497,71 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags", + "bytes", + "http", + "http-body", + "http-body-util", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", +] + [[package]] name = "unicode-ident" version = "1.0.19" @@ -981,6 +1585,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -1007,9 +1617,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" dependencies = [ "cfg-if", "once_cell", @@ -1020,9 +1630,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" dependencies = [ "bumpalo", "log", @@ -1034,9 +1644,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1044,9 +1654,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ "proc-macro2", "quote", @@ -1057,9 +1667,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" dependencies = [ "unicode-ident", ] @@ -1070,6 +1680,85 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" +[[package]] +name = "windows-link" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "winnow" version = "0.7.13" diff --git a/Cargo.toml b/Cargo.toml index c3e666f..96e58bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,3 +5,8 @@ edition = "2024" [dependencies] image = {version = "0.25.8", features = ["png"]} + +# axum = { version = "0.8.4", features = ["multipart", "debug"] } +axum = { version = "0.8.4", features = ["multipart", "http2", "macros"] } +tokio = { version = "1.0", features = ["full"] } +tower-http = { version = "0.6.1", features = ["limit", "trace"] } diff --git a/src/dithering.rs b/src/dithering.rs index 8006276..bc33b6f 100644 --- a/src/dithering.rs +++ b/src/dithering.rs @@ -1,44 +1,31 @@ -use image::DynamicImage; +use image::{ + DynamicImage, GrayImage, + imageops::{self, colorops}, +}; +pub fn auto_brighten(gray: &mut GrayImage) -> () { + let avg = gray.pixels().map(|p| p.0[0] as u32).sum::() / gray.len() as u32; + + if avg < 100 { + colorops::brighten_in_place(gray, 40); + colorops::contrast_in_place(gray, 25.0); + } else if avg > 180 { + colorops::brighten_in_place(gray, -15); + } +} + +pub fn dither(gray: &mut GrayImage) -> Vec { + imageops::dither(gray, &imageops::colorops::BiLevel); -pub fn atkinson_mono(img: &DynamicImage) -> Vec { - let mut gray = img.to_luma8(); let (w, h) = (gray.width() as usize, gray.height() as usize); let pitch = (w + 7) >> 3; let mut bits = vec![0u8; pitch * h]; - let buf = gray.as_mut(); for y in 0..h { - let row = y * w; for x in 0..w { - let i = row + x; - let old = buf[i]; - let new = if old < 128 { 0 } else { 255 }; - buf[i] = new; - let bit = new == 0; - if bit { - let byte = y * pitch + (x >> 3); - bits[byte] |= 128 >> (x & 7); - } - let err = ((old as i16) - (new as i16)) / 8; - - if x + 1 < w { - buf[i + 1] = (buf[i + 1] as i16 + err).clamp(0, 255) as u8; - } - if x + 2 < w { - buf[i + 2] = (buf[i + 2] as i16 + err).clamp(0, 255) as u8; - } - if y + 1 < h { - let down = i + w; - if x > 0 { - buf[down - 1] = (buf[down - 1] as i16 + err).clamp(0, 255) as u8; - } - buf[down] = (buf[down] as i16 + err).clamp(0, 255) as u8; - if x + 1 < w { - buf[down + 1] = (buf[down + 1] as i16 + err).clamp(0, 255) as u8; - } - if y + 2 < h { - buf[down + w] = (buf[down + w] as i16 + err).clamp(0, 255) as u8; - } + let byte = y * pitch + (x >> 3); + let bit = 7 - (x & 7); + if gray[(x as u32, y as u32)].0[0] == 0 { + bits[byte] |= 1 << bit; } } } diff --git a/src/escpos.rs b/src/escpos.rs deleted file mode 100644 index d5c1b67..0000000 --- a/src/escpos.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::dithering::atkinson_mono; -use image::{DynamicImage, imageops::FilterType}; - -const MAX_WIDTH: u32 = 384; - -pub enum ImageOrientation { - Preserve, - Largest, -} - -pub fn escpos_raster( - img: &DynamicImage, - orientation: Option, - mode: Option, -) -> Vec { - let mode = mode.unwrap_or(0); - assert!(mode <= 3 || (48..=51).contains(&mode)); - - let (w, h) = (img.width(), img.height()); - let swap = matches!(orientation, Some(ImageOrientation::Largest)) - && h.min(MAX_WIDTH) * w > w.min(MAX_WIDTH) * h; - - let (w, h) = if swap { (h, w) } else { (w, h) }; - let (w, h) = (MAX_WIDTH, h.saturating_mul(MAX_WIDTH).div_ceil(w)); - - let img = if swap { &img.rotate90() } else { &img }; - let img = img.resize(w, h, FilterType::Triangle); - - let mono = atkinson_mono(&img); - let (width_px, height_px) = (img.width() as u16, img.height() as u16); - let width_bytes = ((width_px + 7) >> 3) as u16; - - let mut out = Vec::with_capacity(8 + mono.len()); - out.extend_from_slice(&[ - 0x1D, - 0x76, - 0x30, - mode, - width_bytes as u8, - (width_bytes >> 8) as u8, - height_px as u8, - (height_px >> 8) as u8, - ]); - out.extend_from_slice(&mono); - out -} diff --git a/src/escpos/errors.rs b/src/escpos/errors.rs new file mode 100644 index 0000000..d7761f1 --- /dev/null +++ b/src/escpos/errors.rs @@ -0,0 +1,8 @@ +#[derive(Debug)] +pub enum EscPosError { + EmptyQueue, + InvalidQueueIndex, + InvalidBitmapMode, + InvalidBarcodeLength(String), + InvalidTextSize, +} diff --git a/src/escpos/job.rs b/src/escpos/job.rs new file mode 100644 index 0000000..2677737 --- /dev/null +++ b/src/escpos/job.rs @@ -0,0 +1,266 @@ +use crate::{ + dithering::{auto_brighten, dither}, + escpos::errors::EscPosError, +}; +use image::{DynamicImage, imageops::FilterType}; + +#[repr(u8)] +pub enum QREcc { + Low = 48, + Medium = 49, + Quartile = 50, + High = 51, +} +#[repr(u8)] +pub enum BARTextPosition { + Hidden = 0, + Above = 1, + Below = 2, + Both = 3, +} +#[repr(u8)] +pub enum BARType { + CODE128 = 0x49, + EAN13 = 0x43, + UPCA = 0x41, +} +pub enum ImageOrientation { + Preserve, + Largest, +} +#[repr(u8)] +pub enum JustifyOrientation { + Left = 0, + Center = 1, + Right = 2, +} + +pub enum TextEffect { + Bold, + DoubleHeight, + DoubleWidth, + InvertColor, + Justify(JustifyOrientation), +} + +pub struct Job { + pub ready: bool, + pub content: Vec, + max_width: u16, +} + +fn is_numeric(s: &[u8]) -> bool { + s.iter().all(|&b| b.is_ascii_digit()) +} + +impl Copy for JustifyOrientation {} +impl Clone for JustifyOrientation { + fn clone(&self) -> Self { + *self + } +} + +impl Job { + pub fn new(max_width: u16) -> Self { + Job { + content: vec![0x1B, b'@'], + ready: false, + max_width: max_width, + } + } + + pub fn write_feed(&mut self, amount: Option) -> () { + let amount = amount.unwrap_or(1); + self.content.extend_from_slice(&[0x1B, b'd', amount]) + } + pub fn write_text( + &mut self, + text: &String, + text_effect: &[TextEffect], + ) -> Result<(), EscPosError> { + let buf = &mut self.content; + buf.extend_from_slice(&[0x1B, b'@']); + + for i in text_effect { + match i { + TextEffect::Bold => buf.extend_from_slice(&[0x1B, b'E', 1]), + TextEffect::DoubleWidth => buf.extend_from_slice(&[0x1B, b'E', 1]), + TextEffect::DoubleHeight => buf.extend_from_slice(&[0x1B, b'E', 1]), + TextEffect::InvertColor => buf.extend_from_slice(&[0x1D, 0x42, 1]), + TextEffect::Justify(orientation) => { + buf.extend_from_slice(&[0x1B, b'a', orientation.clone() as u8]) + } + } + } + + buf.extend_from_slice(text.as_bytes()); + Ok(()) + } + + pub fn write_bitmap( + &mut self, + img: &DynamicImage, + orientation: Option, + mode: Option, + ) -> Result<(), EscPosError> { + let mode = mode.unwrap_or(0); + if !(mode <= 3 || (48..=51).contains(&mode)) { + return Err(EscPosError::InvalidBitmapMode); + } + + let (iw, ih) = (img.width(), img.height()); + let mw = self.max_width as u32; + + let rotate = matches!(orientation, Some(ImageOrientation::Largest)) && { + let scale_a = mw as f32 / iw as f32; + let out_h_a = (ih as f32 * scale_a).round() as u32; + let area_a = (mw as u64) * (out_h_a as u64); + let scale_b = mw as f32 / ih as f32; + let out_h_b = (iw as f32 * scale_b).round() as u32; + let area_b = (mw as u64) * (out_h_b as u64); + area_b > area_a + }; + + let (pw, ph) = if rotate { (ih, iw) } else { (iw, ih) }; + let target_w = mw; + let target_h = (ph as f32 * (mw as f32 / pw as f32)).round() as u32; + + let mut rotated: Option = None; + if rotate { + rotated = Some(img.rotate90()); + } + let src: &DynamicImage = rotated.as_ref().unwrap_or(img); + let resized = DynamicImage::ImageRgba8(image::imageops::resize( + src, + target_w, + target_h, + FilterType::Triangle, + )); + + let mut gray = resized.to_luma8(); + auto_brighten(&mut gray); + let mono = dither(&mut gray); + + let w = target_w; + let h = target_h; + let width_bytes = ((w + 7) >> 3) as u16; + + let buf = &mut self.content; + buf.reserve(8 + mono.len()); + buf.extend_from_slice(&[ + 0x1B, + b'@', + 0x1D, + 0x76, + 0x30, + mode, + (width_bytes & 0xFF) as u8, + ((width_bytes >> 8) & 0xFF) as u8, + (h & 0xFF) as u8, + ((h >> 8) & 0xFF) as u8, + ]); + buf.extend_from_slice(&mono); + + Ok(()) + } + pub fn write_qr( + &mut self, + text: String, + size: Option, + ecc: Option, + ) -> Result<(), EscPosError> { + let buf = &mut self.content; + let ecc = ecc.unwrap_or(QREcc::Medium); + let size = match size { + Some(v) => v.clamp(6, 15), + None => 10, + }; + + let text = text.as_bytes(); + let len = text.len() + 3; + buf.extend_from_slice(&[ + 0x1B, + b'@', + // Size + 0x1D, + 0x28, + 0x6B, + 0x03, + 0x00, + 0x31, + 0x43, + size, + // Error correction level + 0x1D, + 0x28, + 0x6B, + 0x03, + 0x00, + 0x31, + 0x45, + ecc as u8, + // Storing data... + 0x1D, + 0x28, + 0x6B, + (len & 0xFF) as u8, + ((len >> 8) & 0xFF) as u8, + 0x31, + 0x50, + 0x30, + ]); + buf.extend(text); + buf.extend(&[0x1D, 0x28, 0x6B, 0x03, 0x00, 0x31, 0x51, 0x30, 0x0A]); // Print! + + return Ok(()); + } + pub fn write_barcode( + &mut self, + text: String, + height: Option, + mod_width: Option, + text_position: Option, + bar_type: Option, + ) -> Result<(), EscPosError> { + let height: u8 = height.unwrap_or(80); + let mod_width: u8 = mod_width.unwrap_or(2); + let text_position = text_position.unwrap_or(BARTextPosition::Below) as u8; + let bar_type = bar_type.unwrap_or(BARType::CODE128); + + let text = text.as_bytes(); + let len = text.len() as u8; + let is_num = is_numeric(text); + match bar_type { + BARType::UPCA if len != 11 || !is_num => Err(EscPosError::InvalidBarcodeLength( + "UCPA Requires 11 numbers.".to_string(), + )), + + BARType::EAN13 if len != 12 || !is_num => Err(EscPosError::InvalidBarcodeLength( + "EAN13 Requires 12 numbers.".to_string(), + )), + _ => Ok(()), + }?; + + let buf = &mut self.content; + buf.extend([ + 0x1B, + b'@', + 0x1D, + 0x68, + height, + 0x1D, + 0x77, + mod_width, + 0x1d, + 0x48, + text_position, + 0x1D, + 0x6B, + bar_type as u8, + len, + ]); + buf.extend(text); + + Ok(()) + } +} diff --git a/src/escpos/mod.rs b/src/escpos/mod.rs new file mode 100644 index 0000000..b03aae7 --- /dev/null +++ b/src/escpos/mod.rs @@ -0,0 +1,3 @@ +pub mod errors; +pub mod job; +pub mod printer; diff --git a/src/escpos/printer.rs b/src/escpos/printer.rs new file mode 100644 index 0000000..3fe7da9 --- /dev/null +++ b/src/escpos/printer.rs @@ -0,0 +1,43 @@ +use crate::escpos::{errors::EscPosError, job::Job}; +use std::io::Write; + +pub struct Printer { + pub queue: Vec, + pub max_width: u16, +} +impl Printer { + pub fn new(max_width: u16) -> Self { + Printer { + queue: Vec::new(), + max_width: max_width, + } + } + + pub fn new_job(&mut self) -> Result<&mut Job, EscPosError> { + self.queue.push(Job::new(self.max_width)); + self.queue.last_mut().ok_or(EscPosError::InvalidQueueIndex) + } + + fn extract_job(&mut self) -> Result { + self.queue + .extract_if(.., |j| j.ready) + .next() + .ok_or(EscPosError::EmptyQueue) + } + pub fn print_job(&mut self, writer: &mut impl Write) -> Result<(), EscPosError> { + let mut job = self.extract_job()?; + job.write_feed(Some(2)); + + writer.write(&job.content).unwrap(); + Ok(()) + } + pub fn export_job(&mut self) -> Result, EscPosError> { + let mut job = self.extract_job()?; + job.write_feed(Some(2)); + + let mut out = Vec::with_capacity(2 + job.content.len()); + out.extend(job.content); + + Ok(out) + } +} diff --git a/src/main.rs b/src/main.rs index 9de8881..dcdc5a6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,27 +1,94 @@ mod dithering; mod escpos; -use image::{ImageError, ImageReader}; -use std::{env, fs, io, io::Write, process}; +use std::{io::Write, sync::Arc}; -fn main() { - let args: Vec = env::args().collect(); +use axum::{ + Router, + body::{Body, Bytes}, + extract::{Extension, Multipart}, + http::{Response, StatusCode, header}, + response::IntoResponse, + routing::{get, post}, +}; +use image::DynamicImage; +use tokio::sync::Mutex; - let len = args.len(); - if len < 2 || len > 2 { - println!("Please provide a path to the image."); - process::exit(1); - } +use crate::escpos::{errors::EscPosError, job::ImageOrientation, printer::Printer}; - let img = ImageReader::open(&args[1]) - .map_err(|err| ImageError::IoError(err)) - .and_then(|v| v.decode()) - .unwrap(); +#[tokio::main] +async fn main() { + let printer = Arc::new(Mutex::new(Printer::new(380))); - let mut escpos = escpos::escpos_raster(&img, Some(escpos::ImageOrientation::Largest), Some(0)); - for _ in 0..2 { - escpos.push('\n' as u8); - } + let app = Router::new() + .route("/upload", post(upload)) + .route("/print", get(print)) + .layer(Extension(printer)); - io::stdout().write(&escpos).unwrap(); + let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap(); + axum::serve(listener, app).await.unwrap(); +} + +#[axum::debug_handler] +async fn print( + Extension(printer): Extension>>, +) -> impl axum::response::IntoResponse { + let mut printer = printer.lock().await; + let bytes = match printer.export_job() { + Ok(data) => data, + Err(EscPosError::EmptyQueue) => { + println!("requested but no queue"); + return ( + StatusCode::NO_CONTENT, + [(header::CONTENT_TYPE, "application/octet-stream")], + Vec::new(), + ); + } + Err(_) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + [(header::CONTENT_TYPE, "application/octet-stream")], + Vec::new(), + ); + } + }; + + ( + StatusCode::OK, + [(header::CONTENT_TYPE, "application/octet-stream")], + bytes, + ) +} + +#[axum::debug_handler] +async fn upload( + Extension(printer): Extension>>, + mut multipart: Multipart, +) -> Result, StatusCode> { + let field = multipart + .next_field() + .await + .map_err(|_| StatusCode::BAD_REQUEST)? + .ok_or(StatusCode::BAD_REQUEST)?; + let data = field + .bytes() + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? + .to_vec(); + + let img: DynamicImage = + image::load_from_memory(&data).map_err(|_| StatusCode::UNSUPPORTED_MEDIA_TYPE)?; + + println!("{}x{}", img.width(), img.height()); + + let mut printer = printer.lock().await; + let job = printer.new_job().unwrap(); + job.write_bitmap(&img, Some(ImageOrientation::Largest), None) + .unwrap(); + job.ready = true; + + // let mut file = std::fs::File::create("/dev/usb/lp0").unwrap(); + // printer.print_job(&mut file).unwrap(); + + Ok((StatusCode::OK).into_response()) }