mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
Use resvg as library
This commit is contained in:
parent
00e54f7dbe
commit
c59aa5e4c9
11 changed files with 741 additions and 75 deletions
295
Cargo.lock
generated
295
Cargo.lock
generated
|
|
@ -145,6 +145,12 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.6"
|
||||
|
|
@ -496,6 +502,15 @@ version = "0.8.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "core_maths"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30"
|
||||
dependencies = [
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.2"
|
||||
|
|
@ -618,6 +633,12 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "data-url"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a"
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.4.0"
|
||||
|
|
@ -817,6 +838,12 @@ dependencies = [
|
|||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "float-cmp"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
|
|
@ -829,6 +856,29 @@ version = "0.1.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "fontconfig-parser"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1fcfcd44ca6e90c921fee9fa665d530b21ef1327a4c1a6c5250ea44b776ada7"
|
||||
dependencies = [
|
||||
"roxmltree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fontdb"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905"
|
||||
dependencies = [
|
||||
"fontconfig-parser",
|
||||
"log",
|
||||
"memmap2",
|
||||
"slotmap",
|
||||
"tinyvec",
|
||||
"ttf-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fsevent-sys"
|
||||
version = "4.1.0"
|
||||
|
|
@ -1077,6 +1127,12 @@ dependencies = [
|
|||
"quick-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imagesize"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285"
|
||||
|
||||
[[package]]
|
||||
name = "imgref"
|
||||
version = "1.11.0"
|
||||
|
|
@ -1214,6 +1270,16 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kurbo"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89234b2cc610a7dd927ebde6b41dd1a5d4214cffaef4cf1fb2195d592f92518f"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
|
|
@ -1242,6 +1308,12 @@ dependencies = [
|
|||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.3"
|
||||
|
|
@ -1364,6 +1436,15 @@ version = "2.7.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
|
|
@ -1704,6 +1785,12 @@ version = "2.3.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "pico-args"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
|
|
@ -2026,11 +2113,37 @@ version = "0.8.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "resvg"
|
||||
version = "0.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd43d1c474e9dadf09a8fdf22d713ba668b499b5117b9b9079500224e26b5b29"
|
||||
dependencies = [
|
||||
"gif",
|
||||
"image-webp",
|
||||
"log",
|
||||
"pico-args",
|
||||
"rgb",
|
||||
"svgtypes",
|
||||
"tiny-skia",
|
||||
"usvg",
|
||||
"zune-jpeg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rgb"
|
||||
version = "0.8.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roxmltree"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
|
|
@ -2072,6 +2185,24 @@ version = "1.0.20"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
|
||||
|
||||
[[package]]
|
||||
name = "rustybuzz"
|
||||
version = "0.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bytemuck",
|
||||
"core_maths",
|
||||
"log",
|
||||
"smallvec",
|
||||
"ttf-parser",
|
||||
"unicode-bidi-mirroring",
|
||||
"unicode-ccc",
|
||||
"unicode-properties",
|
||||
"unicode-script",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
|
|
@ -2228,6 +2359,21 @@ version = "0.1.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
||||
|
||||
[[package]]
|
||||
name = "simplecss"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c"
|
||||
dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
|
|
@ -2237,6 +2383,15 @@ dependencies = [
|
|||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slotmap"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.0"
|
||||
|
|
@ -2265,6 +2420,15 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "strict-num"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
|
||||
dependencies = [
|
||||
"float-cmp",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
|
|
@ -2293,6 +2457,16 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "svgtypes"
|
||||
version = "0.15.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc"
|
||||
dependencies = [
|
||||
"kurbo",
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.100"
|
||||
|
|
@ -2458,6 +2632,47 @@ dependencies = [
|
|||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-skia"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"arrayvec",
|
||||
"bytemuck",
|
||||
"cfg-if",
|
||||
"log",
|
||||
"png",
|
||||
"tiny-skia-path",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-skia-path"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"bytemuck",
|
||||
"strict-num",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.44.2"
|
||||
|
|
@ -2636,6 +2851,15 @@ dependencies = [
|
|||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ttf-parser"
|
||||
version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
|
||||
dependencies = [
|
||||
"core_maths",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "twox-hash"
|
||||
version = "2.1.0"
|
||||
|
|
@ -2657,6 +2881,24 @@ version = "1.18.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi-mirroring"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ccc"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
|
|
@ -2669,6 +2911,18 @@ version = "0.1.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-properties"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-script"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.12.0"
|
||||
|
|
@ -2686,6 +2940,12 @@ dependencies = [
|
|||
"unicode-width 0.1.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-vo"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.14"
|
||||
|
|
@ -2704,6 +2964,33 @@ version = "2.1.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||
|
||||
[[package]]
|
||||
name = "usvg"
|
||||
version = "0.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ac8e0e3e4696253dc06167990b3fe9a2668ab66270adf949a464db4088cb354"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"data-url",
|
||||
"flate2",
|
||||
"fontdb",
|
||||
"imagesize",
|
||||
"kurbo",
|
||||
"log",
|
||||
"pico-args",
|
||||
"roxmltree",
|
||||
"rustybuzz",
|
||||
"simplecss",
|
||||
"siphasher",
|
||||
"strict-num",
|
||||
"svgtypes",
|
||||
"tiny-skia-path",
|
||||
"unicode-bidi",
|
||||
"unicode-script",
|
||||
"unicode-vo",
|
||||
"xmlwriter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
|
|
@ -3138,6 +3425,12 @@ dependencies = [
|
|||
"bitflags 2.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xmlwriter"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
|
||||
|
||||
[[package]]
|
||||
name = "yazi-adapter"
|
||||
version = "25.4.8"
|
||||
|
|
@ -3149,6 +3442,7 @@ dependencies = [
|
|||
"crossterm",
|
||||
"image",
|
||||
"ratatui",
|
||||
"resvg",
|
||||
"scopeguard",
|
||||
"tokio",
|
||||
"tracing",
|
||||
|
|
@ -3391,6 +3685,7 @@ dependencies = [
|
|||
"parking_lot",
|
||||
"paste",
|
||||
"ratatui",
|
||||
"resvg",
|
||||
"serde_json",
|
||||
"syntect",
|
||||
"tokio",
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ parking_lot = "0.12.3"
|
|||
paste = "1.0.15"
|
||||
ratatui = { version = "0.29.0", features = [ "unstable-rendered-line-info" ] }
|
||||
regex = "1.11.1"
|
||||
resvg = { version = "0.45.0" }
|
||||
scopeguard = "1.2.0"
|
||||
serde = { version = "1.0.219", features = [ "derive" ] }
|
||||
serde_json = "1.0.140"
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ color_quant = "1.1.0"
|
|||
crossterm = { workspace = true }
|
||||
image = { version = "0.25.6", default-features = false, features = [ "avif", "bmp", "dds", "exr", "ff", "gif", "hdr", "ico", "jpeg", "png", "pnm", "qoi", "tga", "webp" ] }
|
||||
ratatui = { workspace = true }
|
||||
resvg = { workspace = true }
|
||||
scopeguard = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,27 +1,24 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
use std::{path::{Path, PathBuf}, sync::{LazyLock, RwLock}};
|
||||
|
||||
use anyhow::Result;
|
||||
use image::{DynamicImage, ExtendedColorType, ImageDecoder, ImageEncoder, ImageError, ImageReader, ImageResult, Limits, codecs::{jpeg::JpegEncoder, png::PngEncoder}, imageops::FilterType, metadata::Orientation};
|
||||
use image::{DynamicImage, ExtendedColorType, ImageBuffer, ImageDecoder, ImageEncoder, ImageError, ImageReader, ImageResult, Limits, codecs::{jpeg::JpegEncoder, png::PngEncoder}, error::UnsupportedErrorKind, imageops::FilterType, metadata::Orientation};
|
||||
use ratatui::layout::Rect;
|
||||
use resvg::{tiny_skia::{Pixmap, Transform}, usvg::{Options, Tree}};
|
||||
use yazi_config::YAZI;
|
||||
|
||||
use crate::Dimension;
|
||||
|
||||
pub static GLOBAL_OPTIONS: LazyLock<RwLock<Options<'static>>> =
|
||||
LazyLock::new(|| RwLock::new(Options::default()));
|
||||
|
||||
pub struct Image;
|
||||
|
||||
impl Image {
|
||||
pub async fn precache(path: &Path, cache: PathBuf) -> Result<()> {
|
||||
let (mut img, orientation, icc) = Self::decode_from(path).await?;
|
||||
let (w, h) = Self::flip_size(orientation, (YAZI.preview.max_width, YAZI.preview.max_height));
|
||||
let (img, icc) =
|
||||
Self::decode_to_fit_from(path, YAZI.preview.max_width, YAZI.preview.max_height).await?;
|
||||
|
||||
let buf = tokio::task::spawn_blocking(move || {
|
||||
if img.width() > w || img.height() > h {
|
||||
img = img.resize(w, h, Self::filter());
|
||||
}
|
||||
if orientation != Orientation::NoTransforms {
|
||||
img.apply_orientation(orientation);
|
||||
}
|
||||
|
||||
let mut buf = Vec::new();
|
||||
if img.color().has_alpha() {
|
||||
let rgba = img.into_rgba8();
|
||||
|
|
@ -42,25 +39,8 @@ impl Image {
|
|||
}
|
||||
|
||||
pub(super) async fn downscale(path: &Path, rect: Rect) -> Result<DynamicImage> {
|
||||
let (mut img, orientation, _) = Self::decode_from(path).await?;
|
||||
let (w, h) = Self::flip_size(orientation, Self::max_pixel(rect));
|
||||
|
||||
// Fast path.
|
||||
if img.width() <= w && img.height() <= h && orientation == Orientation::NoTransforms {
|
||||
return Ok(img);
|
||||
}
|
||||
|
||||
let img = tokio::task::spawn_blocking(move || {
|
||||
if img.width() > w || img.height() > h {
|
||||
img = img.resize(w, h, Self::filter())
|
||||
}
|
||||
if orientation != Orientation::NoTransforms {
|
||||
img.apply_orientation(orientation);
|
||||
}
|
||||
img
|
||||
})
|
||||
.await?;
|
||||
|
||||
let (width, height) = Self::max_pixel(rect);
|
||||
let (img, _) = Self::decode_to_fit_from(path, width, height).await?;
|
||||
Ok(img)
|
||||
}
|
||||
|
||||
|
|
@ -96,7 +76,54 @@ impl Image {
|
|||
}
|
||||
}
|
||||
|
||||
async fn decode_from(path: &Path) -> ImageResult<(DynamicImage, Orientation, Option<Vec<u8>>)> {
|
||||
async fn decode_to_fit_from(
|
||||
path: &Path,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<(DynamicImage, Option<Vec<u8>>)> {
|
||||
let path = path.to_owned();
|
||||
|
||||
tokio::task::spawn_blocking(move || {
|
||||
Self::try_decode_raster(&path, width, height).or_else(|err| match err {
|
||||
ImageError::Unsupported(ref unsupported) => match unsupported.kind() {
|
||||
UnsupportedErrorKind::Format(_) => Self::try_decode_svg(&path, width, height),
|
||||
_ => Err(err.into()),
|
||||
},
|
||||
_ => Err(err.into()),
|
||||
})
|
||||
})
|
||||
.await
|
||||
.map_err(|e| ImageError::IoError(e.into()))?
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_decode_raster(
|
||||
path: &Path,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> ImageResult<(DynamicImage, Option<Vec<u8>>)> {
|
||||
let limits = Self::build_limits();
|
||||
let mut reader = ImageReader::open(path)?;
|
||||
reader.limits(limits);
|
||||
let mut decoder = reader.with_guessed_format()?.into_decoder()?;
|
||||
let orientation = decoder.orientation().unwrap_or(Orientation::NoTransforms);
|
||||
let icc = decoder.icc_profile().unwrap_or_default();
|
||||
|
||||
let mut img = DynamicImage::from_decoder(decoder)?;
|
||||
let (w, h) = Self::flip_size(orientation, (width, height));
|
||||
|
||||
if img.width() > w || img.height() > h {
|
||||
img = img.resize(w, h, Self::filter());
|
||||
}
|
||||
if orientation != Orientation::NoTransforms {
|
||||
img.apply_orientation(orientation);
|
||||
}
|
||||
|
||||
Ok((img, icc))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn build_limits() -> Limits {
|
||||
let mut limits = Limits::no_limits();
|
||||
if YAZI.tasks.image_alloc > 0 {
|
||||
limits.max_alloc = Some(YAZI.tasks.image_alloc as u64);
|
||||
|
|
@ -107,20 +134,76 @@ impl Image {
|
|||
if YAZI.tasks.image_bound[1] > 0 {
|
||||
limits.max_image_height = Some(YAZI.tasks.image_bound[1] as u32);
|
||||
}
|
||||
limits
|
||||
}
|
||||
|
||||
let path = path.to_owned();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let mut reader = ImageReader::open(path)?;
|
||||
reader.limits(limits);
|
||||
#[inline]
|
||||
fn try_decode_svg(
|
||||
path: &Path,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<(DynamicImage, Option<Vec<u8>>)> {
|
||||
let pixmap = Self::render_svg_to_fit_from(path, width, height)?;
|
||||
let (width, height) = (pixmap.width(), pixmap.height());
|
||||
let mut container = pixmap.take();
|
||||
|
||||
let mut decoder = reader.with_guessed_format()?.into_decoder()?;
|
||||
let orientation = decoder.orientation().unwrap_or(Orientation::NoTransforms);
|
||||
let icc = decoder.icc_profile().unwrap_or_default();
|
||||
for rgba in container.chunks_exact_mut(4) {
|
||||
let alpha = rgba[3];
|
||||
if alpha != 0xff {
|
||||
let pixel: &mut [u8; 4] = unsafe { rgba.try_into().unwrap_unchecked() };
|
||||
let a = alpha as f64 / 255.0;
|
||||
pixel[0] = (pixel[0] as f64 / a + 0.5) as u8;
|
||||
pixel[1] = (pixel[1] as f64 / a + 0.5) as u8;
|
||||
pixel[2] = (pixel[2] as f64 / a + 0.5) as u8;
|
||||
}
|
||||
}
|
||||
|
||||
Ok((DynamicImage::from_decoder(decoder)?, orientation, icc))
|
||||
})
|
||||
.await
|
||||
.map_err(|e| ImageError::IoError(e.into()))?
|
||||
let img = ImageBuffer::from_raw(width, height, container)
|
||||
.ok_or_else(|| anyhow::anyhow!("Failed to create image buffer"))?;
|
||||
|
||||
Ok((DynamicImage::ImageRgba8(img), None))
|
||||
}
|
||||
|
||||
/// Helper function to rasterize an SVG to a pixmap fitting within max_width
|
||||
/// and max_height.
|
||||
fn render_svg_to_fit_from(path: &Path, max_width: u32, max_height: u32) -> Result<Pixmap> {
|
||||
let svg = std::fs::read(path)?;
|
||||
let options_guard =
|
||||
GLOBAL_OPTIONS.read().map_err(|e| anyhow::anyhow!("RwLock poisoned: {}", e))?;
|
||||
let tree = Tree::from_data(&svg, &options_guard)?;
|
||||
let (width, height, transform) = Self::svg_size_and_scale(&tree, max_width, max_height);
|
||||
|
||||
let mut pixmap =
|
||||
Pixmap::new(width, height).ok_or_else(|| anyhow::anyhow!("Cannot create pixmap"))?;
|
||||
|
||||
resvg::render(&tree, transform, &mut pixmap.as_mut());
|
||||
|
||||
Ok(pixmap)
|
||||
}
|
||||
|
||||
fn svg_size_and_scale(tree: &Tree, max_width: u32, max_height: u32) -> (u32, u32, Transform) {
|
||||
// It is Ok. The max_width and max_height could not be larger then monitor
|
||||
// dimensions which much less then f32::MAX
|
||||
let max_width = max_width as f32;
|
||||
let max_height = max_height as f32;
|
||||
let mut width = tree.size().width();
|
||||
let mut height = tree.size().height();
|
||||
|
||||
for node in tree.root().children() {
|
||||
if let Some(bounding_box) = node.abs_layer_bounding_box() {
|
||||
width = width.max(bounding_box.width());
|
||||
height = height.max(bounding_box.height());
|
||||
}
|
||||
}
|
||||
if width <= max_width && height <= max_height {
|
||||
return (width.floor() as u32, height.floor() as u32, Transform::from_scale(1.0, 1.0));
|
||||
}
|
||||
let ratio = f32::min(max_width / width, max_height / height);
|
||||
(
|
||||
(width * ratio).floor() as u32,
|
||||
(height * ratio).floor() as u32,
|
||||
Transform::from_scale(ratio, ratio),
|
||||
)
|
||||
}
|
||||
|
||||
fn flip_size(orientation: Orientation, (w, h): (u32, u32)) -> (u32, u32) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
use std::path::Path;
|
||||
use std::{collections::HashSet, path::Path};
|
||||
|
||||
use image::{ImageDecoder, ImageError};
|
||||
use resvg::usvg::{Font, Group, ImageKind, Node, Tree};
|
||||
|
||||
use super::image::GLOBAL_OPTIONS;
|
||||
|
||||
pub type ImageFormat = image::ImageFormat;
|
||||
pub type ImageColor = image::ColorType;
|
||||
|
|
@ -42,3 +45,77 @@ impl ImageInfo {
|
|||
.map_err(|e| ImageError::IoError(e.into()))?
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SvgInfo {
|
||||
pub width: f32,
|
||||
pub height: f32,
|
||||
pub fonts: HashSet<Font>,
|
||||
pub layers: Vec<String>,
|
||||
}
|
||||
|
||||
impl SvgInfo {
|
||||
pub async fn new(path: &Path) -> anyhow::Result<Self> {
|
||||
let path = path.to_owned();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let svg = std::fs::read(path)?;
|
||||
let options_guard =
|
||||
GLOBAL_OPTIONS.read().map_err(|e| anyhow::anyhow!("RwLock poisoned: {}", e))?;
|
||||
let tree = Tree::from_data(&svg, &options_guard)?;
|
||||
|
||||
let mut info = SvgInfo {
|
||||
width: tree.size().width().round(),
|
||||
height: tree.size().height().round(),
|
||||
fonts: HashSet::new(),
|
||||
layers: Vec::new(),
|
||||
};
|
||||
|
||||
Self::collect_group(tree.root(), &mut info);
|
||||
|
||||
Ok(info)
|
||||
})
|
||||
.await?
|
||||
}
|
||||
|
||||
fn collect_group(group: &Group, info: &mut SvgInfo) {
|
||||
if !group.id().is_empty() {
|
||||
info.layers.push(group.id().to_string());
|
||||
}
|
||||
|
||||
for node in group.children() {
|
||||
Self::collect_node(node, info);
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_node(node: &Node, info: &mut SvgInfo) {
|
||||
if let Some(bounding_box) = node.abs_layer_bounding_box() {
|
||||
info.width = info.width.max(bounding_box.width());
|
||||
info.height = info.height.max(bounding_box.height());
|
||||
}
|
||||
match node {
|
||||
Node::Group(g) => Self::collect_group(g, info),
|
||||
|
||||
Node::Path(_) => {}
|
||||
|
||||
Node::Text(t) => {
|
||||
for chunk in t.chunks() {
|
||||
for span in chunk.spans() {
|
||||
if !info.fonts.contains(span.font()) {
|
||||
info.fonts.insert(span.font().clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Node::Image(i) => {
|
||||
if let ImageKind::SVG(sub_tree) = &i.kind() {
|
||||
Self::collect_group(sub_tree.root(), info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node.subroots(|subroot: &Group| {
|
||||
Self::collect_group(subroot, info);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ mlua = { workspace = true }
|
|||
parking_lot = { workspace = true }
|
||||
paste = { workspace = true }
|
||||
ratatui = { workspace = true }
|
||||
resvg = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
syntect = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,45 +1,57 @@
|
|||
local M = {}
|
||||
|
||||
function M:peek(job)
|
||||
local start, cache = os.clock(), ya.file_cache(job)
|
||||
if not cache then
|
||||
return
|
||||
end
|
||||
|
||||
local ok, err = self:preload(job)
|
||||
if not ok or err then
|
||||
return
|
||||
end
|
||||
|
||||
local start = os.clock()
|
||||
ya.sleep(math.max(0, rt.preview.image_delay / 1000 + start - os.clock()))
|
||||
ya.image_show(cache, job.area)
|
||||
ya.image_show(job.file.url, job.area)
|
||||
ya.preview_widgets(job, {})
|
||||
end
|
||||
|
||||
function M:seek() end
|
||||
|
||||
function M:preload(job)
|
||||
local cache = ya.file_cache(job)
|
||||
if not cache or fs.cha(cache) then
|
||||
return true
|
||||
end
|
||||
function M:preload(job) end
|
||||
|
||||
-- stylua: ignore
|
||||
local cmd = require("magick").with_env():args {
|
||||
"-size", string.format("%dx%d^", rt.preview.max_width, rt.preview.max_height),
|
||||
string.format("rsvg:%s", job.file.url), "-strip",
|
||||
"-quality", rt.preview.image_quality,
|
||||
string.format("JPG:%s", cache),
|
||||
}
|
||||
function M:spot(job)
|
||||
local rows = self:spot_base(job)
|
||||
rows[#rows + 1] = ui.Row {}
|
||||
|
||||
local status, err = cmd:status()
|
||||
if status then
|
||||
return status.success
|
||||
else
|
||||
return true, Err("Failed to start `magick`, error: %s", err)
|
||||
end
|
||||
ya.spot_table(
|
||||
job,
|
||||
ui.Table(ya.list_merge(rows, require("file"):spot_base(job)))
|
||||
:area(ui.Pos { "center", w = 60, h = 20 })
|
||||
:row(job.skip)
|
||||
:row(1)
|
||||
:col(1)
|
||||
:col_style(th.spot.tbl_col)
|
||||
:cell_style(th.spot.tbl_cell)
|
||||
:widths { ui.Constraint.Length(14), ui.Constraint.Fill(1) }
|
||||
)
|
||||
end
|
||||
|
||||
function M:spot(job) require("file"):spot(job) end
|
||||
function M:spot_base(job)
|
||||
local info = ya.svg_info(job.file.url)
|
||||
if not info then
|
||||
return {}
|
||||
end
|
||||
|
||||
local rows = {
|
||||
ui.Row({ "SVG Image" }):style(ui.Style():fg("green")),
|
||||
ui.Row { " Size:", string.format("%.0fx%.0f", info.w, info.h) },
|
||||
}
|
||||
|
||||
if #info.layers > 0 then
|
||||
rows[#rows + 1] = ui.Row { " Layers:", table.concat(info.layers, ", ") }
|
||||
end
|
||||
|
||||
if #info.fonts > 0 then
|
||||
rows[#rows + 1] = ui.Row({ " Fonts:", tostring(info.fonts[1]) })
|
||||
|
||||
for i = 2, #info.fonts do
|
||||
rows[#rows + 1] = ui.Row({ "", tostring(info.fonts[i]) })
|
||||
end
|
||||
end
|
||||
|
||||
return rows
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use std::ops::Deref;
|
||||
use std::{fmt::Write, ops::Deref};
|
||||
|
||||
use mlua::{MetaMethod, UserData};
|
||||
use resvg::usvg;
|
||||
|
||||
pub struct ImageInfo(yazi_adapter::ImageInfo);
|
||||
|
||||
|
|
@ -81,3 +82,115 @@ impl UserData for ImageColor {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SvgInfo(yazi_adapter::SvgInfo);
|
||||
|
||||
impl Deref for SvgInfo {
|
||||
type Target = yazi_adapter::SvgInfo;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl From<yazi_adapter::SvgInfo> for SvgInfo {
|
||||
fn from(value: yazi_adapter::SvgInfo) -> Self { Self(value) }
|
||||
}
|
||||
|
||||
impl UserData for SvgInfo {
|
||||
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("w", |_, this| Ok(this.width));
|
||||
fields.add_field_method_get("h", |_, this| Ok(this.height));
|
||||
|
||||
fields.add_field_method_get("layers", |lua, this| {
|
||||
let table = lua.create_table()?;
|
||||
for (i, layer) in this.layers.iter().enumerate() {
|
||||
table.set(i + 1, layer.clone())?;
|
||||
}
|
||||
Ok(table)
|
||||
});
|
||||
|
||||
fields.add_field_method_get("fonts", |lua, this| {
|
||||
let table = lua.create_table()?;
|
||||
for (i, font) in this.fonts.iter().enumerate() {
|
||||
table.set(i + 1, Font(font.clone()))?;
|
||||
}
|
||||
Ok(table)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Font(usvg::Font);
|
||||
|
||||
impl Deref for Font {
|
||||
type Target = usvg::Font;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
trait ToStaticStr {
|
||||
fn as_str(&self) -> &'static str;
|
||||
}
|
||||
|
||||
impl ToStaticStr for usvg::FontStyle {
|
||||
fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Normal => "Normal",
|
||||
Self::Italic => "Italic",
|
||||
Self::Oblique => "Oblique",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToStaticStr for usvg::FontStretch {
|
||||
fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::UltraCondensed => "UltraCondensed",
|
||||
Self::ExtraCondensed => "ExtraCondensed",
|
||||
Self::Condensed => "Condensed",
|
||||
Self::SemiCondensed => "SemiCondensed",
|
||||
Self::Normal => "Normal",
|
||||
Self::SemiExpanded => "SemiExpanded",
|
||||
Self::Expanded => "Expanded",
|
||||
Self::ExtraExpanded => "ExtraExpanded",
|
||||
Self::UltraExpanded => "UltraExpanded",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Font {
|
||||
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("families", |_, this| {
|
||||
let mut families = String::new();
|
||||
for (i, family) in this.families().iter().enumerate() {
|
||||
if i > 0 {
|
||||
families.push_str(", ");
|
||||
}
|
||||
write!(&mut families, "{}", family).unwrap();
|
||||
}
|
||||
Ok(families)
|
||||
});
|
||||
fields.add_field_method_get("style", |_, this| Ok(this.style().as_str()));
|
||||
fields.add_field_method_get("stretch", |_, this| Ok(this.stretch().as_str()));
|
||||
|
||||
fields.add_field_method_get("weight", |_, this| Ok(this.weight()));
|
||||
}
|
||||
|
||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(MetaMethod::ToString, |_, this, ()| {
|
||||
let mut families = String::new();
|
||||
for (i, family) in this.families().iter().enumerate() {
|
||||
if i > 0 {
|
||||
families.push_str(", ");
|
||||
}
|
||||
write!(&mut families, "{}", family).unwrap();
|
||||
}
|
||||
|
||||
Ok(format!(
|
||||
"{}, style: {}, stretch: {}, weight: {}",
|
||||
families,
|
||||
this.style().as_str(),
|
||||
this.stretch().as_str(),
|
||||
this.weight()
|
||||
))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#![allow(clippy::module_inception)]
|
||||
|
||||
yazi_macro::mod_flat!(
|
||||
app cache call image json layer log preview spot sync target text time user utils
|
||||
app cache call image json layer log preview spot svg sync target text time user utils
|
||||
);
|
||||
|
|
|
|||
76
yazi-plugin/src/utils/svg.rs
Normal file
76
yazi-plugin/src/utils/svg.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use mlua::{Function, IntoLua, Lua, Value};
|
||||
use resvg::usvg::fontdb::Database;
|
||||
use yazi_adapter::GLOBAL_OPTIONS;
|
||||
use yazi_binding::UrlRef;
|
||||
|
||||
use super::Utils;
|
||||
use crate::bindings::SvgInfo;
|
||||
|
||||
impl Utils {
|
||||
pub(super) fn svg_info(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_async_function(|lua, url: UrlRef| async move {
|
||||
if let Ok(info) = yazi_adapter::SvgInfo::new(&url).await {
|
||||
SvgInfo::from(info).into_lua(&lua)
|
||||
} else {
|
||||
Value::Nil.into_lua(&lua)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn set_svg_font_family(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_function(|_, (family, font_name): (mlua::String, mlua::String)| {
|
||||
let font = font_name.to_str()?;
|
||||
|
||||
Self::with_mut_fontdb(|db| {
|
||||
match family.to_str()?.as_ref() {
|
||||
"serif" => db.set_serif_family(&*font),
|
||||
"sans-serif" => db.set_sans_serif_family(&*font),
|
||||
"cursive" => db.set_cursive_family(&*font),
|
||||
"fantasy" => db.set_fantasy_family(&*font),
|
||||
"monospace" => db.set_monospace_family(&*font),
|
||||
_ => return Err(mlua::Error::external("Invalid font family")),
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn load_system_fonts(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_function(|_, ()| {
|
||||
Self::with_mut_fontdb(|db| {
|
||||
db.load_system_fonts();
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn load_font_file(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_function(|_, path: mlua::String| {
|
||||
Self::with_mut_fontdb(|db| {
|
||||
db.load_font_file(&*path.to_str()?)
|
||||
.map_err(|e| mlua::Error::external(format!("Failed to load font file: {e}")))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn load_fonts_dir(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_function(|_, dir: mlua::String| {
|
||||
Self::with_mut_fontdb(|db| {
|
||||
db.load_fonts_dir(&*dir.to_str()?);
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn with_mut_fontdb<F>(f: F) -> mlua::Result<()>
|
||||
where
|
||||
F: FnOnce(&mut Database) -> mlua::Result<()>,
|
||||
{
|
||||
let mut options = GLOBAL_OPTIONS
|
||||
.write()
|
||||
.map_err(|e| mlua::Error::external(format!("RwLock poisoned: {}", e)))?;
|
||||
f(Arc::make_mut(&mut options.fontdb))
|
||||
}
|
||||
}
|
||||
|
|
@ -49,6 +49,13 @@ pub fn compose(lua: &Lua, isolate: bool) -> mlua::Result<Value> {
|
|||
b"spot_table" => Utils::spot_table(lua)?,
|
||||
b"spot_widgets" => Utils::spot_widgets(lua)?,
|
||||
|
||||
// Svg
|
||||
b"load_font_file" => Utils::load_font_file(lua)?,
|
||||
b"load_fonts_dir" => Utils::load_fonts_dir(lua)?,
|
||||
b"load_system_fonts" => Utils::load_system_fonts(lua)?,
|
||||
b"svg_info" => Utils::svg_info(lua)?,
|
||||
b"set_svg_font_family" => Utils::set_svg_font_family(lua)?,
|
||||
|
||||
// Sync
|
||||
b"sync" => Utils::sync(lua, isolate)?,
|
||||
b"chan" => Utils::chan(lua)?,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue