diff --git a/Cargo.lock b/Cargo.lock index 9a147ba..d34e88f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -88,7 +88,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn", + "syn 2.0.68", ] [[package]] @@ -204,7 +204,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn", + "syn 2.0.68", ] [[package]] @@ -244,6 +244,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + [[package]] name = "alloc-no-stdlib" version = "2.0.4" @@ -259,6 +265,33 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "allsorts" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afb67debdbc7e8b0716e7b10ce247ff6cd2811b0b478969158b16ac58995d16b" +dependencies = [ + "bitflags 1.3.2", + "bitreader", + "brotli-decompressor 2.5.1", + "byteorder", + "encoding_rs", + "flate2", + "glyph-names", + "itertools", + "lazy_static", + "libc", + "log", + "num-traits", + "ouroboros", + "rustc-hash", + "tinyvec", + "ucd-trie", + "unicode-canonical-combining-class", + "unicode-general-category", + "unicode-joining-type", +] + [[package]] name = "anstream" version = "0.6.14" @@ -308,6 +341,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "askama" version = "0.12.1" @@ -344,7 +383,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn", + "syn 2.0.68", ] [[package]] @@ -370,7 +409,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.68", ] [[package]] @@ -394,6 +433,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "barcoders" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3826fb6e98ec72c0c0db8c9a40af4932d16793021027469857e84c1b50a1e8f" + [[package]] name = "base64" version = "0.21.7" @@ -427,6 +472,15 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +[[package]] +name = "bitreader" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd859c9d97f7c468252795b35aeccc412bdbb1e90ee6969c4fa6328272eaeff" +dependencies = [ + "cfg-if", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -444,7 +498,17 @@ checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", - "brotli-decompressor", + "brotli-decompressor 4.0.1", +] + +[[package]] +name = "brotli-decompressor" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", ] [[package]] @@ -457,6 +521,17 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "bstr" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -542,6 +617,31 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + [[package]] name = "crypto-common" version = "0.1.6" @@ -573,7 +673,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.68", ] [[package]] @@ -584,7 +684,17 @@ checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" dependencies = [ "darling_core", "quote", - "syn", + "syn 2.0.68", +] + +[[package]] +name = "datamatrix" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9133f6cb9217da268046cfa9b5916b37d7c77c11707a8b7e95474a816154c42" +dependencies = [ + "arrayvec", + "flagset", ] [[package]] @@ -623,7 +733,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 2.0.68", ] [[package]] @@ -662,7 +772,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn", + "syn 2.0.68", ] [[package]] @@ -675,7 +785,7 @@ dependencies = [ "dsl_auto_type", "proc-macro2", "quote", - "syn", + "syn 2.0.68", ] [[package]] @@ -695,7 +805,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25" dependencies = [ - "syn", + "syn 2.0.68", ] [[package]] @@ -720,7 +830,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn", + "syn 2.0.68", ] [[package]] @@ -773,6 +883,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" +[[package]] +name = "flagset" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb3aa5e95cf9aabc17f060cfa0ced7b83f042390760ca53bf09df9968acaa1" + [[package]] name = "flate2" version = "1.0.30" @@ -822,7 +938,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.68", ] [[package]] @@ -879,6 +995,12 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +[[package]] +name = "glyph-names" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3531d702d6c1a3ba92a5fb55a404c7b8c476c8e7ca249951077afcbe4bc807f" + [[package]] name = "h2" version = "0.3.26" @@ -1007,6 +1129,15 @@ version = "1.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -1037,6 +1168,12 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "li7y" version = "0.0.0" @@ -1045,13 +1182,19 @@ dependencies = [ "actix-web", "askama", "askama_actix", + "barcoders", + "datamatrix", "diesel", "diesel-async", "diesel-derive-enum", "diesel_migrations", "env_logger", "log", + "mime", + "printpdf", + "rust-fontconfig", "serde", + "thiserror", "uuid", ] @@ -1067,6 +1210,12 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "local-channel" version = "0.1.5" @@ -1100,6 +1249,23 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "lopdf" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07c8e1b6184b1b32ea5f72f572ebdc40e5da1d2921fa469947ff7c480ad1f85a" +dependencies = [ + "encoding_rs", + "flate2", + "itoa", + "linked-hash-map", + "log", + "md5", + "pom", + "time", + "weezl", +] + [[package]] name = "md-5" version = "0.10.6" @@ -1110,6 +1276,12 @@ dependencies = [ "digest", ] +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + [[package]] name = "memchr" version = "2.7.4" @@ -1180,6 +1352,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mmapio" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0204e2cac68f5b2e35b7ec8cb5d906f6e58e78dad8066a30b6ee54da99bb03dd" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "nom" version = "7.1.3" @@ -1230,6 +1412,39 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "ouroboros" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2ba07320d39dfea882faa70554b4bd342a5f273ed59ba7c1c6b4c840492c954" +dependencies = [ + "aliasable", + "ouroboros_macro", + "static_assertions", +] + +[[package]] +name = "ouroboros_macro" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec4c6225c69b4ca778c0aea097321a64c421cf4577b331c61b229267edabb6f8" +dependencies = [ + "heck 0.4.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "706de7e2214113d63a8238d1910463cfce781129a6f263d13fdb09ff64355ba4" +dependencies = [ + "ttf-parser", +] + [[package]] name = "parking_lot" version = "0.12.3" @@ -1301,6 +1516,15 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "pom" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c972d8f86e943ad532d0b04e8965a749ad1d18bb981a9c7b3ae72fe7fd7744b" +dependencies = [ + "bstr", +] + [[package]] name = "postgres-protocol" version = "0.6.6" @@ -1342,6 +1566,42 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "printpdf" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c30a4cc87c3ca9a98f4970db158a7153f8d1ec8076e005751173c57836380b1d" +dependencies = [ + "js-sys", + "lopdf", + "owned_ttf_parser", + "time", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.86" @@ -1390,6 +1650,26 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -1443,12 +1723,30 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +[[package]] +name = "rust-fontconfig" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6002602a06c22f5f4e5ea2ed68854bfce071b48e1092c03874e34d0b59eb4b" +dependencies = [ + "allsorts", + "mmapio", + "rayon", + "xmlparser", +] + [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.4.0" @@ -1503,7 +1801,7 @@ checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.68", ] [[package]] @@ -1600,6 +1898,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "stringprep" version = "0.1.5" @@ -1623,6 +1927,16 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.68" @@ -1634,6 +1948,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", +] + [[package]] name = "time" version = "0.3.36" @@ -1790,12 +2124,24 @@ dependencies = [ "once_cell", ] +[[package]] +name = "ttf-parser" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49d64318d8311fc2668e48b63969f4343e0a85c4a109aa8460d6672e364b8bd1" + [[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + [[package]] name = "unicase" version = "2.7.0" @@ -1811,12 +2157,30 @@ version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +[[package]] +name = "unicode-canonical-combining-class" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6925586af9268182c711e47c0853ed84131049efaca41776d0ca97f983865c32" + +[[package]] +name = "unicode-general-category" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2281c8c1d221438e373249e065ca4989c4c36952c211ff21a0ee91c44a3869e7" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-joining-type" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f8cb47ccb8bc750808755af3071da4a10dcd147b68fc874b7ae4b12543f6f5" + [[package]] name = "unicode-normalization" version = "0.1.23" @@ -1904,7 +2268,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.68", "wasm-bindgen-shared", ] @@ -1926,7 +2290,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.68", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1947,6 +2311,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + [[package]] name = "whoami" version = "1.5.1" @@ -1958,6 +2328,28 @@ dependencies = [ "web-sys", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.48.0" @@ -2106,6 +2498,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + [[package]] name = "zerocopy" version = "0.7.34" @@ -2123,7 +2521,7 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.68", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index b20ce1e..22fe2d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,11 +14,17 @@ actix-files = "0.6.6" actix-web = "4.8.0" askama = { version = "0.12.1", features = ["with-actix-web"] } askama_actix = "0.14.0" +barcoders = { version = "2.0.0", default-features = false, features = ["std"] } +datamatrix = "0.3.1" diesel = { version = "2.2.1", features = ["uuid"] } diesel-async = { git = "https://github.com/weiznich/diesel_async.git", version = "0.4.1", features = ["postgres", "deadpool", "async-connection-wrapper"] } diesel-derive-enum = { version = "2.1.0", features = ["postgres"] } diesel_migrations = { version = "2.2.0", features = ["postgres"] } env_logger = "0.11.3" log = "0.4.21" +mime = "0.3.17" +printpdf = "0.7.0" +rust-fontconfig = "0.1.7" serde = { version = "1.0.203", features = ["serde_derive"] } +thiserror = "1.0.61" uuid = { version = "1.9.0", features = ["serde", "v4"] } diff --git a/src/api/v1/label.rs b/src/api/v1/label.rs new file mode 100644 index 0000000..4fdd977 --- /dev/null +++ b/src/api/v1/label.rs @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2024 Simon Bruder +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +use actix_web::http::header::{ContentDisposition, ContentType, DispositionParam, DispositionType}; +use actix_web::{error, get, web, HttpResponse, Responder}; +use serde::Deserialize; +use uuid::Uuid; + +use crate::label::{Label, LabelPage, LabelPreset}; + +pub fn config(cfg: &mut web::ServiceConfig) { + cfg.service(items); +} + +#[derive(Debug, Deserialize)] +struct QueryParams { + // FIXME: serde_urlencoded does not support sequences + ids: String, + preset: LabelPreset, +} + +#[get("/label/items")] +async fn items(params: web::Query) -> actix_web::Result { + let ids = params + .ids + .split(',') + .skip_while(|s| s.is_empty()) // to make the empty string parse as an empty iterator + .map(Uuid::try_parse) + .collect::, uuid::Error>>() + .map_err(error::ErrorInternalServerError)?; + + let label_config = params.preset.clone().into(); + + let label = Label { + pages: ids + .into_iter() + .map(|id| LabelPage { + id: Some(id), + short_id: None, + }) + .collect(), + config: label_config, + }; + + Ok(HttpResponse::Ok() + .insert_header(ContentType(mime::APPLICATION_PDF)) + .insert_header(ContentDisposition { + disposition: DispositionType::Inline, + parameters: vec![DispositionParam::Filename( + "li7y-item-labels.pdf".to_string(), + )], + }) + .body(label.generate().map_err(error::ErrorInternalServerError)?)) +} diff --git a/src/api/v1/mod.rs b/src/api/v1/mod.rs index 09e9d9a..581d6bf 100644 --- a/src/api/v1/mod.rs +++ b/src/api/v1/mod.rs @@ -4,9 +4,12 @@ mod item; mod item_class; +mod label; use actix_web::web; pub fn config(cfg: &mut web::ServiceConfig) { - cfg.configure(item::config).configure(item_class::config); + cfg.configure(item::config) + .configure(item_class::config) + .configure(label::config); } diff --git a/src/label/barcode.rs b/src/label/barcode.rs new file mode 100644 index 0000000..1178a49 --- /dev/null +++ b/src/label/barcode.rs @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2024 Simon Bruder +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +use barcoders::sym::code128::Code128; +use datamatrix::{DataMatrix, SymbolSize}; +use printpdf::{ColorBits, ColorSpace, Image, ImageXObject, Px}; +use uuid::Uuid; + +fn create_image(width: usize, height: usize, data: Vec) -> Image { + Image::from(ImageXObject { + width: Px(width), + height: Px(height), + color_space: ColorSpace::Greyscale, + bits_per_component: ColorBits::Bit8, // TODO: why does Bit1 not work? + image_data: data + .iter() + .map(|x| match x { + true => 0, + false => u8::MAX, + }) + .collect(), + interpolate: false, + image_filter: None, + smask: None, + clipping_bbox: None, + }) +} + +pub fn encode_data_matrix(id: Uuid) -> Result { + let code = DataMatrix::encode( + &id.to_string().bytes().collect::>(), + SymbolSize::Square22, + )?; + + let bitmap = code.bitmap(); + assert_eq!(bitmap.width(), 22); + assert_eq!(bitmap.height(), 22); + + let mut buf = vec![false; bitmap.width() * bitmap.height()]; + for (x, y) in bitmap.pixels() { + buf[x + y * bitmap.width()] = true; + } + + Ok(create_image(22, 22, buf)) +} + +pub fn encode_code128(text: String) -> Result { + let mut text = text; + // barcoders uses special characters for setting the Code128 type (A, B, C) + text.insert(0, 'À'); + + let buf: Vec = Code128::new(text)? + .encode() + .iter() + .map(|x| !matches!(x, 0)) + .collect(); + + Ok(create_image(buf.len(), 1, buf)) +} diff --git a/src/label/mod.rs b/src/label/mod.rs new file mode 100644 index 0000000..71d2cfc --- /dev/null +++ b/src/label/mod.rs @@ -0,0 +1,239 @@ +// SPDX-FileCopyrightText: 2024 Simon Bruder +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +mod barcode; +mod preset; + +use std::fs::File; +use std::sync::OnceLock; + +use pdf::{IndirectFontRef, PdfLayerReference}; +use printpdf as pdf; +use printpdf::{ImageTransform, Mm, PdfDocument, PdfDocumentReference, Pt, Px}; +use rust_fontconfig::{FcFontCache, FcPattern}; +use thiserror::Error; +use uuid::Uuid; + +use barcode::{encode_code128, encode_data_matrix}; +pub use preset::LabelPreset; + +#[derive(Error, Debug)] +pub enum Error { + #[error("could not encode data matrix")] + DataMatrixEncoding(datamatrix::data::DataEncodingError), + #[error("could not encode barcode: {0}]")] + Barcoders(#[from] barcoders::error::Error), + #[error("IO error: {0}")] + Io(#[from] std::io::Error), + #[error("PDF error: {0}")] + PrintPdf(#[from] printpdf::Error), + + #[error("data is incomplete ({0} not given in data, but required in config)")] + DataIncomplete(String), + #[error("can’t create labels without any pages")] + NoPages, +} + +// The datamatrix crate does not implement Into for its error type +impl From for Error { + fn from(value: datamatrix::data::DataEncodingError) -> Self { + Self::DataMatrixEncoding(value) + } +} + +type Result = std::result::Result; + +// this is implemented based on lazy_static’s recommendation +fn fc_cache() -> &'static FcFontCache { + static FC_CACHE: OnceLock = OnceLock::new(); + FC_CACHE.get_or_init(FcFontCache::build) +} + +fn get_font_path(name: &str) -> String { + fc_cache() + .query(&FcPattern { + name: Some(name.to_string()), + ..Default::default() + }) + .expect("Could not find font") + .path + .clone() +} + +fn mm_to_scale_factor(mm: Mm, size: Px) -> f32 { + mm / Mm::from(Pt(0.24 * (size.0 as f32))) +} + +#[derive(Debug)] +pub struct LabelConfig { + width: Mm, + height: Mm, + data_matrix: Option, + code128: Option, + id_text: Option, + short_id_text: Option, +} + +#[derive(Debug)] +pub struct DataMatrixConfig { + scale: Mm, + position: (Mm, Mm), +} + +impl DataMatrixConfig { + fn draw(&self, layer: &PdfLayerReference, id: Option) -> Result<()> { + let image = encode_data_matrix(id.ok_or(Error::DataIncomplete("UUID".to_string()))?)?; + + let size = image.image.width; + + image.add_to_layer( + layer.clone(), + ImageTransform { + translate_x: Some(self.position.0), + translate_y: Some(self.position.1), + scale_x: Some(mm_to_scale_factor(self.scale, size)), + scale_y: Some(mm_to_scale_factor(self.scale, size)), + ..Default::default() + }, + ); + + Ok(()) + } +} + +#[derive(Debug)] +pub struct Code128Config { + scale: (Mm, Mm), + position: (Mm, Mm), +} + +impl Code128Config { + fn draw>(&self, layer: &PdfLayerReference, id: Option) -> Result<()> { + let image = encode_code128( + id.ok_or(Error::DataIncomplete("short ID".to_string()))? + .into(), + )?; + + let width = image.image.width; + + image.add_to_layer( + layer.clone(), + ImageTransform { + translate_x: Some(self.position.0), + translate_y: Some(self.position.1), + scale_x: Some(mm_to_scale_factor(self.scale.0, width)), + scale_y: Some(mm_to_scale_factor(self.scale.1, Px(1))), + ..Default::default() + }, + ); + + Ok(()) + } +} + +#[derive(Debug)] +pub struct IdTextConfig { + font_size: f32, + position: (Mm, Mm), +} + +impl IdTextConfig { + fn draw>( + &self, + layer: &PdfLayerReference, + font: &IndirectFontRef, + text: Option, + ) -> Result<()> { + layer.use_text( + text.ok_or(Error::DataIncomplete("ID text".to_string()))?, + self.font_size, + self.position.0, + self.position.1, + font, + ); + Ok(()) + } +} + +#[derive(Debug)] +pub struct LabelPage { + pub id: Option, + pub short_id: Option, +} + +impl LabelPage { + fn draw( + &self, + config: &LabelConfig, + layer: &PdfLayerReference, + font: &IndirectFontRef, + ) -> Result<()> { + if let Some(cfg) = &config.data_matrix { + cfg.draw(layer, self.id)?; + } + if let Some(cfg) = &config.code128 { + cfg.draw(layer, self.short_id.as_ref())?; + } + + if let Some(cfg) = &config.id_text { + cfg.draw(layer, font, self.id)?; + } + if let Some(cfg) = &config.short_id_text { + cfg.draw(layer, font, self.short_id.as_ref())?; + } + + Ok(()) + } +} + +#[derive(Debug)] +pub struct Label { + pub pages: Vec, + pub config: LabelConfig, +} + +impl Label { + pub fn generate(&self) -> Result> { + let mut doc: Option = None; + let mut font: Option = None; + + for label in &self.pages { + let page; + let layer; + + let name: String = label + .id + .map(String::from) + .or(label.short_id.clone()) + .unwrap_or("empty".into()); + + if let Some(ref doc) = &doc { + (page, layer) = doc.add_page(self.config.width, self.config.height, name); + } else { + let (new_doc, page1, layer1) = + PdfDocument::new("li7y labels", self.config.width, self.config.height, name); + // TODO: do not hardcode font + // counter-argument: the presets rely on its spacing + // but at least it should be somehow included (or at least documented) + font = + Some(new_doc.add_external_font(File::open(get_font_path( + "IosevkaSbruder Nerd Font", + ))?)?); + + doc = Some(new_doc); + page = page1; + layer = layer1; + } + + let doc = doc.as_ref().unwrap(); + let font = font.as_ref().unwrap(); + + let layer_ref = doc.get_page(page).get_layer(layer); + + label.draw(&self.config, &layer_ref, font)? + } + + Ok(doc.ok_or(Error::NoPages)?.save_to_bytes()?) + } +} diff --git a/src/label/preset.rs b/src/label/preset.rs new file mode 100644 index 0000000..46a219d --- /dev/null +++ b/src/label/preset.rs @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: 2024 Simon Bruder +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +use printpdf::Mm; +use serde::Deserialize; + +use super::{Code128Config, DataMatrixConfig, IdTextConfig, LabelConfig}; + +#[derive(Clone, Debug, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum LabelPreset { + SeikoSlpMrlDataMatrix, + SeikoSlpMrlCode128, + SeikoSlpMrl, +} + +#[allow(clippy::from_over_into)] +impl Into for LabelPreset { + fn into(self) -> LabelConfig { + match self { + Self::SeikoSlpMrlDataMatrix => LabelConfig { + width: Mm(44.45), + height: Mm(24.13), + data_matrix: Some(DataMatrixConfig { + scale: Mm(20.0), + position: (Mm(0.0), Mm(0.0)), + }), + code128: None, + id_text: Some(IdTextConfig { + font_size: 7.0, + position: (Mm(0.0), Mm(22.0)), + }), + short_id_text: None, + }, + Self::SeikoSlpMrlCode128 => LabelConfig { + width: Mm(44.45), + height: Mm(24.13), + data_matrix: None, + code128: Some(Code128Config { + scale: (Mm(44.45), Mm(19.0)), + position: (Mm(0.0), Mm(2.5)), + }), + id_text: Some(IdTextConfig { + font_size: 7.0, + position: (Mm(0.0), Mm(22.0)), + }), + short_id_text: Some(IdTextConfig { + font_size: 7.0, + position: (Mm(0.0), Mm(0.0)), + }), + }, + Self::SeikoSlpMrl => LabelConfig { + width: Mm(44.45), + height: Mm(24.13), + data_matrix: Some(DataMatrixConfig { + scale: Mm(16.0), + position: (Mm(0.0), Mm(4.0)), + }), + code128: Some(Code128Config { + scale: (Mm(27.45), Mm(16.0)), + position: (Mm(17.0), Mm(4.0)), + }), + id_text: Some(IdTextConfig { + font_size: 7.0, + position: (Mm(0.0), Mm(22.0)), + }), + short_id_text: Some(IdTextConfig { + font_size: 7.0, + position: (Mm(17.0), Mm(0.0)), + }), + }, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 1f834a9..cec0a00 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ pub mod api; pub mod frontend; +pub mod label; pub mod manage; pub mod models; pub mod schema;