Compare commits

..

2 Commits

Author SHA1 Message Date
itsjunetime 2d7a913a27 Update changelog 2025-11-16 16:17:22 -06:00
itsjunetime cb4d956f2d implement gg & G keybinds and better help page 2025-11-16 16:07:51 -06:00
16 changed files with 575 additions and 913 deletions
-2
View File
@@ -1,2 +0,0 @@
[*.rs]
indent_style = tab
+1 -9
View File
@@ -1,18 +1,10 @@
# Unreleased # Unreleased
# v0.5.0
- Switched simd base64 crate for one that works on stable (from `vb64` to `base64_simd`) - Switched simd base64 crate for one that works on stable (from `vb64` to `base64_simd`)
- Allow boolean arguments to function as flags, without a `true` or `false` argument following the flag itself ([#109](https://github.com/itsjunetime/tdf/pull/109), thanks [@tatounee](https://github.com/tatounee)!) - Allow boolean arguments to function as flags, without a `true` or `false` argument following the flag itself
- Fix cropping issues when zooming out too much while using kitty protocol - Fix cropping issues when zooming out too much while using kitty protocol
- Added `gg` and `G` keybindings for scrolling to the top and bottom of a page, respectively, when filling the width of the screen with kitty - Added `gg` and `G` keybindings for scrolling to the top and bottom of a page, respectively, when filling the width of the screen with kitty
- Updated help page to only show kitty keybindings when you're actually using kitty - Updated help page to only show kitty keybindings when you're actually using kitty
- Map page-up and page-down keybindings to do the same thing as up-key and down-key ([#115](https://github.com/itsjunetime/tdf/pull/115), thanks [@maxdexh](https://github.com/maxdexh)!)
- Vertically center pages within the available space if they are not constrained by the height ([#116](https://github.com/itsjunetime/tdf/pull/116), thanks [@maxdexh](https://github.com/maxdexh)!)
- Fixed issue with cooked mode not being restored upon panic/error ([#118](https://github.com/itsjunetime/tdf/pull/118), thanks [@maxdexh](https://github.com/maxdexh)!)
- Implemented a debounce for file reload updates to prevent some editors from paralyzing the app due to a flurry of reloads ([#117](https://github.com/itsjunetime/tdf/pull/117), thanks [@maxdexh](https://github.com/maxdexh))
- Fixed an overflow when zooming out of horizontal pdfs ([#119](https://github.com/itsjunetime/tdf/pull/119), thanks [@maxdexh](https://github.com/maxdexh)!)
- Reworked zooming to allow for full zooming in and out and panning in both directions ([#121](https://github.com/itsjunetime/tdf/pull/121), thanks [@maxdexh](https://github.com/maxdexh)!)
# v0.4.3 # v0.4.3
Generated
+203 -240
View File
@@ -26,15 +26,6 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "aligned"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "377e4c0ba83e4431b10df45c1d4666f178ea9c552cac93e60c3a88bf32785923"
dependencies = [
"as-slice",
]
[[package]] [[package]]
name = "aligned-vec" name = "aligned-vec"
version = "0.6.4" version = "0.6.4"
@@ -91,7 +82,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.111", "syn 2.0.109",
] ]
[[package]] [[package]]
@@ -100,15 +91,6 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]]
name = "as-slice"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516"
dependencies = [
"stable_deref_trait",
]
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.89" version = "0.1.89"
@@ -117,7 +99,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.111", "syn 2.0.109",
] ]
[[package]] [[package]]
@@ -141,26 +123,6 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "av-scenechange"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394"
dependencies = [
"aligned",
"anyhow",
"arg_enum_proc_macro",
"arrayvec",
"log",
"num-rational",
"num-traits",
"pastey",
"rayon",
"thiserror 2.0.17",
"v_frame",
"y4m",
]
[[package]] [[package]]
name = "av1-grain" name = "av1-grain"
version = "0.2.5" version = "0.2.5"
@@ -186,9 +148,9 @@ dependencies = [
[[package]] [[package]]
name = "axum" name = "axum"
version = "0.8.7" version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425" checksum = "8a18ed336352031311f4e0b4dd2ff392d4fbb370777c9d18d7fc9d7359f73871"
dependencies = [ dependencies = [
"axum-core", "axum-core",
"bytes", "bytes",
@@ -279,7 +241,7 @@ dependencies = [
"regex", "regex",
"rustc-hash", "rustc-hash",
"shlex", "shlex",
"syn 2.0.111", "syn 2.0.109",
] ]
[[package]] [[package]]
@@ -297,12 +259,6 @@ version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
[[package]]
name = "bit_field"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.3.2" version = "1.3.2"
@@ -317,12 +273,9 @@ checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]] [[package]]
name = "bitstream-io" name = "bitstream-io"
version = "4.9.0" version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60d4bd9d1db2c6bdf285e223a7fa369d5ce98ec767dec949c6ca62863ce61757" checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2"
dependencies = [
"core2",
]
[[package]] [[package]]
name = "block-buffer" name = "block-buffer"
@@ -335,9 +288,9 @@ dependencies = [
[[package]] [[package]]
name = "built" name = "built"
version = "0.8.0" version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b"
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
@@ -365,9 +318,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.11.0" version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]] [[package]]
name = "cast" name = "cast"
@@ -386,9 +339,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.47" version = "1.2.44"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07" checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"jobserver", "jobserver",
@@ -405,6 +358,16 @@ dependencies = [
"nom 7.1.3", "nom 7.1.3",
] ]
[[package]]
name = "cfg-expr"
version = "0.15.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02"
dependencies = [
"smallvec",
"target-lexicon",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.4" version = "1.0.4"
@@ -468,18 +431,18 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.53" version = "4.5.51"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
] ]
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.53" version = "4.5.51"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"clap_lex", "clap_lex",
@@ -606,15 +569,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "core2"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.17" version = "0.2.17"
@@ -748,9 +702,9 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.1.7" version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [ dependencies = [
"generic-array", "generic-array",
"typenum", "typenum",
@@ -768,11 +722,11 @@ dependencies = [
[[package]] [[package]]
name = "csscolorparser" name = "csscolorparser"
version = "0.8.0" version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b9e6b904cb9ee4cb0bb93ba2b1bf3dcd2d3fc58c25557934a4b56cbd2ad9f88" checksum = "5fda6aace1fbef3aa217b27f4c8d7d071ef2a70a5ca51050b1f17d40299d3f16"
dependencies = [ dependencies = [
"num-traits", "phf",
] ]
[[package]] [[package]]
@@ -796,7 +750,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim", "strsim",
"syn 2.0.111", "syn 2.0.109",
] ]
[[package]] [[package]]
@@ -807,15 +761,9 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"quote", "quote",
"syn 2.0.111", "syn 2.0.109",
] ]
[[package]]
name = "debounce"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f2e5bc95e82bd8e9b333f4c5ff6dceab54e2e99f4d8cef2a680d417206ead34"
[[package]] [[package]]
name = "deltae" name = "deltae"
version = "0.3.2" version = "0.3.2"
@@ -849,7 +797,7 @@ dependencies = [
"convert_case", "convert_case",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.111", "syn 2.0.109",
] ]
[[package]] [[package]]
@@ -936,7 +884,7 @@ checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.111", "syn 2.0.109",
] ]
[[package]] [[package]]
@@ -974,21 +922,6 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "exr"
version = "1.74.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be"
dependencies = [
"bit_field",
"half",
"lebe",
"miniz_oxide",
"rayon-core",
"smallvec",
"zune-inflate",
]
[[package]] [[package]]
name = "fancy-regex" name = "fancy-regex"
version = "0.11.0" version = "0.11.0"
@@ -1021,9 +954,9 @@ dependencies = [
[[package]] [[package]]
name = "find-msvc-tools" name = "find-msvc-tools"
version = "0.1.5" version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
[[package]] [[package]]
name = "finl_unicode" name = "finl_unicode"
@@ -1138,7 +1071,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.111", "syn 2.0.109",
] ]
[[package]] [[package]]
@@ -1208,9 +1141,9 @@ dependencies = [
[[package]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.14.7" version = "0.14.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
dependencies = [ dependencies = [
"typenum", "typenum",
"version_check", "version_check",
@@ -1283,9 +1216,9 @@ dependencies = [
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.16.1" version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
dependencies = [ dependencies = [
"allocator-api2", "allocator-api2",
"equivalent", "equivalent",
@@ -1319,11 +1252,12 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]] [[package]]
name = "http" name = "http"
version = "1.4.0" version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
dependencies = [ dependencies = [
"bytes", "bytes",
"fnv",
"itoa", "itoa",
] ]
@@ -1370,9 +1304,9 @@ checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424"
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "1.8.1" version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e"
dependencies = [ dependencies = [
"atomic-waker", "atomic-waker",
"bytes", "bytes",
@@ -1406,9 +1340,9 @@ dependencies = [
[[package]] [[package]]
name = "hyper-util" name = "hyper-util"
version = "0.1.18" version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
@@ -1463,13 +1397,12 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]] [[package]]
name = "image" name = "image"
version = "0.25.9" version = "0.25.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a" checksum = "529feb3e6769d234375c4cf1ee2ce713682b8e76538cb13f9fc23e1400a591e7"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"byteorder-lite", "byteorder-lite",
"exr",
"moxcms", "moxcms",
"num-traits", "num-traits",
"png", "png",
@@ -1487,9 +1420,9 @@ checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8"
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.12.1" version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown", "hashbrown",
@@ -1534,7 +1467,7 @@ dependencies = [
"indoc", "indoc",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.111", "syn 2.0.109",
] ]
[[package]] [[package]]
@@ -1545,7 +1478,16 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.111", "syn 2.0.109",
]
[[package]]
name = "itertools"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
dependencies = [
"either",
] ]
[[package]] [[package]]
@@ -1605,11 +1547,10 @@ dependencies = [
[[package]] [[package]]
name = "kittage" name = "kittage"
version = "0.1.1" version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/itsjunetime/kittage.git#0ec72474513721f9960332067fd740afab3ed255"
checksum = "be6b61789e2841119b210569d4acd950d47dae805f8d3235190d28cb40677632"
dependencies = [ dependencies = [
"base64-simd", "base64 0.22.1",
"crossterm", "crossterm",
"futures-core", "futures-core",
"image", "image",
@@ -1655,12 +1596,6 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "lebe"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.177" version = "0.2.177"
@@ -1687,12 +1622,6 @@ dependencies = [
"windows-link 0.2.1", "windows-link 0.2.1",
] ]
[[package]]
name = "libm"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
[[package]] [[package]]
name = "libmimalloc-sys" name = "libmimalloc-sys"
version = "0.1.44" version = "0.1.44"
@@ -2032,7 +1961,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.111", "syn 2.0.109",
] ]
[[package]] [[package]]
@@ -2062,7 +1991,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"libm",
] ]
[[package]] [[package]]
@@ -2145,12 +2073,6 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pastey"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec"
[[package]] [[package]]
name = "pathfinder_geometry" name = "pathfinder_geometry"
version = "0.5.1" version = "0.5.1"
@@ -2178,9 +2100,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]] [[package]]
name = "pest" name = "pest"
version = "2.8.4" version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22" checksum = "989e7521a040efde50c3ab6bbadafbe15ab6dc042686926be59ac35d74607df4"
dependencies = [ dependencies = [
"memchr", "memchr",
"ucd-trie", "ucd-trie",
@@ -2188,9 +2110,9 @@ dependencies = [
[[package]] [[package]]
name = "pest_derive" name = "pest_derive"
version = "2.8.4" version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51f72981ade67b1ca6adc26ec221be9f463f2b5839c7508998daa17c23d94d7f" checksum = "187da9a3030dbafabbbfb20cb323b976dc7b7ce91fcd84f2f74d6e31d378e2de"
dependencies = [ dependencies = [
"pest", "pest",
"pest_generator", "pest_generator",
@@ -2198,22 +2120,22 @@ dependencies = [
[[package]] [[package]]
name = "pest_generator" name = "pest_generator"
version = "2.8.4" version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dee9efd8cdb50d719a80088b76f81aec7c41ed6d522ee750178f83883d271625" checksum = "49b401d98f5757ebe97a26085998d6c0eecec4995cad6ab7fc30ffdf4b052843"
dependencies = [ dependencies = [
"pest", "pest",
"pest_meta", "pest_meta",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.111", "syn 2.0.109",
] ]
[[package]] [[package]]
name = "pest_meta" name = "pest_meta"
version = "2.8.4" version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf1d70880e76bdc13ba52eafa6239ce793d85c8e43896507e43dd8984ff05b82" checksum = "72f27a2cfee9f9039c4d86faa5af122a0ac3851441a34865b8a043b46be0065a"
dependencies = [ dependencies = [
"pest", "pest",
"sha2", "sha2",
@@ -2246,7 +2168,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
dependencies = [ dependencies = [
"phf_shared", "phf_shared",
"rand 0.8.5", "rand",
] ]
[[package]] [[package]]
@@ -2259,7 +2181,7 @@ dependencies = [
"phf_shared", "phf_shared",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.111", "syn 2.0.109",
] ]
[[package]] [[package]]
@@ -2288,7 +2210,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.111", "syn 2.0.109",
] ]
[[package]] [[package]]
@@ -2396,7 +2318,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b"
dependencies = [ dependencies = [
"quote", "quote",
"syn 2.0.111", "syn 2.0.109",
] ]
[[package]] [[package]]
@@ -2419,7 +2341,7 @@ dependencies = [
"itertools 0.14.0", "itertools 0.14.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.111", "syn 2.0.109",
] ]
[[package]] [[package]]
@@ -2433,9 +2355,9 @@ dependencies = [
[[package]] [[package]]
name = "psx-shm" name = "psx-shm"
version = "0.2.1" version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d9d53299b024cf527e0cdc4e871cee6e7aa28955c1214b78799804102a2e3d" checksum = "562d6abd69b7d14272c59b72b14e3ff058232197ac0846480d58f047140ce1a2"
dependencies = [ dependencies = [
"memmap2", "memmap2",
"rustix", "rustix",
@@ -2458,9 +2380,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.42" version = "1.0.41"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@@ -2477,27 +2399,19 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [ dependencies = [
"rand_core 0.6.4", "libc",
]
[[package]]
name = "rand"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha", "rand_chacha",
"rand_core 0.9.3", "rand_core",
] ]
[[package]] [[package]]
name = "rand_chacha" name = "rand_chacha"
version = "0.9.0" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [ dependencies = [
"ppv-lite86", "ppv-lite86",
"rand_core 0.9.3", "rand_core",
] ]
[[package]] [[package]]
@@ -2505,14 +2419,8 @@ name = "rand_core"
version = "0.6.4" version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]]
name = "rand_core"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [ dependencies = [
"getrandom 0.3.4", "getrandom 0.2.16",
] ]
[[package]] [[package]]
@@ -2610,21 +2518,19 @@ dependencies = [
[[package]] [[package]]
name = "rav1e" name = "rav1e"
version = "0.8.1" version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9"
dependencies = [ dependencies = [
"aligned-vec",
"arbitrary", "arbitrary",
"arg_enum_proc_macro", "arg_enum_proc_macro",
"arrayvec", "arrayvec",
"av-scenechange",
"av1-grain", "av1-grain",
"bitstream-io", "bitstream-io",
"built", "built",
"cfg-if", "cfg-if",
"interpolate_name", "interpolate_name",
"itertools 0.14.0", "itertools 0.12.1",
"libc", "libc",
"libfuzzer-sys", "libfuzzer-sys",
"log", "log",
@@ -2633,21 +2539,23 @@ dependencies = [
"noop_proc_macro", "noop_proc_macro",
"num-derive", "num-derive",
"num-traits", "num-traits",
"once_cell",
"paste", "paste",
"profiling", "profiling",
"rand 0.9.2", "rand",
"rand_chacha", "rand_chacha",
"simd_helpers", "simd_helpers",
"thiserror 2.0.17", "system-deps",
"thiserror 1.0.69",
"v_frame", "v_frame",
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]] [[package]]
name = "ravif" name = "ravif"
version = "0.12.0" version = "0.11.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef69c1990ceef18a116855938e74793a5f7496ee907562bd0857b6ac734ab285" checksum = "5825c26fddd16ab9f515930d49028a630efec172e903483c94796cfe31893e6b"
dependencies = [ dependencies = [
"avif-serialize", "avif-serialize",
"imgref", "imgref",
@@ -2827,7 +2735,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.111", "syn 2.0.109",
] ]
[[package]] [[package]]
@@ -2843,6 +2751,15 @@ dependencies = [
"serde_core", "serde_core",
] ]
[[package]]
name = "serde_spanned"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "sha2" name = "sha2"
version = "0.10.9" version = "0.10.9"
@@ -2892,9 +2809,9 @@ dependencies = [
[[package]] [[package]]
name = "signal-hook-registry" name = "signal-hook-registry"
version = "1.4.7" version = "1.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@@ -2951,12 +2868,6 @@ dependencies = [
"lock_api", "lock_api",
] ]
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]] [[package]]
name = "static_assertions" name = "static_assertions"
version = "1.1.0" version = "1.1.0"
@@ -2987,7 +2898,7 @@ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.111", "syn 2.0.109",
] ]
[[package]] [[package]]
@@ -3003,9 +2914,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.111" version = "2.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" checksum = "2f17c7e013e88258aa9543dcbe81aca68a667a9ac37cd69c9fbc07858bfe0e2f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -3018,16 +2929,34 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
[[package]]
name = "system-deps"
version = "6.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349"
dependencies = [
"cfg-expr",
"heck",
"pkg-config",
"toml",
"version-compare",
]
[[package]]
name = "target-lexicon"
version = "0.12.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]] [[package]]
name = "tdf-viewer" name = "tdf-viewer"
version = "0.5.0" version = "0.4.3"
dependencies = [ dependencies = [
"console-subscriber", "console-subscriber",
"cpuprofiler", "cpuprofiler",
"criterion", "criterion",
"crossterm", "crossterm",
"csscolorparser 0.8.0", "csscolorparser 0.7.2",
"debounce",
"flexi_logger", "flexi_logger",
"flume", "flume",
"futures-util", "futures-util",
@@ -3135,7 +3064,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.111", "syn 2.0.109",
] ]
[[package]] [[package]]
@@ -3146,7 +3075,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.111", "syn 2.0.109",
] ]
[[package]] [[package]]
@@ -3213,7 +3142,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.111", "syn 2.0.109",
] ]
[[package]] [[package]]
@@ -3240,6 +3169,40 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "toml"
version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
[[package]] [[package]]
name = "tonic" name = "tonic"
version = "0.14.2" version = "0.14.2"
@@ -3330,7 +3293,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.111", "syn 2.0.109",
] ]
[[package]] [[package]]
@@ -3440,6 +3403,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
name = "version-compare"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.5" version = "0.9.5"
@@ -3527,7 +3496,7 @@ dependencies = [
"bumpalo", "bumpalo",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.111", "syn 2.0.109",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@@ -3726,7 +3695,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.111", "syn 2.0.109",
] ]
[[package]] [[package]]
@@ -3737,7 +3706,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.111", "syn 2.0.109",
] ]
[[package]] [[package]]
@@ -3902,6 +3871,15 @@ dependencies = [
"windows-ext", "windows-ext",
] ]
[[package]]
name = "winnow"
version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "wio" name = "wio"
version = "0.2.2" version = "0.2.2"
@@ -3932,12 +3910,6 @@ version = "0.4.0-pre.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a6d9b56f406f5754a3808524166b6e6bdfe219c0526e490cfc39ecc0582a4e6" checksum = "8a6d9b56f406f5754a3808524166b6e6bdfe219c0526e490cfc39ecc0582a4e6"
[[package]]
name = "y4m"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448"
[[package]] [[package]]
name = "yeslogic-fontconfig-sys" name = "yeslogic-fontconfig-sys"
version = "6.0.0" version = "6.0.0"
@@ -3951,44 +3923,35 @@ dependencies = [
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.8.30" version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c" checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
dependencies = [ dependencies = [
"zerocopy-derive", "zerocopy-derive",
] ]
[[package]] [[package]]
name = "zerocopy-derive" name = "zerocopy-derive"
version = "0.8.30" version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5" checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.111", "syn 2.0.109",
] ]
[[package]] [[package]]
name = "zune-core" name = "zune-core"
version = "0.5.0" version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "111f7d9820f05fd715df3144e254d6fc02ee4088b0644c0ffd0efc9e6d9d2773" checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
[[package]]
name = "zune-inflate"
version = "0.2.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
dependencies = [
"simd-adler32",
]
[[package]] [[package]]
name = "zune-jpeg" name = "zune-jpeg"
version = "0.5.5" version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc6fb7703e32e9a07fb3f757360338b3a567a5054f21b5f52a666752e333d58e" checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713"
dependencies = [ dependencies = [
"zune-core", "zune-core",
] ]
+20 -136
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tdf-viewer" name = "tdf-viewer"
version = "0.5.0" version = "0.4.3"
authors = ["June Welker <junewelker@gmail.com>"] authors = ["June Welker <junewelker@gmail.com>"]
edition = "2024" edition = "2024"
description = "A terminal viewer for PDFs" description = "A terminal viewer for PDFs"
@@ -39,11 +39,10 @@ xflags = "0.4.0-pre.2"
mimalloc = "0.1.43" mimalloc = "0.1.43"
nix = { version = "0.30.0", features = ["signal"] } nix = { version = "0.30.0", features = ["signal"] }
mupdf = { git = "https://github.com/messense/mupdf-rs.git", rev = "2e0fae910fac8048c7008211fc4d3b9f5d227a07", default-features = false, features = ["svg", "system-fonts", "img"] } mupdf = { git = "https://github.com/messense/mupdf-rs.git", rev = "2e0fae910fac8048c7008211fc4d3b9f5d227a07", default-features = false, features = ["svg", "system-fonts", "img"] }
rayon = { version = "1", default-features = false } rayon = { version = "*", default-features = false }
# kittage = { path = "../kittage/", features = ["crossterm-tokio", "image-crate", "log"] } # kittage = { path = "../kittage/", features = ["crossterm-tokio", "image-crate", "log"] }
kittage = { version = "0.1.1", features = ["crossterm-tokio", "image-crate", "log"] } kittage = { git = "https://github.com/itsjunetime/kittage.git", features = ["crossterm-tokio", "image-crate", "log"] }
memmap2 = "0" memmap2 = "*"
csscolorparser = { version = "0.8.0", default-features = false }
# logging # logging
log = "0.4.27" log = "0.4.27"
@@ -51,7 +50,7 @@ flexi_logger = "0.31"
# for tracing with tokio-console # for tracing with tokio-console
console-subscriber = { version = "0.5.0", optional = true } console-subscriber = { version = "0.5.0", optional = true }
debounce = "0.2.2" csscolorparser = { version = "0.7.0" }
[profile.production] [profile.production]
inherits = "release" inherits = "release"
@@ -76,205 +75,90 @@ name = "for_profiling"
path = "./benches/for_profiling.rs" path = "./benches/for_profiling.rs"
[lints.clippy] [lints.clippy]
alloc_instead_of_core = "warn" uninlined_format_args = "warn"
allow_attributes = "warn" redundant_closure_for_method_calls = "warn"
as_pointer_underscore = "warn" cast_lossless = "warn"
as_ptr_cast_mut = "warn" single_char_pattern = "warn"
as_underscore = "warn" manual_let_else = "warn"
ignored_unit_patterns = "warn"
range_plus_one = "warn"
unreadable_literal = "warn"
redundant_else = "warn"
assigning_clones = "warn" assigning_clones = "warn"
assertions_on_result_states = "warn"
bool_to_int_with_if = "warn" bool_to_int_with_if = "warn"
borrow_as_ptr = "warn" borrow_as_ptr = "warn"
branches_sharing_code = "warn"
cargo_common_metadata = "warn"
case_sensitive_file_extension_comparisons = "warn"
cast_lossless = "warn"
cast_ptr_alignment = "warn" cast_ptr_alignment = "warn"
cfg_not_test = "warn"
checked_conversions = "warn" checked_conversions = "warn"
clear_with_drain = "warn"
cloned_instead_of_copied = "warn"
coerce_container_to_any = "warn"
comparison_chain = "warn"
copy_iterator = "warn" copy_iterator = "warn"
create_dir = "warn"
debug_assert_with_mut_call = "warn"
decimal_literal_representation = "warn"
default_trait_access = "warn" default_trait_access = "warn"
deref_by_slicing = "warn"
doc_broken_link = "warn"
doc_link_code = "warn"
doc_link_with_quotes = "warn" doc_link_with_quotes = "warn"
elidable_lifetime_names = "warn"
empty_drop = "warn"
empty_enums = "warn" empty_enums = "warn"
empty_enum_variants_with_brackets = "warn"
empty_structs_with_brackets = "warn"
equatable_if_let = "warn"
error_impl_error = "warn"
expl_impl_clone_on_copy = "warn"
explicit_deref_methods = "warn"
explicit_into_iter_loop = "warn" explicit_into_iter_loop = "warn"
explicit_iter_loop = "warn" explicit_iter_loop = "warn"
fallible_impl_from = "warn"
filetype_is_file = "warn"
filter_map_next = "warn" filter_map_next = "warn"
flat_map_option = "warn" flat_map_option = "warn"
fn_to_numeric_cast_any = "warn"
fn_params_excessive_bools = "warn" fn_params_excessive_bools = "warn"
format_collect = "warn" from_iter_instead_of_collect = "warn"
format_push_string = "warn"
get_unwrap = "warn"
if_then_some_else_none = "warn"
ignore_without_reason = "warn"
ignored_unit_patterns = "warn"
implicit_clone = "warn" implicit_clone = "warn"
imprecise_flops = "warn"
index_refutable_slice = "warn" index_refutable_slice = "warn"
indexing_slicing = "allow" # can't warn on this cause we basically have to do indexing for some ratatui apis inefficient_to_string = "warn"
infinite_loop = "warn"
invalid_upcast_comparisons = "warn" invalid_upcast_comparisons = "warn"
ip_constant = "warn"
iter_filter_is_ok = "warn" iter_filter_is_ok = "warn"
iter_filter_is_some = "warn" iter_filter_is_some = "warn"
iter_not_returning_iterator = "warn" iter_not_returning_iterator = "warn"
iter_on_empty_collections = "warn"
iter_on_single_items = "warn"
large_digit_groups = "warn"
large_futures = "warn" large_futures = "warn"
large_include_file = "warn" large_stack_arrays = "warn"
large_stack_frames = "warn"
large_types_passed_by_value = "warn" large_types_passed_by_value = "warn"
linkedlist = "warn" linkedlist = "warn"
literal_string_with_formatting_args = "warn"
lossy_float_literal = "warn"
macro_use_imports = "warn" macro_use_imports = "warn"
manual_assert = "warn" manual_assert = "warn"
manual_instant_elapsed = "warn" manual_instant_elapsed = "warn"
manual_is_power_of_two = "warn" manual_is_power_of_two = "warn"
manual_is_variant_and = "warn" manual_is_variant_and = "warn"
manual_let_else = "warn"
manual_midpoint = "warn"
manual_ok_or = "warn" manual_ok_or = "warn"
manual_string_new = "warn"
many_single_char_names = "warn" many_single_char_names = "warn"
map_err_ignore = "warn" manual_unwrap_or = "warn"
map_unwrap_or = "warn"
map_with_unused_argument_over_ranges = "warn"
match_same_arms = "warn" match_same_arms = "warn"
match_wild_err_arm = "warn"
match_wildcard_for_single_variants = "warn" match_wildcard_for_single_variants = "warn"
maybe_infinite_iter = "warn" maybe_infinite_iter = "warn"
mem_forget = "warn"
mismatching_type_param_order = "warn" mismatching_type_param_order = "warn"
missing_assert_message = "warn"
missing_fields_in_debug = "warn" missing_fields_in_debug = "warn"
mixed_read_write_in_expression = "warn"
multiple_unsafe_ops_per_block = "warn"
must_use_candidate = "warn"
mut_mut = "warn" mut_mut = "warn"
mutex_atomic = "warn"
mutex_integer = "warn"
naive_bytecount = "warn"
needless_bitwise_bool = "warn" needless_bitwise_bool = "warn"
needless_collect = "warn"
needless_continue = "warn" needless_continue = "warn"
needless_for_each = "warn" needless_for_each = "warn"
needless_pass_by_ref_mut = "warn"
needless_pass_by_value = "warn" needless_pass_by_value = "warn"
needless_raw_string_hashes = "warn" needless_raw_string_hashes = "warn"
needless_raw_strings = "warn"
negative_feature_names = "warn"
no_effect_underscore_binding = "warn" no_effect_underscore_binding = "warn"
no_mangle_with_rust_abi = "warn" no_mangle_with_rust_abi = "warn"
non_send_fields_in_send_ty = "warn"
non_std_lazy_statics = "warn"
non_zero_suggestions = "warn"
nonstandard_macro_braces = "warn"
option_as_ref_cloned = "warn" option_as_ref_cloned = "warn"
option_option = "warn" option_option = "warn"
or_fun_call = "warn"
path_buf_push_overwrite = "warn"
pathbuf_init_then_push = "warn"
precedence_bits = "warn"
ptr_as_ptr = "warn" ptr_as_ptr = "warn"
ptr_cast_constness = "warn" ptr_cast_constness = "warn"
pub_underscore_fields = "warn"
pub_without_shorthand = "warn"
range_minus_one = "warn" range_minus_one = "warn"
range_plus_one = "warn"
rc_buffer = "warn"
rc_mutex = "warn"
read_zero_byte_vec = "warn"
redundant_clone = "warn"
redundant_closure_for_method_calls = "warn"
redundant_else = "warn"
redundant_pub_crate = "warn"
redundant_test_prefix = "warn"
ref_as_ptr = "warn" ref_as_ptr = "warn"
ref_binding_to_reference = "warn" ref_binding_to_reference = "warn"
ref_option = "warn" ref_option = "warn"
ref_option_ref = "warn" ref_option_ref = "warn"
rest_pat_in_fully_bound_structs = "warn"
return_self_not_must_use = "warn" return_self_not_must_use = "warn"
same_functions_in_if_condition = "warn" same_functions_in_if_condition = "warn"
self_named_module_files = "warn"
semicolon_if_nothing_returned = "warn"
semicolon_inside_block = "warn"
should_panic_without_expect = "warn" should_panic_without_expect = "warn"
significant_drop_in_scrutinee = "warn" # I thought this was fixed in the 2024 edition. watever similar_names = "warn"
significant_drop_tightening = "warn"
single_char_pattern = "warn"
single_option_map = "warn"
stable_sort_primitive = "warn" stable_sort_primitive = "warn"
str_split_at_newline = "warn" str_split_at_newline = "warn"
string_lit_as_bytes = "warn"
string_lit_chars_any = "warn"
string_slice = "warn"
struct_excessive_bools = "warn" struct_excessive_bools = "warn"
struct_field_names = "warn" struct_field_names = "warn"
suboptimal_flops = "warn"
suspicious_operation_groupings = "warn"
suspicious_xor_used_as_pow = "warn"
tests_outside_test_module = "warn"
trait_duplication_in_bounds = "warn"
transmute_ptr_to_ptr = "warn" transmute_ptr_to_ptr = "warn"
trivial_regex = "warn"
trivially_copy_pass_by_ref = "warn" trivially_copy_pass_by_ref = "warn"
try_err = "warn"
tuple_array_conversions = "warn"
type_repetition_in_bounds = "warn"
unchecked_time_subtraction = "warn"
undocumented_unsafe_blocks = "warn"
unicode_not_nfc = "warn" unicode_not_nfc = "warn"
uninhabited_references = "warn"
uninlined_format_args = "warn"
unnecessary_box_returns = "warn" unnecessary_box_returns = "warn"
unnecessary_debug_formatting = "warn"
unnecessary_join = "warn" unnecessary_join = "warn"
unnecessary_literal_bound = "warn" unnecessary_literal_bound = "warn"
unnecessary_safety_comment = "warn"
unnecessary_safety_doc = "warn"
unnecessary_self_imports = "warn"
unnecessary_semicolon = "warn"
unnecessary_struct_initialization = "warn"
unnecessary_wraps = "warn" unnecessary_wraps = "warn"
unnested_or_patterns = "warn" unnested_or_patterns = "warn"
unreadable_literal = "warn"
unsafe_derive_deserialize = "warn"
unused_async = "warn" unused_async = "warn"
unused_peekable = "warn"
unused_result_ok = "warn"
unused_rounding = "warn"
unused_self = "warn" unused_self = "warn"
unused_trait_names = "warn"
use_self = "warn"
used_underscore_binding = "warn" used_underscore_binding = "warn"
used_underscore_items = "warn" used_underscore_items = "warn"
useless_let_if_seq = "warn"
verbose_bit_mask = "warn"
verbose_file_reads = "warn"
volatile_composites = "warn"
while_float = "warn"
wildcard_dependencies = "warn"
wildcard_imports = "warn"
zero_sized_map_values = "warn" zero_sized_map_values = "warn"
+1 -5
View File
@@ -31,8 +31,6 @@ If it turns out that you're missing one of these, it will fail to compile and te
The binary should then be found at `./target/release/tdf`. The binary should then be found at `./target/release/tdf`.
You can also pull this in via [radicle](https://radicle.xyz) with `rad clone rad:zb11K1XGfQooopqEfwtCMyvbcyK1`
## Why in the world would you use this? ## Why in the world would you use this?
I dunno. Just for fun, mostly. I dunno. Just for fun, mostly.
@@ -41,6 +39,4 @@ I dunno. Just for fun, mostly.
Yeah, sure. Please do. Yeah, sure. Please do.
Please note, though, that: Please note, though, that all contributions will be treated as licensed under MPL-2.0.
1. No AI-generated or AI-assisted or AI-viewed or AI-anythinged code will be accepted. "AI" is a plague upon this earth and I won't be caught dead pretending it's normal.
2. All contributions will be treated as licensed under MPL-2.0 :)
+1 -3
View File
@@ -1,5 +1,3 @@
use ratatui_image::picker::ProtocolType;
mod utils; mod utils;
const BLACK: i32 = 0; const BLACK: i32 = 0;
@@ -14,5 +12,5 @@ async fn main() {
.nth(1) .nth(1)
.expect("Please enter a file to profile"); .expect("Please enter a file to profile");
utils::render_doc(file, None, BLACK, WHITE, ProtocolType::Kitty).await; utils::render_doc(file, None, BLACK, WHITE).await;
} }
+66 -82
View File
@@ -1,15 +1,17 @@
mod utils; mod utils;
use std::{hint::black_box, path::Path}; use std::{
hint::black_box,
path::Path,
time::{SystemTime, UNIX_EPOCH}
};
use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main}; use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main, profiler::Profiler};
use futures_util::StreamExt as _; use futures_util::StreamExt;
use ratatui_image::picker::ProtocolType;
use tdf::{ use tdf::{
converter::{ConvertedPage, ConverterMsg}, converter::{ConvertedPage, ConverterMsg},
renderer::{PageInfo, RenderInfo, fill_default} renderer::{PageInfo, RenderInfo, fill_default}
}; };
use tokio::runtime::Runtime;
use utils::{ use utils::{
RenderState, handle_converter_msg, handle_renderer_msg, render_doc, start_all_rendering, RenderState, handle_converter_msg, handle_renderer_msg, render_doc, start_all_rendering,
start_converting_loop, start_rendering_loop start_converting_loop, start_rendering_loop
@@ -21,103 +23,90 @@ const FILES: [&str; 3] = [
"benches/geotopo.pdf" "benches/geotopo.pdf"
]; ];
const PROTOS: [ProtocolType; 3] = [
ProtocolType::Kitty,
ProtocolType::Sixel,
ProtocolType::Iterm2
];
const BLACK: i32 = 0; const BLACK: i32 = 0;
const WHITE: i32 = i32::from_be_bytes([0, 0xff, 0xff, 0xff]); const WHITE: i32 = i32::from_be_bytes([0, 0xff, 0xff, 0xff]);
fn for_all_combos( fn render_full(c: &mut Criterion) {
name: &'static str, for file in FILES {
mut f: impl FnMut(&Runtime, BenchmarkId, &'static str, ProtocolType) c.bench_with_input(BenchmarkId::new("render_full", file), &file, |b, &file| {
) { b.to_async(tokio::runtime::Runtime::new().unwrap())
let rt = tokio::runtime::Runtime::new().unwrap(); .iter(|| render_doc(file, None, BLACK, WHITE))
for proto in PROTOS { });
for file in FILES {
f(
&rt,
BenchmarkId::new(name, format!("{file},{proto:?}")),
file,
proto
);
}
} }
} }
fn render_full(c: &mut Criterion) {
for_all_combos("render_full", |rt, id, file, proto| {
_ = c.bench_with_input(id, &file, |b, &file| {
b.to_async(rt)
.iter(|| render_doc(file, None, BLACK, WHITE, proto));
});
});
}
fn render_to_first_page(c: &mut Criterion) { fn render_to_first_page(c: &mut Criterion) {
for_all_combos("render_first_page", |rt, id, file, proto| { for file in FILES {
c.bench_with_input(id, &file, |b, &file| { c.bench_with_input(
b.to_async(rt) BenchmarkId::new("render_first_page", file),
.iter(|| render_first_page(file, BLACK, WHITE, proto)); &file,
}); |b, &file| {
}); b.to_async(tokio::runtime::Runtime::new().unwrap())
.iter(|| render_first_page(file, BLACK, WHITE))
}
);
}
} }
fn only_converting(c: &mut Criterion) { fn only_converting(c: &mut Criterion) {
for_all_combos("only_converting", |rt, id, file, proto| { for file in FILES {
let all_rendered = rt.block_on(render_all_files(file, BLACK, WHITE)); let runtime = tokio::runtime::Runtime::new().unwrap();
let all_rendered = runtime.block_on(render_all_files(file, BLACK, WHITE));
c.bench_with_input(id, &all_rendered, |b, rendered| { c.bench_with_input(
b.to_async(rt) BenchmarkId::new("only_converting", file),
.iter_with_setup(|| rendered.clone(), |f| convert_all_files(f, proto)); &(all_rendered, file),
}); |b, (rendered, _)| {
}); b.to_async(tokio::runtime::Runtime::new().unwrap())
.iter_with_setup(|| rendered.clone(), convert_all_files)
}
);
}
} }
/*
fn search_short_common(c: &mut Criterion) { fn search_short_common(c: &mut Criterion) {
for_all_combos("search_short_common", |rt, id, file, proto| { for file in FILES {
c.bench_with_input(id, &file, |b, &file| { c.bench_with_input(
b.to_async(rt) BenchmarkId::new("search_short_common", file),
.iter(|| render_doc(file, Some("an"), BLACK, WHITE, proto)) &file,
}); |b, &file| {
}); b.to_async(tokio::runtime::Runtime::new().unwrap())
.iter(|| render_doc(file, Some("an"), BLACK, WHITE))
}
);
}
} }
fn search_long_rare(c: &mut Criterion) { fn search_long_rare(c: &mut Criterion) {
for_all_combos("search_long_rare", |rt, id, file, proto| { for file in FILES {
c.bench_with_input(id, &file, |b, &file| { c.bench_with_input(
b.to_async(rt) BenchmarkId::new("search_long_rare", file),
.iter(|| render_doc(file, Some("this is long and rare"), BLACK, WHITE, proto)) &file,
}); |b, &file| {
}); b.to_async(tokio::runtime::Runtime::new().unwrap())
.iter(|| render_doc(file, Some("this is long and rare"), BLACK, WHITE))
}
);
}
} }
*/
pub async fn render_first_page( pub async fn render_first_page(path: impl AsRef<Path>, black: i32, white: i32) {
path: impl AsRef<Path>,
black: i32,
white: i32,
proto: ProtocolType
) {
let RenderState { let RenderState {
mut from_render_rx, mut from_render_rx,
mut from_converter_rx, mut from_converter_rx,
mut pages, mut pages,
to_converter_tx, mut to_converter_tx,
to_render_tx to_render_tx
} = start_all_rendering(path, black, white, proto); } = start_all_rendering(path, black, white);
// we only want to render until the first page is ready to be printed // we only want to render until the first page is ready to be printed
while pages.iter().all(Option::is_none) { while pages.iter().all(Option::is_none) {
tokio::select! { tokio::select! {
Some(renderer_msg) = from_render_rx.next() => { Some(renderer_msg) = from_render_rx.next() => {
handle_renderer_msg(renderer_msg, &mut pages, &to_converter_tx); handle_renderer_msg(renderer_msg, &mut pages, &mut to_converter_tx);
}, },
Some(converter_msg) = from_converter_rx.next() => { Some(converter_msg) = from_converter_rx.next() => {
handle_converter_msg(converter_msg, &mut pages, &to_converter_tx); handle_converter_msg(converter_msg, &mut pages, &mut to_converter_tx);
} }
} }
} }
@@ -140,7 +129,7 @@ async fn render_all_files(path: &'static str, black: i32, white: i32) -> Vec<Pag
let num = page.page_num; let num = page.page_num;
pages[num] = Some(page); pages[num] = Some(page);
} }
} };
if pages.iter().all(Option::is_some) { if pages.iter().all(Option::is_some) {
break; break;
@@ -151,9 +140,9 @@ async fn render_all_files(path: &'static str, black: i32, white: i32) -> Vec<Pag
pages.into_iter().flatten().collect() pages.into_iter().flatten().collect()
} }
async fn convert_all_files(files: Vec<PageInfo>, proto: ProtocolType) { async fn convert_all_files(files: Vec<PageInfo>) {
let num_files = files.len(); let num_files = files.len();
let (mut from_converter_rx, to_converter_tx) = start_converting_loop(proto, num_files); let (mut from_converter_rx, to_converter_tx) = start_converting_loop(num_files);
to_converter_tx to_converter_tx
.send(ConverterMsg::NumPages(num_files)) .send(ConverterMsg::NumPages(num_files))
@@ -192,12 +181,10 @@ async fn convert_all_files(files: Vec<PageInfo>, proto: ProtocolType) {
black_box(converted); black_box(converted);
} }
/*
struct CpuProfiler; struct CpuProfiler;
impl criterion::profiler::Profiler for CpuProfiler { impl Profiler for CpuProfiler {
fn start_profiling(&mut self, benchmark_id: &str, _: &std::path::Path) { fn start_profiling(&mut self, benchmark_id: &str, _: &std::path::Path) {
use std::time::{SystemTime, UNIX_EPOCH}
let file = format!( let file = format!(
"./{}-{}.profile", "./{}-{}.profile",
benchmark_id.replace('/', "-"), benchmark_id.replace('/', "-"),
@@ -213,13 +200,10 @@ impl criterion::profiler::Profiler for CpuProfiler {
cpuprofiler::PROFILER.lock().unwrap().stop().unwrap(); cpuprofiler::PROFILER.lock().unwrap().stop().unwrap();
} }
} }
*/
criterion_group!( criterion_group!(
name = benches; name = benches;
// config = Criterion::default().sample_size(40).with_profiler(CpuProfiler); config = Criterion::default().sample_size(40).with_profiler(CpuProfiler);
config = Criterion::default().sample_size(40); targets = render_full, render_to_first_page, only_converting, search_short_common, search_long_rare
// targets = render_full, render_to_first_page, only_converting, search_short_common, search_long_rare
targets = render_full, render_to_first_page, only_converting
); );
criterion_main!(benches); criterion_main!(benches);
+10 -23
View File
@@ -13,7 +13,7 @@ use tdf::{
pub fn handle_renderer_msg( pub fn handle_renderer_msg(
msg: Result<RenderInfo, RenderError>, msg: Result<RenderInfo, RenderError>,
pages: &mut Vec<Option<ConvertedPage>>, pages: &mut Vec<Option<ConvertedPage>>,
to_converter_tx: &Sender<tdf::converter::ConverterMsg> to_converter_tx: &mut Sender<tdf::converter::ConverterMsg>
) { ) {
match msg { match msg {
Ok(RenderInfo::NumPages(num)) => { Ok(RenderInfo::NumPages(num)) => {
@@ -30,7 +30,7 @@ pub fn handle_renderer_msg(
pub fn handle_converter_msg( pub fn handle_converter_msg(
msg: Result<ConvertedPage, RenderError>, msg: Result<ConvertedPage, RenderError>,
pages: &mut [Option<ConvertedPage>], pages: &mut [Option<ConvertedPage>],
to_converter_tx: &Sender<ConverterMsg> to_converter_tx: &mut Sender<ConverterMsg>
) { ) {
let page = msg.expect("Got error from converter"); let page = msg.expect("Got error from converter");
let num = page.num; let num = page.num;
@@ -106,9 +106,7 @@ pub fn start_rendering_loop(
(from_render_rx, to_render_tx) (from_render_rx, to_render_tx)
} }
#[must_use]
pub fn start_converting_loop( pub fn start_converting_loop(
proto: ProtocolType,
prerender: usize prerender: usize
) -> ( ) -> (
RecvStream<'static, Result<ConvertedPage, RenderError>>, RecvStream<'static, Result<ConvertedPage, RenderError>>,
@@ -118,7 +116,7 @@ pub fn start_converting_loop(
let (to_main_tx, from_converter_rx) = unbounded(); let (to_main_tx, from_converter_rx) = unbounded();
let mut picker = Picker::from_fontsize(FONT_SIZE); let mut picker = Picker::from_fontsize(FONT_SIZE);
picker.set_protocol_type(proto); picker.set_protocol_type(ProtocolType::Kitty);
tokio::spawn(run_conversion_loop( tokio::spawn(run_conversion_loop(
to_main_tx, to_main_tx,
@@ -133,14 +131,9 @@ pub fn start_converting_loop(
(from_converter_rx, to_converter_tx) (from_converter_rx, to_converter_tx)
} }
pub fn start_all_rendering( pub fn start_all_rendering(path: impl AsRef<Path>, black: i32, white: i32) -> RenderState {
path: impl AsRef<Path>,
black: i32,
white: i32,
proto: ProtocolType
) -> RenderState {
let (from_render_rx, to_render_tx) = start_rendering_loop(path, black, white); let (from_render_rx, to_render_tx) = start_rendering_loop(path, black, white);
let (from_converter_rx, to_converter_tx) = start_converting_loop(proto, 20); let (from_converter_rx, to_converter_tx) = start_converting_loop(20);
let pages: Vec<Option<ConvertedPage>> = Vec::new(); let pages: Vec<Option<ConvertedPage>> = Vec::new();
@@ -153,20 +146,14 @@ pub fn start_all_rendering(
} }
} }
pub async fn render_doc( pub async fn render_doc(path: impl AsRef<Path>, search_term: Option<&str>, black: i32, white: i32) {
path: impl AsRef<Path>,
search_term: Option<&str>,
black: i32,
white: i32,
proto: ProtocolType
) {
let RenderState { let RenderState {
mut from_render_rx, mut from_render_rx,
mut from_converter_rx, mut from_converter_rx,
mut pages, mut pages,
to_converter_tx, mut to_converter_tx,
to_render_tx to_render_tx
} = start_all_rendering(path, black, white, proto); } = start_all_rendering(path, black, white);
if let Some(term) = search_term { if let Some(term) = search_term {
to_render_tx to_render_tx
@@ -177,10 +164,10 @@ pub async fn render_doc(
while pages.is_empty() || pages.iter().any(Option::is_none) { while pages.is_empty() || pages.iter().any(Option::is_none) {
tokio::select! { tokio::select! {
Some(renderer_msg) = from_render_rx.next() => { Some(renderer_msg) = from_render_rx.next() => {
handle_renderer_msg(renderer_msg, &mut pages, &to_converter_tx); handle_renderer_msg(renderer_msg, &mut pages, &mut to_converter_tx);
}, },
Some(converter_msg) = from_converter_rx.next() => { Some(converter_msg) = from_converter_rx.next() => {
handle_converter_msg(converter_msg, &mut pages, &to_converter_tx); handle_converter_msg(converter_msg, &mut pages, &mut to_converter_tx);
} }
} }
} }
-14
View File
@@ -1,14 +0,0 @@
{
pkgs ? import <nixpkgs> { },
}:
pkgs.mkShell {
nativeBuildInputs = [ pkgs.pkg-config ];
buildInputs = [
pkgs.cargo
pkgs.rustc
pkgs.rustPlatform.bindgenHook
pkgs.cairo
pkgs.rust-analyzer
];
}
+12 -15
View File
@@ -1,19 +1,19 @@
use std::{ use std::{
num::NonZeroUsize, num::{NonZeroU32, NonZeroUsize},
time::{SystemTime, UNIX_EPOCH} time::{SystemTime, UNIX_EPOCH}
}; };
use flume::{Receiver, SendError, Sender, TryRecvError}; use flume::{Receiver, SendError, Sender, TryRecvError};
use futures_util::stream::StreamExt as _; use futures_util::stream::StreamExt;
use image::DynamicImage; use image::DynamicImage;
use kittage::{NumberOrId, action::NONZERO_ONE}; use kittage::NumberOrId;
use ratatui::layout::Rect; use ratatui::layout::Rect;
use ratatui_image::{ use ratatui_image::{
Resize, Resize,
picker::{Picker, ProtocolType}, picker::{Picker, ProtocolType},
protocol::Protocol protocol::Protocol
}; };
use rayon::iter::ParallelIterator as _; use rayon::iter::ParallelIterator;
use crate::{ use crate::{
renderer::{PageInfo, RenderError, fill_default}, renderer::{PageInfo, RenderError, fill_default},
@@ -37,7 +37,6 @@ pub enum ConvertedImage {
} }
impl ConvertedImage { impl ConvertedImage {
#[must_use]
pub fn w_h(&self) -> (u16, u16) { pub fn w_h(&self) -> (u16, u16) {
match self { match self {
Self::Generic(prot) => { Self::Generic(prot) => {
@@ -68,7 +67,7 @@ pub enum ConverterMsg {
pub async fn run_conversion_loop( pub async fn run_conversion_loop(
sender: Sender<Result<ConvertedPage, RenderError>>, sender: Sender<Result<ConvertedPage, RenderError>>,
receiver: Receiver<ConverterMsg>, receiver: Receiver<ConverterMsg>,
picker: Picker, mut picker: Picker,
prerender: usize, prerender: usize,
shms_work: bool shms_work: bool
) -> Result<(), SendError<Result<ConvertedPage, RenderError>>> { ) -> Result<(), SendError<Result<ConvertedPage, RenderError>>> {
@@ -78,7 +77,7 @@ pub async fn run_conversion_loop(
fn next_page( fn next_page(
images: &mut [Option<PageInfo>], images: &mut [Option<PageInfo>],
picker: &Picker, picker: &mut Picker,
page: usize, page: usize,
iteration: &mut usize, iteration: &mut usize,
prerender: usize, prerender: usize,
@@ -127,7 +126,7 @@ pub async fn run_conversion_loop(
.for_each(|(_, _, px)| px.0[2] = px.0[2].saturating_sub(u8::MAX / 2)); .for_each(|(_, _, px)| px.0[2] = px.0[2].saturating_sub(u8::MAX / 2));
}, },
_ => unreachable!() _ => unreachable!()
} };
let img_area = Rect { let img_area = Rect {
width: page_info.img_data.cell_w, width: page_info.img_data.cell_w,
@@ -141,20 +140,18 @@ pub async fn run_conversion_loop(
let rn = SystemTime::now() let rn = SystemTime::now()
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
.unwrap_or_default() .unwrap_or_default()
.as_nanos() % 1_000_000; .as_millis() % 1_000_000;
let mut img = if shms_work { let mut img = if shms_work {
kittage::image::Image::shm_from(dyn_img, &format!("/tdf_{pid}_{rn}_{page_num}")) kittage::image::Image::shm_from(dyn_img, &format!("tdf_{pid}_{rn}_{page_num}"))
.map_err(|e| { .map_err(|e| {
RenderError::Converting(format!("Couldn't write to shm: {e:?}")) RenderError::Converting(format!("Couldn't write to shm: {e}"))
})? })?
} else { } else {
kittage::image::Image::from(dyn_img) kittage::image::Image::from(dyn_img)
}; };
// if ur pdf has 4 billion pages then you deserve to suffer img.num_or_id = NumberOrId::Id(NonZeroU32::new(page_num as u32 + 1).unwrap());
img.num_or_id = NumberOrId::Id(NONZERO_ONE.saturating_add(page_num as u32));
ConvertedImage::Kitty { ConvertedImage::Kitty {
img: MaybeTransferred::NotYet(img), img: MaybeTransferred::NotYet(img),
cell_w: page_info.img_data.cell_w, cell_w: page_info.img_data.cell_w,
@@ -217,7 +214,7 @@ pub async fn run_conversion_loop(
match next_page( match next_page(
&mut images, &mut images,
&picker, &mut picker,
page, page,
&mut iteration, &mut iteration,
prerender, prerender,
+2 -2
View File
@@ -60,8 +60,8 @@ impl<W: Write> Write for DbgWriter<W> {
} }
} }
pub async fn run_action<'es>( pub async fn run_action<'image, 'data, 'es>(
action: Action<'_, '_>, action: Action<'image, 'data>,
ev_stream: &'es mut EventStream ev_stream: &'es mut EventStream
) -> Result<ImageId, TransmitError<<&'es mut EventStream as AsyncInputReader>::Error>> { ) -> Result<ImageId, TransmitError<<&'es mut EventStream as AsyncInputReader>::Error>> {
let writer = DbgWriter { let writer = DbgWriter {
-1
View File
@@ -27,7 +27,6 @@ pub struct ScaledResult {
scale_factor: f32 scale_factor: f32
} }
#[must_use]
pub fn scale_img_for_area( pub fn scale_img_for_area(
(img_width, img_height): (f32, f32), (img_width, img_height): (f32, f32),
(area_width, area_height): (f32, f32), (area_width, area_height): (f32, f32),
+53 -78
View File
@@ -5,11 +5,8 @@ use core::{
use std::{ use std::{
borrow::Cow, borrow::Cow,
ffi::OsString, ffi::OsString,
io::{BufReader, Read as _, Stdout, Write as _, stdout}, io::{BufReader, Read, Stdout, Write, stdout},
mem, path::PathBuf
path::PathBuf,
sync::{Arc, Mutex},
time::Duration
}; };
use crossterm::{ use crossterm::{
@@ -20,16 +17,15 @@ use crossterm::{
enable_raw_mode, window_size enable_raw_mode, window_size
} }
}; };
use debounce::EventDebouncer;
use flexi_logger::FileSpec; use flexi_logger::FileSpec;
use flume::{Sender, r#async::RecvStream}; use flume::{Sender, r#async::RecvStream};
use futures_util::{FutureExt as _, stream::StreamExt as _}; use futures_util::{FutureExt, stream::StreamExt};
use kittage::{ use kittage::{
action::Action, action::Action,
delete::{ClearOrDelete, DeleteConfig, WhichToDelete}, delete::{ClearOrDelete, DeleteConfig, WhichToDelete},
error::{TerminalError, TransmitError} error::{TerminalError, TransmitError}
}; };
use notify::{Event, EventKind, RecursiveMode, Watcher as _}; use notify::{Event, EventKind, RecursiveMode, Watcher};
use ratatui::{Terminal, backend::CrosstermBackend}; use ratatui::{Terminal, backend::CrosstermBackend};
use ratatui_image::{ use ratatui_image::{
FontSize, FontSize,
@@ -61,20 +57,17 @@ impl std::fmt::Debug for WrappedErr {
impl std::error::Error for WrappedErr {} impl std::error::Error for WrappedErr {}
fn reset_term() { fn reset_term() {
_ = disable_raw_mode();
_ = execute!( _ = execute!(
std::io::stdout(), std::io::stdout(),
LeaveAlternateScreen, LeaveAlternateScreen,
crossterm::cursor::Show, crossterm::cursor::Show,
crossterm::event::DisableMouseCapture crossterm::event::DisableMouseCapture
); )
} }
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), WrappedErr> { async fn main() -> Result<(), WrappedErr> {
let result = inner_main().await; inner_main().await.inspect_err(|_| reset_term())
reset_term();
result
} }
async fn inner_main() -> Result<(), WrappedErr> { async fn inner_main() -> Result<(), WrappedErr> {
@@ -87,8 +80,6 @@ async fn inner_main() -> Result<(), WrappedErr> {
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
console_subscriber::init(); console_subscriber::init();
const DEFAULT_DEBOUNCE_DELAY: Duration = Duration::from_millis(50);
let flags = xflags::parse_or_exit! { let flags = xflags::parse_or_exit! {
/// Display the pdf with the pages starting at the right hand size and moving left and /// Display the pdf with the pages starting at the right hand size and moving left and
/// adjust input keys to match /// adjust input keys to match
@@ -97,9 +88,6 @@ async fn inner_main() -> Result<(), WrappedErr> {
optional -m,--max-wide max_wide: NonZeroUsize optional -m,--max-wide max_wide: NonZeroUsize
/// Fullscreen the pdf (hide document name, page count, etc) /// Fullscreen the pdf (hide document name, page count, etc)
optional -f,--fullscreen optional -f,--fullscreen
/// The time to wait for the file to stop changing before reloading, in milliseconds.
/// Defaults to 50ms.
optional --reload-delay reload_delay: u64
/// The number of pages to prerender surrounding the currently-shown page; 0 means no /// The number of pages to prerender surrounding the currently-shown page; 0 means no
/// limit. By default, there is no limit. /// limit. By default, there is no limit.
optional -p,--prerender prerender: usize optional -p,--prerender prerender: usize
@@ -146,8 +134,10 @@ async fn inner_main() -> Result<(), WrappedErr> {
// need to keep it around throughout the lifetime of the program, but don't rly need to use it. // need to keep it around throughout the lifetime of the program, but don't rly need to use it.
// Just need to make sure it doesn't get dropped yet. // Just need to make sure it doesn't get dropped yet.
let maybe_logger = if std::env::var("RUST_LOG").is_ok() { let mut maybe_logger = None;
Some(
if std::env::var("RUST_LOG").is_ok() {
maybe_logger = Some(
flexi_logger::Logger::try_with_env() flexi_logger::Logger::try_with_env()
.map_err(|e| WrappedErr(format!("Couldn't create initial logger: {e}").into()))? .map_err(|e| WrappedErr(format!("Couldn't create initial logger: {e}").into()))?
.log_to_file(FileSpec::try_from("./debug.log").map_err(|e| { .log_to_file(FileSpec::try_from("./debug.log").map_err(|e| {
@@ -155,10 +145,8 @@ async fn inner_main() -> Result<(), WrappedErr> {
})?) })?)
.start() .start()
.map_err(|e| WrappedErr(format!("Can't start logger: {e}").into()))? .map_err(|e| WrappedErr(format!("Can't start logger: {e}").into()))?
) );
} else { }
None
};
let (watch_to_render_tx, render_rx) = flume::unbounded(); let (watch_to_render_tx, render_rx) = flume::unbounded();
let to_renderer = watch_to_render_tx.clone(); let to_renderer = watch_to_render_tx.clone();
@@ -170,11 +158,8 @@ async fn inner_main() -> Result<(), WrappedErr> {
watch_to_tui_tx, watch_to_tui_tx,
watch_to_render_tx, watch_to_render_tx,
path.file_name() path.file_name()
.ok_or_else(|| WrappedErr("Path does not have a last component??".into()))? .ok_or(WrappedErr("Path does not have a last component??".into()))?
.to_owned(), .to_owned()
flags
.reload_delay
.map_or(DEFAULT_DEBOUNCE_DELAY, Duration::from_millis)
)) ))
.map_err(|e| WrappedErr(format!("Couldn't start watching the provided file: {e}").into()))?; .map_err(|e| WrappedErr(format!("Couldn't start watching the provided file: {e}").into()))?;
@@ -345,7 +330,17 @@ async fn inner_main() -> Result<(), WrappedErr> {
) )
})?; })?;
execute!(
term.backend_mut(),
LeaveAlternateScreen,
crossterm::cursor::Show,
crossterm::event::DisableMouseCapture
)
.unwrap();
disable_raw_mode().unwrap();
drop(maybe_logger); drop(maybe_logger);
Ok(()) Ok(())
} }
@@ -466,58 +461,38 @@ async fn enter_redraw_loop(
fn on_notify_ev( fn on_notify_ev(
to_tui_tx: flume::Sender<Result<RenderInfo, RenderError>>, to_tui_tx: flume::Sender<Result<RenderInfo, RenderError>>,
to_render_tx: flume::Sender<RenderNotif>, to_render_tx: flume::Sender<RenderNotif>,
file_name: OsString, file_name: OsString
debounce_delay: Duration
) -> impl Fn(notify::Result<Event>) { ) -> impl Fn(notify::Result<Event>) {
let last_event: Mutex<Result<(), RenderError>> = Mutex::new(Ok(())); move |res| match res {
let last_event = Arc::new(last_event); // If we get an error here, and then an error sending, everything's going wrong. Just give
// up lol.
Err(e) => to_tui_tx.send(Err(RenderError::Notify(e))).unwrap(),
// TODO: Should we match EventKind::Rename and propogate that so that the other parts of the
// process know that too? Or should that be
Ok(ev) => {
// We only watch the parent directory (see the comment above `watcher.watch` in `fn
// main`) so we need to filter out events to only ones that pertain to the single file
// we care about
if !ev
.paths
.iter()
.any(|path| path.file_name().is_some_and(|f| f == file_name))
{
return;
}
let debouncer = EventDebouncer::new(debounce_delay, { match ev.kind {
let last_event = last_event.clone(); EventKind::Access(_) => (),
move |()| { EventKind::Remove(_) => to_tui_tx
let event = mem::replace(&mut *last_event.lock().unwrap(), Ok(())); .send(Err(RenderError::Converting("File was deleted".into())))
match event { .unwrap(),
// This shouldn't fail to send unless the receiver gets disconnected. If that's // This shouldn't fail to send unless the receiver gets disconnected. If that's
// happened, then like the main thread has panicked or something, so it doesn't matter // happened, then like the main thread has panicked or something, so it doesn't matter
// we don't handle the error here. // we don't handle the error here.
Ok(()) => to_render_tx.send(RenderNotif::Reload).unwrap(), EventKind::Other | EventKind::Any | EventKind::Create(_) | EventKind::Modify(_) =>
// If we get an error here, and then an error sending, everything's going wrong. Just give to_render_tx.send(RenderNotif::Reload).unwrap(),
// up lol.
Err(e) => to_tui_tx.send(Err(e)).unwrap()
} }
} }
});
move |res| {
let event = match res {
Err(e) => Err(RenderError::Notify(e)),
// TODO: Should we match EventKind::Rename and propogate that so that the other parts of the
// process know that too? Or should that be
Ok(ev) => {
// We only watch the parent directory (see the comment above `watcher.watch` in `fn
// main`) so we need to filter out events to only ones that pertain to the single file
// we care about
if !ev
.paths
.iter()
.any(|path| path.file_name().is_some_and(|f| f == file_name))
{
return;
}
match ev.kind {
EventKind::Access(_) => return,
EventKind::Remove(_) => Err(RenderError::Converting("File was deleted".into())),
EventKind::Other
| EventKind::Any
| EventKind::Create(_)
| EventKind::Modify(_) => Ok(())
}
}
};
*last_event.lock().unwrap() = event;
debouncer.put(());
} }
} }
@@ -574,18 +549,18 @@ fn get_font_size_through_stdio() -> Result<(u16, u16), WrappedErr> {
)); ));
}; };
let h = h.parse::<u16>().map_err(|e| { let h = h.parse::<u16>().map_err(|_| {
WrappedErr( WrappedErr(
format!( format!(
"Your terminal said its height is {h}, but that is not a 16-bit unsigned integer: {e}" "Your terminal said its height is {h}, but that is not a 16-bit unsigned integer"
) )
.into() .into()
) )
})?; })?;
let w = w.parse::<u16>().map_err(|e| { let w = w.parse::<u16>().map_err(|_| {
WrappedErr( WrappedErr(
format!( format!(
"Your terminal said its width is {w}, but that is not a 16-bit unsigned integer: {e}" "Your terminal said its width is {w}, but that is not a 16-bit unsigned integer"
) )
.into() .into()
) )
+6 -6
View File
@@ -78,7 +78,7 @@ pub fn fill_default<T: Default>(vec: &mut Vec<T>, size: usize) {
// We're allowing passing by value here because this is only called once, at the beginning of the // We're allowing passing by value here because this is only called once, at the beginning of the
// program, and the arguments that 'should' be passed by value (`receiver` and `size`) would // program, and the arguments that 'should' be passed by value (`receiver` and `size`) would
// probably be more performant if accessed by-value instead of through a reference. Probably. // probably be more performant if accessed by-value instead of through a reference. Probably.
#[expect(clippy::needless_pass_by_value, clippy::too_many_arguments)] #[allow(clippy::needless_pass_by_value, clippy::too_many_arguments)]
pub fn start_rendering( pub fn start_rendering(
path: &str, path: &str,
sender: Sender<Result<RenderInfo, RenderError>>, sender: Sender<Result<RenderInfo, RenderError>>,
@@ -116,7 +116,7 @@ pub fn start_rendering(
// temporarily removed to facilitate a save or something like that) // temporarily removed to facilitate a save or something like that)
while let Ok(msg) = receiver.recv() { while let Ok(msg) = receiver.recv() {
// and once that comes, just try to reload again // and once that comes, just try to reload again
if matches!(msg, RenderNotif::Reload) { if let RenderNotif::Reload = msg {
continue 'reload; continue 'reload;
} }
} }
@@ -313,7 +313,7 @@ pub fn start_rendering(
if let Err(e) = ctx.pixmap.write_to(&mut pixels, mupdf::ImageFormat::PNM) { if let Err(e) = ctx.pixmap.write_to(&mut pixels, mupdf::ImageFormat::PNM) {
sender.send(Err(RenderError::Doc(e)))?; sender.send(Err(RenderError::Doc(e)))?;
continue; continue;
} };
log::debug!("got pixmap for page {page_num} with WxH {w}x{h}"); log::debug!("got pixmap for page {page_num} with WxH {w}x{h}");
@@ -341,7 +341,7 @@ pub fn start_rendering(
Err(TryRecvError::Disconnected) => return Ok(()), Err(TryRecvError::Disconnected) => return Ok(()),
Ok(notif) => handle_notif!(notif), Ok(notif) => handle_notif!(notif),
Err(TryRecvError::Empty) => () Err(TryRecvError::Empty) => ()
} };
} }
// Now, if we have a search term, we want to look through the rest of the document past // Now, if we have a search term, we want to look through the rest of the document past
@@ -434,7 +434,7 @@ pub fn start_rendering(
return Ok(()); return Ok(());
}; };
handle_notif!(msg); handle_notif!(msg)
} }
} }
} }
@@ -565,7 +565,7 @@ struct PopOnNext<'a> {
inner: &'a mut VecDeque<usize> inner: &'a mut VecDeque<usize>
} }
impl Iterator for PopOnNext<'_> { impl<'a> Iterator for PopOnNext<'a> {
type Item = usize; type Item = usize;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
self.inner.pop_front() self.inner.pop_front()
-2
View File
@@ -7,7 +7,6 @@ pub struct Skip {
} }
impl Skip { impl Skip {
#[must_use]
pub fn new(skip: bool) -> Self { pub fn new(skip: bool) -> Self {
Self { skip } Self { skip }
} }
@@ -46,7 +45,6 @@ impl InterleavedAroundWithMax {
/// the following must hold or else this is liable to panic or produce nonsense values: /// the following must hold or else this is liable to panic or produce nonsense values:
/// - inclusive_min < exclusive_max /// - inclusive_min < exclusive_max
/// - inclusive_min <= around <= exclusive_max /// - inclusive_min <= around <= exclusive_max
#[must_use]
pub fn new(around: usize, inclusive_min: usize, exclusive_max: NonZeroUsize) -> Self { pub fn new(around: usize, inclusive_min: usize, exclusive_max: NonZeroUsize) -> Self {
Self { Self {
around, around,
+200 -295
View File
@@ -78,8 +78,8 @@ struct PageConstraints {
#[derive(Default, Debug)] #[derive(Default, Debug)]
struct Zoom { struct Zoom {
// just how much 'zoom' you have. 0 means it fills the screen (instead of fits), such // just how much 'zoom' you have. Doesn't relate to anything specific yet, except that 0 means
// that one axis is fully on-screen // it fills the screen (instead of fits)
level: i16, level: i16,
// how many terminal-cells worth of content overflow the left side of the screen (and are thus // how many terminal-cells worth of content overflow the left side of the screen (and are thus
// not displayed) // not displayed)
@@ -88,21 +88,6 @@ struct Zoom {
// not displayed) // not displayed)
cell_pan_from_top: u16 cell_pan_from_top: u16
} }
impl Zoom {
/// Returns the zoom factor, where 1 is the default and means fill-screen
fn factor(&self) -> f32 {
// TODO: Make these configurable once we have a good way to set options after startup
const ZOOM_RATE: f32 = 1.1;
const ZOOM_RATE_GRANULAR: f32 = 1.05;
if self.level > 0 {
ZOOM_RATE.powi(self.level.into())
} else {
// use a more granular zoom rate for the steps between fit-screen and fill-screen
ZOOM_RATE_GRANULAR.powi(self.level.into())
}
}
}
// This seems like a kinda weird struct because it holds two optionals but any representation // This seems like a kinda weird struct because it holds two optionals but any representation
// within it is valid; I think it's the best way to represent it // within it is valid; I think it's the best way to represent it
@@ -124,8 +109,7 @@ pub struct RenderLayout {
} }
impl Tui { impl Tui {
#[must_use] pub fn new(name: String, max_wide: Option<NonZeroUsize>, r_to_l: bool, is_kitty: bool) -> Tui {
pub fn new(name: String, max_wide: Option<NonZeroUsize>, r_to_l: bool, is_kitty: bool) -> Self {
Self { Self {
name, name,
page: 0, page: 0,
@@ -140,7 +124,6 @@ impl Tui {
} }
} }
#[must_use]
pub fn main_layout(frame: &Frame<'_>, fullscreened: bool) -> RenderLayout { pub fn main_layout(frame: &Frame<'_>, fullscreened: bool) -> RenderLayout {
if fullscreened { if fullscreened {
RenderLayout { RenderLayout {
@@ -165,129 +148,6 @@ impl Tui {
} }
} }
fn render_zoomed<'s>(
// area of the 'fit-screen' page
mut img_area: Rect,
font_size: FontSize,
zoom: &mut Zoom,
img: &'s mut MaybeTransferred,
page_num: usize,
img_cell_w: u16,
img_cell_h: u16
) -> KittyDisplay<'s> {
log::debug!("zoom is {zoom:#?}");
log::debug!("page area is {img_area:#?}");
log::debug!("img dimensions are {img_cell_w}x{img_cell_h}");
// Dimensions of the section of the image to be displayed.
// Kittage calls this the "image area to display".
// We need to shrink this or the page area in order to zoom in or out,
// respectively.
let mut img_section_w = f32::from(img_cell_w);
let mut img_section_h = f32::from(img_cell_h);
let zoom_factor = zoom.factor();
if zoom_factor >= 1.0 {
// Use a smaller section of the image. This efficively zooms into that section.
img_section_w /= zoom_factor;
img_section_h /= zoom_factor;
} else {
// Shrink the page area, such that the fill-screen conversion
// will zoom out of the image.
let initial_page_w = f32::from(img_area.width);
let initial_page_h = f32::from(img_area.height);
// how many pages the image is wide/high
let img_page_w_ratio = img_section_w / initial_page_w;
let img_page_h_ratio = img_section_h / initial_page_h;
let shrink_move_page = |dim: &mut u16, pos: &mut u16, axis_zoom_factor: f32| {
let old_dim = *dim;
// The axis zoom factor tells us what portion of the axis
// we need to show.
*dim = (f32::from(*dim) * axis_zoom_factor) as u16;
*pos += old_dim
.checked_sub(*dim)
.expect("zooming out should shrink the image")
/ 2;
};
// TODO: Detect max zoom-out in zoom levels
if img_page_w_ratio < img_page_h_ratio {
// vertical scroll / tall image. zooming out means decreasing the width of the page area
shrink_move_page(
&mut img_area.width,
&mut img_area.x,
// disallow zooming out past fit-screen
zoom_factor.max(1.0 / img_page_h_ratio)
);
} else {
// horizontal scroll / wide image. zooming out means decreasing the width of the page area
shrink_move_page(
&mut img_area.height,
&mut img_area.y,
// disallow zooming out past fit-screen
zoom_factor.max(1.0 / img_page_w_ratio)
);
}
}
log::debug!("after adjustment, page area is {img_area:#?}");
// Crop the image such that in the end, the aspect ratio of the section
// is the same as that of the page area. This effectively performs the
// conversion to fill-screen.
// Note that this only works because cell_w, cell_h is in fit-screen
// format, i.e. the cell size and the page area already share at
// least one dimension.
{
let page_area_w = f32::from(img_area.width);
let page_area_h = f32::from(img_area.height);
// how many pages the image is wide/high
// Note that this is not the same as during the
// zoom-out calculation, since it changed the page
// dimensions.
let img_page_w_ratio = img_section_w / page_area_w;
let img_page_h_ratio = img_section_h / page_area_h;
if img_page_w_ratio < img_page_h_ratio {
img_section_h = page_area_h * img_page_w_ratio;
} else {
img_section_w = page_area_w * img_page_h_ratio;
}
}
let width = (img_section_w * f32::from(font_size.0)) as u32;
let height = (img_section_h * f32::from(font_size.1)) as u32;
zoom.cell_pan_from_left = zoom
.cell_pan_from_left
.min(img_cell_w.saturating_sub(img_section_w.ceil() as u16));
zoom.cell_pan_from_top = zoom
.cell_pan_from_top
.min(img_cell_h.saturating_sub(img_section_h.ceil() as u16));
KittyDisplay::DisplayImages(vec![KittyReadyToDisplay {
img,
page_num,
pos: Position {
x: img_area.x,
y: img_area.y
},
display_loc: DisplayLocation {
x: u32::from(zoom.cell_pan_from_left) * u32::from(font_size.0),
y: u32::from(zoom.cell_pan_from_top) * u32::from(font_size.1),
width,
height,
columns: img_area.width,
rows: img_area.height,
..DisplayLocation::default()
}
}])
}
// TODO: Make a way to fill the width of the screen with one page and scroll down to view it // TODO: Make a way to fill the width of the screen with one page and scroll down to view it
#[must_use] #[must_use]
pub fn render<'s>( pub fn render<'s>(
@@ -321,120 +181,171 @@ impl Tui {
// be written and set to skip it so that ratatui doesn't spend a lot of time diffing it // be written and set to skip it so that ratatui doesn't spend a lot of time diffing it
// each re-render // each re-render
frame.render_widget(Skip::new(true), img_area); frame.render_widget(Skip::new(true), img_area);
return KittyDisplay::NoChange; KittyDisplay::NoChange
}
if let Some(ref mut zoom) = self.zoom {
// yes this is ugly and I hate it. it's due to the limitations that currently exist
// in the borrow checker. Once `-Zpolonius=next` is stabilized, we can rework this
// to look like what we expect.
// See https://github.com/rust-lang/rfcs/blob/master/text/2094-nll.md#problem-case-3-conditional-control-flow-across-functions
// You can also rewrite this to just if an `if let` and run it under
// `RUSTFLAGS="-Zpolonius=next"` and see that it works
if self.rendered[self.page]
.img
.as_ref()
.is_some_and(|c| matches!(c, ConvertedImage::Kitty { .. }))
{
let Some(ConvertedImage::Kitty {
ref mut img,
cell_w,
cell_h
}) = self.rendered[self.page].img
else {
unreachable!()
};
self.last_render = LastRender {
rect: size,
pages_shown: 1,
unused_width: 0
};
return Self::render_zoomed(
img_area, font_size, zoom, img, self.page, cell_w, cell_h
);
}
}
// here we calculate how many pages can fit in the available area.
let mut test_area_w = img_area.width;
// go through our pages, starting at the first one we want to view
let mut page_sizes = self.rendered[self.page..]
.iter_mut()
// and get this to represent a count of how many we're looking at so far to render
.enumerate()
// and only take as many as are ready to be rendered
.take_while(|(idx, page)| {
let mut take = page.img.is_some();
if let Some(max) = self.page_constraints.max_wide {
take &= *idx < max.get();
}
take
})
// and map it to their width (in cells on the terminal, not pixels)
.filter_map(|(_, page)| {
page.img.as_mut().map(|img| {
let (w, h) = img.w_h();
(w, h, img)
})
})
// and then take them as long as they won't overflow the available area.
.take_while(|(width, _, _)| match test_area_w.checked_sub(*width) {
Some(new_val) => {
test_area_w = new_val;
true
}
None => false
})
.collect::<Vec<_>>();
if self.page_constraints.r_to_l {
page_sizes.reverse();
}
if page_sizes.is_empty() {
// If none are ready to render, just show the loading thing
Self::render_loading_in(frame, img_area);
KittyDisplay::ClearImages
} else { } else {
execute!(stdout(), BeginSynchronizedUpdate).unwrap(); if let Some(ref mut zoom) = self.zoom {
// yes this is ugly and I hate it. it's due to the limitations that currently exist
// in the borrow checker. Once `-Zpolonius=next` is stabilized, we can rework this
// to look like what we expect.
// See https://github.com/rust-lang/rfcs/blob/master/text/2094-nll.md#problem-case-3-conditional-control-flow-across-functions
// You can also rewrite this to just if an `if let` and run it under
// `RUSTFLAGS="-Zpolonius=next"` and see that it works
if self.rendered[self.page]
.img
.as_ref()
.is_some_and(|c| matches!(c, ConvertedImage::Kitty { .. }))
{
let Some(ConvertedImage::Kitty {
ref mut img,
cell_w,
cell_h
}) = self.rendered[self.page].img
else {
unreachable!()
};
let total_width = page_sizes.iter().map(|(w, _, _)| w).sum::<u16>(); log::debug!("zoom is now {zoom:#?}");
log::debug!("img_area is {img_area:#?}");
log::debug!("img dimensions are {cell_w}x{cell_h}");
self.last_render.pages_shown = page_sizes.len(); let img_width = f32::from(cell_w);
let img_height = f32::from(cell_h);
let unused_width = img_area.width - total_width; let img_aspect_ratio = img_width / img_height;
self.last_render.unused_width = unused_width;
img_area.x += unused_width / 2;
if let Some(total_height) = page_sizes.iter().map(|(_, h, _)| h).max() { if zoom.level < 0 {
// This subtraction might sporadicly fail while shrinking the window. let old_width = img_area.width;
if let Some(unused_height) = img_area.height.checked_sub(*total_height) { img_area.width = img_area
img_area.y += unused_height / 2; .width
} .saturating_sub((zoom.level * 2).unsigned_abs())
} .max((f32::from(img_area.height) * img_aspect_ratio) as u16);
img_area.x += (old_width - img_area.width) / 2;
let to_display = page_sizes log::debug!("after adjustment, img_area is {img_area:#?}");
.into_iter()
.enumerate() // TODO: Find a way to detect when we've hit the maximum zoom-out and stop
.filter_map(|(idx, (width, _, img))| { // more zooming out
let maybe_img = }
Self::render_single_page(frame, img, Rect { width, ..img_area });
img_area.x += width; // Ugh I don't like this logic. I wish we could simplify it.
maybe_img.map(|(img, pos)| KittyReadyToDisplay { let img_area_width = f32::from(img_area.width);
let img_area_height = f32::from(img_area.height);
let available_to_real_width_ratio = img_area_width / img_width;
let available_to_real_height_ratio = img_area_height / img_height;
let (new_cell_width, new_cell_height) =
if available_to_real_width_ratio > available_to_real_height_ratio {
(img_width, img_area_height / available_to_real_width_ratio)
} else {
(img_area_width / available_to_real_height_ratio, img_height)
};
log::debug!("new_cell stuff is {new_cell_width}x{new_cell_height}");
let width = (new_cell_width * f32::from(font_size.0)) as u32;
let height = (new_cell_height * f32::from(font_size.1)) as u32;
self.last_render = LastRender {
rect: size,
pages_shown: 1,
unused_width: 0
};
zoom.cell_pan_from_left = zoom
.cell_pan_from_left
.min(cell_w.saturating_sub(new_cell_width as u16));
zoom.cell_pan_from_top = zoom
.cell_pan_from_top
.min(cell_h.saturating_sub(new_cell_height as u16));
return KittyDisplay::DisplayImages(vec![KittyReadyToDisplay {
img, img,
page_num: idx + self.page, page_num: self.page,
pos, pos: Position {
display_loc: DisplayLocation::default() x: img_area.x,
}) y: img_area.y
},
display_loc: DisplayLocation {
x: u32::from(zoom.cell_pan_from_left) * u32::from(font_size.0),
y: u32::from(zoom.cell_pan_from_top) * u32::from(font_size.1),
width,
height,
columns: img_area.width,
rows: img_area.height,
..DisplayLocation::default()
}
}]);
}
};
// here we calculate how many pages can fit in the available area.
let mut test_area_w = img_area.width;
// go through our pages, starting at the first one we want to view
let mut page_widths = self.rendered[self.page..]
.iter_mut()
// and get this to represent a count of how many we're looking at so far to render
.enumerate()
// and only take as many as are ready to be rendered
.take_while(|(idx, page)| {
let mut take = page.img.is_some();
if let Some(max) = self.page_constraints.max_wide {
take &= *idx < max.get();
}
take
})
// and map it to their width (in cells on the terminal, not pixels)
.filter_map(|(_, page)| page.img.as_mut().map(|img| (img.w_h().0, img)))
// and then take them as long as they won't overflow the available area.
.take_while(|(width, _)| match test_area_w.checked_sub(*width) {
Some(new_val) => {
test_area_w = new_val;
true
}
None => false
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
// we want to set this at the very end so it doesn't get set somewhere halfway through and if self.page_constraints.r_to_l {
// then the whole diffing thing messes it up page_widths.reverse();
self.last_render.rect = size; }
KittyDisplay::DisplayImages(to_display) if page_widths.is_empty() {
// If none are ready to render, just show the loading thing
Self::render_loading_in(frame, img_area);
KittyDisplay::ClearImages
} else {
execute!(stdout(), BeginSynchronizedUpdate).unwrap();
let total_width = page_widths.iter().map(|(w, _)| w).sum::<u16>();
self.last_render.pages_shown = page_widths.len();
let unused_width = img_area.width - total_width;
self.last_render.unused_width = unused_width;
img_area.x += unused_width / 2;
let to_display = page_widths
.into_iter()
.enumerate()
.filter_map(|(idx, (width, img))| {
let maybe_img =
Self::render_single_page(frame, img, Rect { width, ..img_area });
img_area.x += width;
maybe_img.map(|(img, pos)| KittyReadyToDisplay {
img,
page_num: idx + self.page,
pos,
display_loc: DisplayLocation::default()
})
})
.collect::<Vec<_>>();
// we want to set this at the very end so it doesn't get set somewhere halfway through and
// then the whole diffing thing messes it up
self.last_render.rect = size;
KittyDisplay::DisplayImages(to_display)
}
} }
} }
@@ -460,13 +371,12 @@ impl Tui {
} }
fn render_loading_in(frame: &mut Frame<'_>, area: Rect) { fn render_loading_in(frame: &mut Frame<'_>, area: Rect) {
const LOADING_STR: &str = "Loading..."; let loading_str = "Loading...";
let inner_space = let inner_space = Layout::horizontal([Constraint::Length(loading_str.len() as u16)])
Layout::horizontal([Constraint::Length(const { LOADING_STR.len() as u16 })]) .flex(Flex::Center)
.flex(Flex::Center) .split(area);
.split(area);
let loading_span = Span::styled(LOADING_STR, Style::new().fg(Color::Cyan)); let loading_span = Span::styled(loading_str, Style::new().fg(Color::Cyan));
frame.render_widget(loading_span, inner_space[0]); frame.render_widget(loading_span, inner_space[0]);
} }
@@ -495,8 +405,6 @@ impl Tui {
PageChange::Prev => self.set_page(self.page.saturating_sub(diff)) PageChange::Prev => self.set_page(self.page.saturating_sub(diff))
} }
// Yes these conversions could wrap around if you have > isize::MAX pages, but we already
// decided that you deserve to suffer if you have more than u32::MAX pages, so that's fine.
match self.page as isize - old as isize { match self.page as isize - old as isize {
0 => None, 0 => None,
_ => Some(InputAction::JumpingToPage(self.page)) _ => Some(InputAction::JumpingToPage(self.page))
@@ -630,11 +538,17 @@ impl Tui {
} }
pub fn handle_event(&mut self, ev: &Event) -> Option<InputAction> { pub fn handle_event(&mut self, ev: &Event) -> Option<InputAction> {
fn jump_to_page(page: &mut usize, rect: &mut Rect, new_page: usize) -> InputAction { fn jump_to_page(
*page = new_page; page: &mut usize,
// Make sure we re-render rect: &mut Rect,
*rect = Rect::default(); new_page: Option<usize>
InputAction::JumpingToPage(new_page) ) -> Option<InputAction> {
new_page.map(|new_page| {
*page = new_page;
// Make sure we re-render
*rect = Rect::default();
InputAction::JumpingToPage(new_page)
})
} }
match ev { match ev {
@@ -691,38 +605,30 @@ impl Tui {
'n' if self.page < self.rendered.len() - 1 => { 'n' if self.page < self.rendered.len() - 1 => {
// TODO: If we can't find one, then maybe like block until we've verified // TODO: If we can't find one, then maybe like block until we've verified
// all the pages have been checked? // all the pages have been checked?
self.rendered[(self.page + 1)..] let next_page = self.rendered[(self.page + 1)..]
.iter() .iter()
.enumerate() .enumerate()
.find_map(|(idx, p)| { .find_map(|(idx, p)| {
p.num_results p.num_results
.is_some_and(|num| num > 0) .is_some_and(|num| num > 0)
.then_some(self.page + 1 + idx) .then_some(self.page + 1 + idx)
}) });
.map(|next_page| {
jump_to_page( jump_to_page(&mut self.page, &mut self.last_render.rect, next_page)
&mut self.page, }
&mut self.last_render.rect, 'N' if self.page > 0 => {
next_page let prev_page = self.rendered[..(self.page)]
) .iter()
}) .rev()
.enumerate()
.find_map(|(idx, p)| {
p.num_results
.is_some_and(|num| num > 0)
.then_some(self.page - (idx + 1))
});
jump_to_page(&mut self.page, &mut self.last_render.rect, prev_page)
} }
'N' if self.page > 0 => self.rendered[..(self.page)]
.iter()
.rev()
.enumerate()
.find_map(|(idx, p)| {
p.num_results
.is_some_and(|num| num > 0)
.then_some(self.page - (idx + 1))
})
.map(|prev_page| {
jump_to_page(
&mut self.page,
&mut self.last_render.rect,
prev_page
)
}),
'z' if key.modifiers.contains(KeyModifiers::CONTROL) => { 'z' if key.modifiers.contains(KeyModifiers::CONTROL) => {
// [todo] better error handling here? // [todo] better error handling here?
@@ -762,21 +668,22 @@ impl Tui {
self.last_render.rect = Rect::default(); self.last_render.rect = Rect::default();
Some(InputAction::SwitchRenderZoom(f_or_f)) Some(InputAction::SwitchRenderZoom(f_or_f))
} }
'o' if self.is_kitty => 'o' if self.is_kitty => self.update_zoom(|z|
self.update_zoom(|z| z.level = z.level.saturating_add(1)), // TODO: for now, we don't let people zoom in past fill-screen
z.level = z.level.saturating_add(1).min(0)),
'O' if self.is_kitty => 'O' if self.is_kitty =>
self.update_zoom(|z| z.level = z.level.saturating_sub(1)), self.update_zoom(|z| z.level = z.level.saturating_sub(1)),
'L' if self.is_kitty => self.update_zoom(|z| { 'L' if self.is_kitty => self.update_zoom(|z| {
z.cell_pan_from_left = z.cell_pan_from_left.saturating_add(1); z.cell_pan_from_left = z.cell_pan_from_left.saturating_add(1)
}), }),
'H' if self.is_kitty => self.update_zoom(|z| { 'H' if self.is_kitty => self.update_zoom(|z| {
z.cell_pan_from_left = z.cell_pan_from_left.saturating_sub(1); z.cell_pan_from_left = z.cell_pan_from_left.saturating_sub(1)
}), }),
'J' if self.is_kitty => self.update_zoom(|z| { 'J' if self.is_kitty => self.update_zoom(|z| {
z.cell_pan_from_top = z.cell_pan_from_top.saturating_add(1); z.cell_pan_from_top = z.cell_pan_from_top.saturating_add(1)
}), }),
'K' if self.is_kitty => self.update_zoom(|z| { 'K' if self.is_kitty => self.update_zoom(|z| {
z.cell_pan_from_top = z.cell_pan_from_top.saturating_sub(1); z.cell_pan_from_top = z.cell_pan_from_top.saturating_sub(1)
}), }),
'G' if self.is_kitty => 'G' if self.is_kitty =>
self.update_zoom(|z| z.cell_pan_from_top = u16::MAX), self.update_zoom(|z| z.cell_pan_from_top = u16::MAX),
@@ -793,11 +700,9 @@ impl Tui {
None None
} }
KeyCode::Right => self.change_page(PageChange::Next, ChangeAmount::Single), KeyCode::Right => self.change_page(PageChange::Next, ChangeAmount::Single),
KeyCode::Down | KeyCode::PageDown => KeyCode::Down => self.change_page(PageChange::Next, ChangeAmount::WholeScreen),
self.change_page(PageChange::Next, ChangeAmount::WholeScreen),
KeyCode::Left => self.change_page(PageChange::Prev, ChangeAmount::Single), KeyCode::Left => self.change_page(PageChange::Prev, ChangeAmount::Single),
KeyCode::Up | KeyCode::PageUp => KeyCode::Up => self.change_page(PageChange::Prev, ChangeAmount::WholeScreen),
self.change_page(PageChange::Prev, ChangeAmount::WholeScreen),
KeyCode::Esc => match (self.showing_help_msg, &self.bottom_msg) { KeyCode::Esc => match (self.showing_help_msg, &self.bottom_msg) {
(false, BottomMessage::Help) => Some(InputAction::QuitApp), (false, BottomMessage::Help) => Some(InputAction::QuitApp),
_ => { _ => {
@@ -900,7 +805,7 @@ impl Tui {
#[expect(clippy::unnecessary_wraps)] #[expect(clippy::unnecessary_wraps)]
fn update_zoom(&mut self, f: impl FnOnce(&mut Zoom)) -> Option<InputAction> { fn update_zoom(&mut self, f: impl FnOnce(&mut Zoom)) -> Option<InputAction> {
if let Some(z) = &mut self.zoom { if let Some(z) = &mut self.zoom {
f(z); f(z)
} }
self.last_render.rect = Rect::default(); self.last_render.rect = Rect::default();
Some(InputAction::Redraw) Some(InputAction::Redraw)