diff --git a/Cargo.lock b/Cargo.lock index 3485eff..ea70ae2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,6 +29,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + [[package]] name = "atty" version = "0.2.14" @@ -46,6 +55,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "bit_field" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4" + [[package]] name = "bitflags" version = "1.3.2" @@ -75,6 +90,19 @@ name = "cc" version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cfb" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74f89d248799e3f15f91b70917f65381062a01bb8e222700ea0e5a7ff9785f9c" +dependencies = [ + "byteorder", + "uuid", +] [[package]] name = "cfg-if" @@ -228,12 +256,11 @@ checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" [[package]] name = "deflate" -version = "0.8.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" +checksum = "c86f7e25f518f4b81808a2cf1c50996a61f5c2eb394b2393bd87f2a4780a432f" dependencies = [ "adler32", - "byteorder", ] [[package]] @@ -262,6 +289,34 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "exr" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4badb9489a465cb2c555af1f00f0bfd8cecd6fc12ac11da9d5b40c5dd5f0200" +dependencies = [ + "bit_field", + "deflate", + "flume", + "half", + "inflate", + "lebe", + "smallvec", + "threadpool", +] + +[[package]] +name = "flate2" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +dependencies = [ + "cfg-if", + "crc32fast", + "libc", + "miniz_oxide 0.4.4", +] + [[package]] name = "fltk" version = "1.2.26" @@ -295,6 +350,31 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "flume" +version = "0.10.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b279436a715a9de95dcd26b151db590a71961cc06e54918b24fe0dd5b7d3fc4" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "pin-project", + "spin", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + [[package]] name = "getrandom" version = "0.1.16" @@ -313,8 +393,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.10.2+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -327,6 +409,12 @@ dependencies = [ "weezl", ] +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + [[package]] name = "hashbrown" version = "0.11.2" @@ -350,15 +438,16 @@ dependencies = [ [[package]] name = "image" -version = "0.23.14" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" +checksum = "db207d030ae38f1eb6f240d5a1c1c88ff422aa005d10f8c6c6fc5e75286ab30e" dependencies = [ "bytemuck", "byteorder", "color_quant", + "exr", "gif", - "jpeg-decoder", + "jpeg-decoder 0.2.2", "num-iter", "num-rational", "num-traits", @@ -370,17 +459,17 @@ dependencies = [ [[package]] name = "imageproc" version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7923654f3ce7cb6849d5dc9e544aaeab49c508a90b56c721b046e7234c74ab53" +source = "git+https://github.com/image-rs/imageproc#b7942657b1a370fc485507693ed4df1f8a116cb7" dependencies = [ + "approx", "conv", "image", "itertools", - "num 0.3.1", + "nalgebra", + "num", "rand", "rand_distr", "rayon", - "rulinalg", "rusttype", ] @@ -395,10 +484,28 @@ dependencies = [ ] [[package]] -name = "itertools" -version = "0.9.0" +name = "infer" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" +checksum = "20b2b533137b9cad970793453d4f921c2e91312a6d88b1085c07bc15fc51bb3b" +dependencies = [ + "cfb", +] + +[[package]] +name = "inflate" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff" +dependencies = [ + "adler32", +] + +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" dependencies = [ "either", ] @@ -409,11 +516,26 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +[[package]] +name = "jobserver" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +dependencies = [ + "libc", +] + [[package]] name = "jpeg-decoder" version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" + +[[package]] +name = "jpeg-decoder" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "105fb082d64e2100074587f59a74231f771750c664af903f1f9f76c9dedfc6f1" dependencies = [ "rayon", ] @@ -433,12 +555,36 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lebe" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7efd1d698db0759e6ef11a7cd44407407399a910c774dd804c64c032da7826ff" + [[package]] name = "libc" version = "0.2.112" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" +[[package]] +name = "libwebp-sys" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439fd1885aa28937e7edcd68d2e793cb4a22f8733460d2519fbafd2b215672bf" +dependencies = [ + "cc", +] + +[[package]] +name = "lock_api" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.14" @@ -450,9 +596,9 @@ dependencies = [ [[package]] name = "matrixmultiply" -version = "0.1.15" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcad67dcec2d58ff56f6292582377e6921afdf3bfbd533e26fb8900ae575e002" +checksum = "add85d4dd35074e6fedc608f8c8f513a3548619a9024b751949ef0e8e45a4d84" dependencies = [ "rawpointer", ] @@ -472,15 +618,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "miniz_oxide" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" -dependencies = [ - "adler32", -] - [[package]] name = "miniz_oxide" version = "0.4.4" @@ -492,21 +629,43 @@ dependencies = [ ] [[package]] -name = "num" -version = "0.1.42" +name = "miniz_oxide" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e" +checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" dependencies = [ - "num-integer", - "num-iter", + "adler", +] + +[[package]] +name = "nalgebra" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb2d0de08694bed883320212c18ee3008576bfe8c306f4c3c4a58b4876998be" +dependencies = [ + "approx", + "matrixmultiply", + "num-complex", + "num-rational", "num-traits", + "simba", + "typenum", +] + +[[package]] +name = "nanorand" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "729eb334247daa1803e0a094d0a5c55711b85571179f5ec6e53eccfdf7008958" +dependencies = [ + "getrandom 0.2.3", ] [[package]] name = "num" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7a8e9be5e039e2ff869df49155f1c06bd01ade2117ec783e56ab0932b67a8f" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" dependencies = [ "num-bigint", "num-complex", @@ -518,9 +677,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.3.3" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" dependencies = [ "autocfg", "num-integer", @@ -529,9 +688,9 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747d632c0c558b87dbabbe6a82f3b4ae03720d0646ac5b7b4dae89394be5f2c5" +checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085" dependencies = [ "num-traits", ] @@ -559,9 +718,9 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.3.2" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" dependencies = [ "autocfg", "num-bigint", @@ -613,20 +772,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" [[package]] -name = "png" -version = "0.16.8" +name = "pin-project" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" +checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "png" +version = "0.17.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc38c0ad57efb786dd57b9864e5b18bae478c00c824dc55a38bbc9da95dde3ba" dependencies = [ "bitflags", "crc32fast", "deflate", - "miniz_oxide 0.3.7", + "miniz_oxide 0.5.1", ] [[package]] name = "post_maker" -version = "0.3.0" +version = "0.4.0-alpha.1" dependencies = [ "clap", "dirs", @@ -634,6 +813,7 @@ dependencies = [ "fltk-theme", "image", "imageproc", + "infer", "lazy_static", "log", "rusttype", @@ -642,6 +822,7 @@ dependencies = [ "simplelog", "textwrap", "webbrowser", + "webp", ] [[package]] @@ -744,9 +925,9 @@ dependencies = [ [[package]] name = "rawpointer" -version = "0.1.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebac11a9d2e11f2af219b8b8d833b76b1ea0e054aa0e8d8e9e4cbde353bdf019" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" [[package]] name = "rayon" @@ -809,16 +990,6 @@ version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" -[[package]] -name = "rulinalg" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04ada202c9685e1d72a7420c578e92b358dbf807d3dfabb676a3dab9cc3bb12f" -dependencies = [ - "matrixmultiply", - "num 0.1.42", -] - [[package]] name = "rusttype" version = "0.9.2" @@ -835,6 +1006,15 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +[[package]] +name = "safe_arch" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "794821e4ccb0d9f979512f9c1973480123f9bd62a90d74ab0f9426fcf8f4a529" +dependencies = [ + "bytemuck", +] + [[package]] name = "scoped_threadpool" version = "0.1.9" @@ -878,6 +1058,19 @@ dependencies = [ "serde", ] +[[package]] +name = "simba" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13a2609e876d4f77f6ab7ff5254fc39b4f1927ba8e6db3d18be7c32534d3725e" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + [[package]] name = "simplelog" version = "0.11.2" @@ -889,12 +1082,27 @@ dependencies = [ "termcolor", ] +[[package]] +name = "smallvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" + [[package]] name = "smawk" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" +[[package]] +name = "spin" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "511254be0c5bcf062b019a6c89c01a664aa359ded62f78aa72c6fc137c0590e5" +dependencies = [ + "lock_api", +] + [[package]] name = "strsim" version = "0.10.0" @@ -933,13 +1141,22 @@ dependencies = [ ] [[package]] -name = "tiff" -version = "0.6.1" +name = "threadpool" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" dependencies = [ - "jpeg-decoder", - "miniz_oxide 0.4.4", + "num_cpus", +] + +[[package]] +name = "tiff" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0247608e998cb6ce39dfc8f4a16c50361ce71e5b52e6d24ea1227ea8ea8ee0b2" +dependencies = [ + "flate2", + "jpeg-decoder 0.1.22", "weezl", ] @@ -959,6 +1176,12 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e5d7cd7ab3e47dda6e56542f4bbf3824c15234958c6e1bd6aaa347e93499fdc" +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + [[package]] name = "unicode-linebreak" version = "0.1.2" @@ -980,6 +1203,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" + [[package]] name = "version_check" version = "0.9.4" @@ -1073,12 +1302,32 @@ dependencies = [ "winapi", ] +[[package]] +name = "webp" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf022f821f166079a407d000ab57e84de020e66ffbbf4edde999bc7d6e371cae" +dependencies = [ + "image", + "libwebp-sys", +] + [[package]] name = "weezl" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b77fdfd5a253be4ab714e4ffa3c49caf146b4de743e97510c0656cf90f1e8e" +[[package]] +name = "wide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3aba2d1dac31ac7cae82847ac5b8be822aee8f99a4e100f279605016b185c5f" +dependencies = [ + "bytemuck", + "safe_arch", +] + [[package]] name = "widestring" version = "0.4.3" diff --git a/Cargo.toml b/Cargo.toml index 5288833..6dfe953 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "post_maker" -version = "0.3.0" +version = "0.4.0-alpha.1" edition = "2021" description = "Post Maker helps you to make post for Instagram and other Social Media apps easily." authors = ["PiyushXCoder "] @@ -16,12 +16,14 @@ log = "0.4" simplelog = "0.11" fltk = "1.2" fltk-theme = "0.4" -image = "0.23" -imageproc = "0.22" +image = "0.24.1" +imageproc = { git = "https://github.com/image-rs/imageproc"} +webp = "0.2" rusttype = "0.9" serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } lazy_static = "1.4" dirs = "4.0" +infer = "0.7.0" textwrap = "0.14" webbrowser = "0.5" diff --git a/src/about_window.rs b/src/about_window.rs index 90e3d5e..0cea209 100644 --- a/src/about_window.rs +++ b/src/about_window.rs @@ -13,11 +13,10 @@ */ //! About Window -use crate::{config, globals}; +use crate::{config, globals, result_ext::ResultExt}; use fltk::{ app, button::Button, - dialog, enums::{self, Align, Event}, frame::Frame, group::Flex, @@ -152,10 +151,7 @@ impl About { // Repository Link self.repo_link.handle(|_, ev| { if ev == Event::Push { - if let Err(e) = webbrowser::open(env!("CARGO_PKG_REPOSITORY")) { - dialog::alert_default("Failed to open the link!"); - warn!("Failed to open the link!\n{:?}", e); - } + webbrowser::open(env!("CARGO_PKG_REPOSITORY")).warn_log("Failed to open the link!"); } true }); @@ -163,10 +159,7 @@ impl About { // Developer's Link self.dev_link.handle(|_, ev| { if ev == Event::Push { - if let Err(e) = webbrowser::open("https://piyushxcoder.in") { - dialog::alert_default("Failed to open the link!"); - warn!("Failed to open the link!\n{:?}", e); - } + webbrowser::open("https://piyushxcoder.in").warn_log("Failed to open the link!"); } true }); @@ -174,10 +167,7 @@ impl About { // License Link self.license_link.handle(|_, ev| { if ev == Event::Push { - if let Err(e) = webbrowser::open("https://www.gnu.org/licenses/gpl-3.0.html") { - dialog::alert_default("Failed to open the link!"); - warn!("Failed to open the link!\n{:?}", e); - } + webbrowser::open("https://www.gnu.org/licenses/gpl-3.0.html").warn_log("Failed to open the link!"); } true }); diff --git a/src/config.rs b/src/config.rs index e03260a..fc837b0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,7 +13,7 @@ */ //! load, save configuration and parse cli args -use crate::{config_picker::ConfigPicker, globals}; +use crate::{config_picker::ConfigPicker, globals, result_ext::ResultExt, utils::ImageType}; use clap::{ArgEnum, Parser}; use fltk::dialog; use fltk_theme::ThemeType; @@ -128,7 +128,7 @@ pub(crate) struct ConfigFile { pub(crate) tag2_position_ratio: f64, pub(crate) image_ratio: (f64, f64), pub(crate) color_layer: [u8; 4], - pub(crate) image_format: String, + pub(crate) image_format: ImageType, } impl Default for ConfigFile { @@ -151,7 +151,7 @@ impl Default for ConfigFile { tag2_position_ratio: 0.95, image_ratio: (4.0, 5.0), color_layer: [20, 22, 25, 197], - image_format: "png".to_owned(), + image_format: ImageType::Png, } } } @@ -207,28 +207,10 @@ pub(crate) fn get_configs() -> Option> { /// Save configs pub(crate) fn save_configs(configs: HashMap) { - if let Err(e) = std::fs::write(&*CONFIG_FILE, serde_json::to_string(&configs).unwrap()) { - dialog::alert_default("Can't write config!"); - error!("Can't write config!\n{:?}", e); - panic!("Can't write config!\n{:?}", e); - } + std::fs::write(&*CONFIG_FILE, serde_json::to_string(&configs).unwrap()).expect_log("Can't write config!"); } pub(crate) fn log_file() -> File { - // match File::open(&*LOG_FILE) { - // Ok(mut file) => { - // if is_file_30_days_old(&file) { - // match File::create(&*LOG_FILE) { - // Ok(f) => file = f, - // Err(e) => { - // dialog::alert_default("Can't open log file!"); - // panic!("{:?}", e); - // } - // } - // } - // file - // } - // Err(_) => match File::create(&*LOG_FILE) { Ok(f) => f, Err(e) => { @@ -236,18 +218,4 @@ pub(crate) fn log_file() -> File { panic!("{:?}", e); } } - // } } - -// pub(crate) fn is_file_30_days_old(file: &File) -> bool { -// if let Ok(meta) = file.metadata() { -// if let Ok(time) = meta.created() { -// if let Ok(dur) = SystemTime::now().duration_since(time) { -// if dur > Duration::from_secs(60 * 60 * 24 * 30) { -// return true; -// } -// } -// } -// } -// false -// } diff --git a/src/config_window.rs b/src/config_window.rs index 7e2f689..8460693 100644 --- a/src/config_window.rs +++ b/src/config_window.rs @@ -33,7 +33,7 @@ use fltk::{ use crate::{ config::{self, ConfigFile}, - globals, utils, + globals, utils::{self, ImageType}, }; pub(crate) struct ConfigWindow { @@ -505,12 +505,12 @@ impl ConfigWindow { self.translucent_layer_alpha .set_value(config.color_layer[3] as f64); - match config.image_format.as_str() { - "png" => { + match config.image_format { + utils::ImageType::Png => { self.png_format.set_value(true); self.jpeg_format.set_value(false) } - "jpg" => { + utils::ImageType::Jpeg => { self.png_format.set_value(false); self.jpeg_format.set_value(true) } @@ -1063,7 +1063,7 @@ impl ConfigWindow { .borrow_mut() .get_mut(&browse.selected_text().unwrap()) { - conf.image_format = "png".to_owned(); + conf.image_format = ImageType::Png; } }); @@ -1075,7 +1075,7 @@ impl ConfigWindow { .borrow_mut() .get_mut(&browse.selected_text().unwrap()) { - conf.image_format = "jpg".to_owned(); + conf.image_format = ImageType::Jpeg; } }); diff --git a/src/crop_window.rs b/src/crop_window.rs index 7670ec4..f54481f 100644 --- a/src/crop_window.rs +++ b/src/crop_window.rs @@ -15,7 +15,7 @@ //! Window to change Crop properties of image use crate::{ globals, - utils::{self, Coord, ImageContainer, ImageProperties}, + utils::{self, Coord, ImageContainer, ImageProperties, ImageInfo}, }; use fltk::{ app, button::Button, draw, enums::Event, frame::Frame, group::Flex, image::SvgImage, @@ -24,7 +24,6 @@ use fltk::{ use image::GenericImageView; use std::{ cell::RefCell, - path::PathBuf, rc::Rc, sync::{Arc, RwLock}, }; @@ -108,7 +107,7 @@ impl CropWindow { /// Call it to show window to crop image pub(crate) fn load_to_crop( &mut self, - path: &PathBuf, + path: &ImageInfo, crop_pos: Option<(f64, f64)>, ) -> Option<(f64, f64)> { let mut container = diff --git a/src/draw_thread.rs b/src/draw_thread.rs index ed187c0..e88ac22 100644 --- a/src/draw_thread.rs +++ b/src/draw_thread.rs @@ -14,7 +14,8 @@ //! Thread to manage drawing in background -use crate::utils::{ImageContainer, ImageProperties}; +use crate::result_ext::ResultExt; +use crate::utils::{ImageContainer, ImageProperties, ImageInfo}; use crate::{ main_window::{MainWindow, Page}, utils::{self, ImagePropertiesFile}, @@ -33,7 +34,7 @@ use fltk::{ }; use std::{ fs, - path::{Path, PathBuf}, + path::{Path}, sync::{mpsc, Arc, RwLock}, }; @@ -85,7 +86,7 @@ pub(crate) fn spawn_image_thread( let mut status = main_win.status.clone(); let mut count = main_win.count.clone(); let mut dimension = main_win.dimension.clone(); - let images_path = Arc::clone(&main_win.images_path); + let images_path = Arc::clone(&main_win.images_list); let mut _container: Option = None; std::thread::spawn(move || loop { @@ -179,13 +180,13 @@ pub(crate) fn spawn_image_thread( if let Some(cont) = &mut _container { status.set_label("Cloning..."); win.deactivate(); - if let Some(path) = cont.clone_img() { + if let Some(image_info) = cont.clone_img() { let idx = file_choice.value(); let mut imgs = images_path.write().unwrap(); - imgs.insert(idx as usize, path.clone()); + imgs.insert(idx as usize, image_info.clone()); file_choice.insert( idx, - path.file_name().unwrap().to_str().unwrap(), + image_info.path.file_name().unwrap().to_str().unwrap(), enums::Shortcut::None, menu::MenuFlag::Normal, |a| a.do_callback(), @@ -225,7 +226,7 @@ pub(crate) fn spawn_image_thread( /// Loads the selected image in file_choice to ImageContainer to edit fn load_image( file_choice: &mut menu::Choice, - images_path: Arc>>, + images_list: Arc>>, crop: Option<(f64, f64)>, quote: &mut MultilineInput, subquote: &mut MultilineInput, @@ -251,19 +252,19 @@ fn load_image( properties: Arc>, container: &mut Option, ) { - let imgs = images_path.read().unwrap(); + let imgs = images_list.read().unwrap(); if imgs.len() == 0 { *container = None; flush_buffer(app_sender, container); return; } count.set_label(&format!("[{}/{}]", file_choice.value() + 1, imgs.len())); - let file = imgs.get(file_choice.value() as usize).unwrap(); + let image_info = imgs.get(file_choice.value() as usize).unwrap(); - *container = Some(ImageContainer::new(&file, Arc::clone(&properties))); + *container = Some(ImageContainer::new(&image_info, Arc::clone(&properties))); if let Some(cont) = container { - let file = Path::new(&file); + let file = Path::new(&image_info.path); let properties_file = file.with_extension("prop"); let read = fs::read_to_string(&properties_file).unwrap_or("{}".to_owned()); @@ -273,10 +274,7 @@ fn load_image( warn!("Config is corrupt\n{:?}", e); match dialog::choice_default("Config is corrupt, fix??", "yes", "no", "") { 1 => { - if let Err(e) = fs::remove_file(&properties_file) { - dialog::alert_default("Failed to delete image properties file!"); - warn!("Failed to delete image properties file!\n{:?}", e); - } + fs::remove_file(&properties_file).warn_log("Failed to delete image properties file!"); ImagePropertiesFile::default() } _ => return, diff --git a/src/main.rs b/src/main.rs index a1b481e..b98796b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,6 +26,7 @@ mod draw_thread; mod globals; mod main_window; mod utils; +mod result_ext; use fltk::{ app::{channel, App}, diff --git a/src/main_window.rs b/src/main_window.rs index c47ea85..13eea25 100644 --- a/src/main_window.rs +++ b/src/main_window.rs @@ -16,7 +16,10 @@ use crate::about_window::About; use crate::crop_window::CropWindow; use crate::draw_thread::*; +use crate::result_ext::ResultExt; use crate::utils; +use crate::utils::ImageInfo; +use crate::utils::ImageType; use crate::utils::ImageProperties; use crate::{config_window::ConfigWindow, globals}; use fltk::{ @@ -38,7 +41,7 @@ use fltk::{ }; use std::path::PathBuf; use std::sync::{mpsc, RwLock}; -use std::{ffi::OsStr, fs, sync::Arc}; +use std::{ fs, sync::Arc}; pub(crate) struct MainWindow { pub(crate) win: Window, @@ -81,7 +84,7 @@ pub(crate) struct MainWindow { pub(crate) count: Frame, pub(crate) dimension: Frame, pub(crate) page: Page, - pub(crate) images_path: Arc>>, + pub(crate) images_list: Arc>>, pub(crate) draw_buff: Arc>>>, pub(crate) properties: Arc>, pub(crate) sender: mpsc::Sender, @@ -380,7 +383,7 @@ impl MainWindow { status, count, dimension, - images_path: Arc::new(RwLock::new(vec![])), + images_list: Arc::new(RwLock::new(vec![])), draw_buff, properties: Arc::clone(&properties), page: Page { @@ -401,7 +404,7 @@ impl MainWindow { fn menu(&mut self) { let mut file_choice = self.file_choice.clone(); let sender = self.sender.clone(); - let imgs = Arc::clone(&self.images_path); + let imgs = Arc::clone(&self.images_list); self.menubar.add( "&File/Open Folder...\t", Shortcut::Ctrl | 'o', @@ -418,8 +421,7 @@ impl MainWindow { let expost_dir = path.join("export"); if !expost_dir.exists() { if let Err(e) = fs::create_dir(expost_dir) { - fltk::dialog::alert_default("Failed to create export folder!"); - warn!("Failed to create export folder!\n{:?}", e); + Result::<(), _>::Err(e).warn_log("Failed to create export folder!"); return; } } @@ -492,10 +494,10 @@ impl MainWindow { // Resest Button for FileChoice let mut file_choice = self.file_choice.clone(); let sender = self.sender.clone(); - let imgs = Arc::clone(&self.images_path); + let imgs = Arc::clone(&self.images_list); self.reset_file_choice.set_callback(move |_| { let path = match imgs.read().unwrap().first() { - Some(path) => path.parent().unwrap().to_path_buf(), + Some(image_info) => image_info.path.parent().unwrap().to_path_buf(), None => return, }; load_dir(&path, Arc::clone(&imgs), &mut file_choice, &sender); @@ -662,8 +664,8 @@ impl MainWindow { let sender = self.sender.clone(); self.crop_btn.set_callback(move |_| { let mut prop = properties.write().unwrap(); - if let Some(path) = &prop.path { - if let Some((x, y)) = crop_win.load_to_crop(path, prop.crop_position) { + if let Some(image_info) = &prop.image_info { + if let Some((x, y)) = crop_win.load_to_crop(&image_info, prop.crop_position) { sender.send(DrawMessage::ChangeCrop((x, y))).unwrap(); prop.is_saved = false; } @@ -1004,7 +1006,7 @@ impl MainWindow { /// Load all iamges in a directory fn load_dir( path: &PathBuf, - imgs: Arc>>, + imgs: Arc>>, file_choice: &mut menu::Choice, sender: &mpsc::Sender, ) { @@ -1018,11 +1020,15 @@ fn load_dir( *imgs_b = vec![]; for file in files { let path = file.path(); - if path.extension() == Some(OsStr::new("jpg")) - || path.extension() == Some(OsStr::new("png")) - { - text = format!("{}|{}", text, path.file_name().unwrap().to_str().unwrap()); - imgs_b.push(path); + if let Ok(Some(ty)) = infer::get_from_path(&path) { + let mime = ty.mime_type(); + match ImageType::from_mime(mime) { + ImageType::None => (), + _ => { + text = format!("{}|{}", text, path.file_name().unwrap().to_str().unwrap()); + imgs_b.push(ImageInfo { path, image_type: ImageType::from_mime(mime) }); + } + } } } if text.len() == 0 { diff --git a/src/result_ext.rs b/src/result_ext.rs new file mode 100644 index 0000000..dadb603 --- /dev/null +++ b/src/result_ext.rs @@ -0,0 +1,39 @@ +use std::fmt::Debug; +use std::panic::Location; +use fltk::dialog; + +pub trait ResultExt { + fn expect_log(self, msg: &str) -> T; + fn error_log(&self, msg: &str); + fn warn_log(&self, msg: &str); +} + +impl ResultExt for Result { + #[track_caller] + fn expect_log(self, msg: &str) -> T { + match self { + Ok(v) => v, + Err(e) => { + dialog::alert_default(msg); + error!("{}\n{:?}\n{}", msg, e, Location::caller()); + std::process::exit(1); + } + } + } + + #[track_caller] + fn error_log(&self, msg: &str) { + if let Err(e) = self { + dialog::alert_default(msg); + error!("{}\n{:?}\n{}", msg, e, Location::caller()); + } + } + + #[track_caller] + fn warn_log(&self, msg: &str) { + if let Err(e) = self { + dialog::alert_default(msg); + warn!("{}\n{:?}", msg, e); + } + } +} \ No newline at end of file diff --git a/src/utils.rs b/src/utils.rs index cf5358b..483be13 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -15,13 +15,14 @@ use std::{ fs::{self, File}, path::{Path, PathBuf}, - sync::{Arc, RwLock}, + sync::{Arc, RwLock}, io::Read }; -use fltk::{button::Button, dialog, enums, prelude::*}; +use fltk::{button::Button, enums, prelude::*}; use image::{DynamicImage, GenericImageView, ImageBuffer, ImageEncoder}; use serde::{Deserialize, Serialize}; +use crate::result_ext::ResultExt; use crate::globals; /// helps cast tupels to f64 @@ -63,6 +64,40 @@ impl Into<(i32, i32)> for Coord { } } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub(crate) struct ImageInfo { + pub(crate) path: PathBuf, + pub(crate) image_type: ImageType +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub(crate) enum ImageType { + Jpeg, + Png, + Webp, + None +} + +impl ImageType { + pub(crate) fn from_mime(v: &str) -> Self { + match v { + "image/jpeg" | "image/jpg" => Self::Jpeg, + "image/png" => Self::Png, + "image/webp" => Self::Webp, + _ => Self::None + } + } + + pub(crate) fn as_extension(&self) -> String { + match self { + Self::Jpeg => "jpg", + Self::Png => "png", + Self::Webp => "webp", + Self::None => "none" + }.to_owned() + } +} + /// Contains Image and its buffer(edited image) #[derive(Debug, Clone)] pub(crate) struct ImageContainer { @@ -72,22 +107,13 @@ pub(crate) struct ImageContainer { } impl ImageContainer { - pub(crate) fn new(path: &PathBuf, properties: Arc>) -> Self { - let img = match image::open(path) { - Ok(i) => i, - Err(e) => { - dialog::alert_default("Failed to open image!"); - error!("Failed to open image\n{:?}", e); - panic!("Failed to open image\n{:?}", e); - } - }; - - let img = DynamicImage::ImageRgb8(img.into_rgb8()); + pub(crate) fn new(image_info: &ImageInfo, properties: Arc>) -> Self { + let img = load_image(&image_info); let (width, height): (f64, f64) = Coord::from(img.dimensions()).into(); let config = globals::CONFIG.read().unwrap(); let mut prop = properties.write().unwrap(); - prop.path = Some(path.to_owned()); + prop.image_info = Some(image_info.to_owned()); prop.original_dimension = (width, height); prop.quote_position = height * config.quote_position_ratio; prop.subquote_position = height * config.subquote_position_ratio; @@ -185,16 +211,16 @@ impl ImageContainer { pub(crate) fn save(&self) { let prop = self.properties.read().unwrap(); - let path_original = match &prop.path { - Some(p) => Path::new(p), + let (path_original, mut original_image) = match &prop.image_info { + Some(p) => (Path::new(&p.path), load_image(p)), None => return, }; let path_properties = path_original.with_extension("prop"); let config = globals::CONFIG.read().unwrap(); - let export_format = config.image_format.as_str(); + let export_format = &config.image_format; let export = path_original.parent().unwrap().join("export").join( path_original - .with_extension(export_format) + .with_extension(export_format.as_extension()) .file_name() .unwrap() .to_str() @@ -202,20 +228,13 @@ impl ImageContainer { ); let mut prop = prop.clone(); - prop.path = None; - if let Err(e) = fs::write( - &path_properties, - serde_json::to_string(&ImagePropertiesFile::from(&prop)).unwrap(), - ) { - dialog::alert_default("Failed to save properties!"); - warn!("Failed to save properties!\n{:?}", e); - } + prop.image_info = None; + fs::write(&path_properties, serde_json::to_string(&ImagePropertiesFile::from(&prop)).unwrap()).warn_log("Failed to save properties!"); - let mut img = image::open(&path_original).unwrap(); - let (width, height): (f64, f64) = Coord::from(img.dimensions()).into(); + let (width, height): (f64, f64) = Coord::from(original_image.dimensions()).into(); let (crop_x, crop_y) = prop.crop_position.unwrap(); let (crop_width, crop_height) = croped_ratio(width, height); - let mut img = img.crop( + let mut img = original_image.crop( crop_x as u32, crop_y as u32, crop_width as u32, @@ -241,75 +260,62 @@ impl ImageContainer { let mut output = match File::create(&export) { Ok(a) => a, Err(e) => { - dialog::alert_default("Failed to write to disk!"); - warn!("Failed to write to disk!\n{:?}", e); + Result::<(), _>::Err(e).warn_log("Failed to write to disk!"); return; } }; match export_format { - "png" => { + ImageType::Png => { let encoder = image::codecs::png::PngEncoder::new_with_quality( &mut output, - image::png::CompressionType::Default, - image::png::FilterType::NoFilter, + image::codecs::png::CompressionType::Best, + image::codecs::png::FilterType::Sub ); let (w, h) = img.dimensions(); - if let Err(e) = - encoder.write_image(&img.into_rgba8(), w, h, image::ColorType::Rgba8) - { - dialog::alert_default("Failed to export Image!"); - warn!("Failed to export Image!\n{:?}", e); - } + encoder.write_image(&img.into_rgba8(), w, h, image::ColorType::Rgba8).warn_log("Failed to export Image!"); } - "jpg" => { + ImageType::Jpeg => { let mut encoder = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut output, 100); encoder.set_pixel_density(image::codecs::jpeg::PixelDensity::dpi(300)); - if let Err(e) = encoder.encode_image(&img) { - dialog::alert_default("Failed to export Image!"); - warn!("Failed to export Image!\n{:?}", e); - } + encoder.encode_image(&img).warn_log("Failed to export Image!"); } _ => (), } } - pub(crate) fn clone_img(&self) -> Option { + pub(crate) fn clone_img(&self) -> Option { let prop = self.properties.read().unwrap(); - match &prop.path { - Some(path) => { - let name = path.file_stem().unwrap().to_string_lossy(); - let ext = path.extension().unwrap().to_string_lossy(); + match &prop.image_info { + Some(image_info) => { + let name = image_info.path.file_stem().unwrap().to_string_lossy(); + let ext = image_info.path.extension().unwrap().to_string_lossy(); let mut i = 1; - let mut new_path = path.clone(); + let mut new_path = image_info.path.clone(); while new_path.exists() { let new_file = format!("{}{}.{}", name, "-copy".repeat(i), ext); - new_path = path.with_file_name(&new_file); + new_path = image_info.path.with_file_name(&new_file); i += 1; } - let path_properties = path.with_extension("prop"); + let path_properties = image_info.path.with_extension("prop"); let path_properties_new = new_path.with_extension("prop"); - if path.exists() { - if let Err(e) = fs::copy(path, &new_path) { - dialog::alert_default("Failed to clone image!"); - warn!("Failed to clone image!\n{:?}", e); - return None; - } + if image_info.path.exists() { + fs::copy(&image_info.path, &new_path).warn_log("Failed to clone image!"); } if path_properties.exists() { - if let Err(e) = fs::copy(path_properties, &path_properties_new) { - dialog::alert_default("Failed to clone image properties!"); - warn!("Failed to clone image properties!\n{:?}", e); - } + fs::copy(path_properties, &path_properties_new).warn_log("Failed to clone image properties!"); } - Some(new_path) + Some(ImageInfo { + path: new_path, + image_type: image_info.image_type.clone() + }) } None => None, } @@ -318,10 +324,10 @@ impl ImageContainer { pub(crate) fn delete(&self) { let prop = self.properties.read().unwrap(); let config = globals::CONFIG.read().unwrap(); - let export_format = config.image_format.as_str(); + let export_format = config.image_format.as_extension(); - let path_original = match &prop.path { - Some(p) => Path::new(p), + let path_original = match &prop.image_info { + Some(p) => Path::new(&p.path), None => return, }; let path_properties = path_original.with_extension("prop"); @@ -335,24 +341,15 @@ impl ImageContainer { ); if path_original.exists() { - if let Err(e) = fs::remove_file(path_original) { - dialog::alert_default("Failed to delete image!"); - warn!("Failed to delete image!\n{:?}", e); - } + fs::remove_file(path_original).warn_log("Failed to delete image!"); } if path_properties.exists() { - if let Err(e) = fs::remove_file(path_properties) { - dialog::alert_default("Failed to delete image properties!"); - warn!("Failed to delete image properties!\n{:?}", e); - } + fs::remove_file(path_properties).warn_log("Failed to delete image properties!"); } if export.exists() { - if let Err(e) = fs::remove_file(export) { - dialog::alert_default("Failed to delete exported image!"); - warn!("Failed to delete exported image!\n{:?}", e); - } + fs::remove_file(export).warn_log("Failed to delete exported image!"); } } } @@ -415,7 +412,7 @@ impl From<&ImageProperties> for ImagePropertiesFile { /// Properties of loaded image #[derive(Serialize, Deserialize, Debug, Clone)] pub(crate) struct ImageProperties { - pub(crate) path: Option, + pub(crate) image_info: Option, pub(crate) dimension: (f64, f64), pub(crate) original_dimension: (f64, f64), pub(crate) crop_position: Option<(f64, f64)>, @@ -436,7 +433,7 @@ pub(crate) struct ImageProperties { impl Default for ImageProperties { fn default() -> Self { Self { - path: None, + image_info: None, dimension: (0.0, 0.0), original_dimension: (0.0, 0.0), crop_position: None, @@ -480,6 +477,33 @@ impl ImageProperties { } } +/// Load image as Dynamic Image +fn load_image(image_info: &ImageInfo) -> DynamicImage { + let img = match image_info.image_type { + ImageType::Webp => { + let mut f = File::open(&image_info.path).expect_log("Failed to open image!"); + let mut buf = vec![]; + f.read_to_end(&mut buf).unwrap(); + let a = webp::Decoder::new(&buf).decode().unwrap(); + a.to_image() + } + ImageType::Jpeg => { + let dec = image::codecs::jpeg::JpegDecoder::new(File::open(&image_info.path).expect_log("Failed to open image!")).expect_log("Failed to decode image!"); + DynamicImage::from_decoder(dec).expect_log("Failed to open image!") + } + ImageType::Png => { + let dec = image::codecs::png::PngDecoder::new(File::open(&image_info.path).expect_log("Failed to open image!")).expect_log("Failed to decode image!"); + DynamicImage::from_decoder(dec).expect_log("Failed to open image!") + } + ImageType::None => { + Result::<(), _>::Err("Failed to open image!").expect_log(""); + std::process::exit(1); + } + }; + + DynamicImage::ImageRgb8(img.into_rgb8()) +} + /// Draw text and stuffs on image fn draw_layer_and_text( tmp: &mut DynamicImage, @@ -552,8 +576,8 @@ fn draw_layer_and_text( imageproc::drawing::draw_text_mut( tmp, image::Rgba([255, 255, 255, 255]), - (width * 0.99 - text_width) as u32, - ((tag_position * height) / original_height + index as f64 * (text_height * 1.2)) as u32, + (width * 0.99 - text_width) as i32, + ((tag_position * height) / original_height + index as f64 * (text_height * 1.2)) as i32, rusttype::Scale::uniform(size as f32), &globals::FONT_TAG, line, @@ -578,8 +602,8 @@ pub(crate) fn draw_multiline_mid_string( imageproc::drawing::draw_text_mut( tmp, image::Rgba([255, 255, 255, 255]), - ((width - text_width) / 2.0) as u32, - ((position * height) / original_height + index as f64 * (text_height * 1.15)) as u32, + ((width - text_width) / 2.0) as i32, + ((position * height) / original_height + index as f64 * (text_height * 1.15)) as i32, rusttype::Scale::uniform(size as f32), font, line,