Skip to content

Commit 5ad40d9

Browse files
authored
feat!: switch to resvg as the SVG renderer (#2581)
1 parent be00881 commit 5ad40d9

File tree

9 files changed

+241
-67
lines changed

9 files changed

+241
-67
lines changed

Cargo.lock

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

yazi-plugin/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,12 @@ unicode-width = { workspace = true }
4646
yazi-prebuilt = "0.1.0"
4747

4848
[target."cfg(unix)".dependencies]
49+
libc = { workspace = true }
4950
uzers = { workspace = true }
5051

5152
[target."cfg(windows)".dependencies]
5253
clipboard-win = "5.4.0"
54+
windows-sys = { version = "0.59.0", features = [ "Win32_Security", "Win32_System_JobObjects", "Win32_System_Threading" ] }
5355

5456
[target.'cfg(target_os = "macos")'.dependencies]
5557
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }

yazi-plugin/preset/plugins/svg.lua

+33-7
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,44 @@ function M:preload(job)
2525
end
2626

2727
-- stylua: ignore
28-
local cmd = require("magick").with_env():args {
29-
"-size", string.format("%dx%d^", rt.preview.max_width, rt.preview.max_height),
30-
string.format("rsvg:%s", job.file.url), "-strip",
31-
"-quality", rt.preview.image_quality,
32-
string.format("JPG:%s", cache),
28+
local cmd = Command("resvg"):args {
29+
"-w", rt.preview.max_width, "-h", rt.preview.max_height,
30+
"--image-rendering", "optimizeSpeed",
31+
tostring(job.file.url), tostring(cache)
3332
}
33+
if rt.tasks.image_alloc > 0 then
34+
cmd = cmd:memory(rt.tasks.image_alloc)
35+
end
36+
37+
local child, err = cmd:spawn()
38+
if not child then
39+
return true, Err("Failed to start `resvg`, error: %s", err)
40+
end
41+
42+
local status, err
43+
while true do
44+
ya.sleep(0.2)
45+
46+
status, err = child:try_wait()
47+
if status or err then
48+
break
49+
end
50+
51+
local id, mem = child:id(), nil
52+
if id then
53+
mem = ya.proc_info(id).mem_resident
54+
end
55+
if mem and mem > rt.tasks.image_alloc then
56+
child:start_kill()
57+
err = Err("memory limit exceeded, pid: %s, memory: %s", id, mem)
58+
break
59+
end
60+
end
3461

35-
local status, err = cmd:status()
3662
if status then
3763
return status.success
3864
else
39-
return true, Err("Failed to start `magick`, error: %s", err)
65+
return true, Err("Error while running `resvg`: %s", err)
4066
end
4167
end
4268

yazi-plugin/preset/plugins/video.lua

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ function M:preload(job)
5858
"-skip_frame", "nokey", "-ss", ss,
5959
"-an", "-sn", "-dn",
6060
"-i", tostring(job.file.url),
61-
"-vframes", 1,
61+
"-map", "0:v", "-vframes", 1,
6262
"-q:v", qv,
6363
"-vf", string.format("scale=-1:'min(%d,ih)':flags=fast_bilinear", rt.preview.max_height),
6464
"-f", "image2",

yazi-plugin/src/process/child.rs

+90-51
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,103 @@
1-
use std::{ops::DerefMut, time::Duration};
1+
use std::{ops::DerefMut, process::ExitStatus, time::Duration};
22

33
use futures::future::try_join3;
4-
use mlua::{AnyUserData, ExternalError, IntoLua, IntoLuaMulti, Table, UserData, Value};
4+
use mlua::{AnyUserData, ExternalError, IntoLua, IntoLuaMulti, Table, UserData, UserDataFields, UserDataMethods, Value};
55
use tokio::{io::{self, AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader, BufWriter}, process::{ChildStderr, ChildStdin, ChildStdout}, select};
66
use yazi_binding::Error;
77

88
use super::Status;
99
use crate::process::Output;
1010

1111
pub struct Child {
12-
inner: tokio::process::Child,
13-
stdin: Option<BufWriter<ChildStdin>>,
14-
stdout: Option<BufReader<ChildStdout>>,
15-
stderr: Option<BufReader<ChildStderr>>,
12+
inner: tokio::process::Child,
13+
stdin: Option<BufWriter<ChildStdin>>,
14+
stdout: Option<BufReader<ChildStdout>>,
15+
stderr: Option<BufReader<ChildStderr>>,
16+
#[cfg(windows)]
17+
job_handle: Option<std::os::windows::io::RawHandle>,
18+
}
19+
20+
#[cfg(windows)]
21+
impl Drop for Child {
22+
fn drop(&mut self) {
23+
if let Some(h) = self.job_handle.take() {
24+
unsafe { windows_sys::Win32::Foundation::CloseHandle(h) };
25+
}
26+
}
1627
}
1728

1829
impl Child {
19-
pub fn new(mut inner: tokio::process::Child) -> Self {
30+
pub fn new(
31+
mut inner: tokio::process::Child,
32+
#[cfg(windows)] job_handle: Option<std::os::windows::io::RawHandle>,
33+
) -> Self {
2034
let stdin = inner.stdin.take().map(BufWriter::new);
2135
let stdout = inner.stdout.take().map(BufReader::new);
2236
let stderr = inner.stderr.take().map(BufReader::new);
23-
Self { inner, stdin, stdout, stderr }
37+
Self {
38+
inner,
39+
stdin,
40+
stdout,
41+
stderr,
42+
#[cfg(windows)]
43+
job_handle,
44+
}
2445
}
25-
}
2646

27-
impl UserData for Child {
28-
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
29-
#[inline]
30-
async fn read_line(me: &mut Child) -> (Option<Vec<u8>>, u8) {
31-
async fn read(r: Option<impl AsyncBufReadExt + Unpin>) -> Option<Vec<u8>> {
32-
let mut buf = Vec::new();
33-
match r?.read_until(b'\n', &mut buf).await {
34-
Ok(0) | Err(_) => None,
35-
Ok(_) => Some(buf),
36-
}
47+
pub(super) async fn wait(&mut self) -> io::Result<ExitStatus> {
48+
drop(self.stdin.take());
49+
self.inner.wait().await
50+
}
51+
52+
pub(super) async fn status(&mut self) -> io::Result<ExitStatus> {
53+
drop(self.stdin.take());
54+
drop(self.stdout.take());
55+
drop(self.stderr.take());
56+
self.inner.wait().await
57+
}
58+
59+
async fn read_line(&mut self) -> (Option<Vec<u8>>, u8) {
60+
async fn read(r: Option<impl AsyncBufReadExt + Unpin>) -> Option<Vec<u8>> {
61+
let mut buf = Vec::new();
62+
match r?.read_until(b'\n', &mut buf).await {
63+
Ok(0) | Err(_) => None,
64+
Ok(_) => Some(buf),
3765
}
66+
}
67+
68+
select! {
69+
r @ Some(_) = read(self.stdout.as_mut()) => (r, 0u8),
70+
r @ Some(_) = read(self.stderr.as_mut()) => (r, 1u8),
71+
else => (None, 2u8),
72+
}
73+
}
3874

39-
select! {
40-
r @ Some(_) = read(me.stdout.as_mut()) => (r, 0u8),
41-
r @ Some(_) = read(me.stderr.as_mut()) => (r, 1u8),
42-
else => (None, 2u8),
75+
pub(super) async fn wait_with_output(mut self) -> io::Result<std::process::Output> {
76+
async fn read(r: &mut Option<impl AsyncBufReadExt + Unpin>) -> io::Result<Vec<u8>> {
77+
let mut vec = Vec::new();
78+
if let Some(r) = r.as_mut() {
79+
r.read_to_end(&mut vec).await?;
4380
}
81+
Ok(vec)
4482
}
4583

84+
// Ensure stdin is closed so the child isn't stuck waiting on input while the
85+
// parent is waiting for it to exit.
86+
drop(self.stdin.take());
87+
88+
// Drop happens after `try_join` due to <https://github.com/tokio-rs/tokio/issues/4309>
89+
let mut stdout = self.stdout.take();
90+
let mut stderr = self.stderr.take();
91+
92+
let result = try_join3(self.inner.wait(), read(&mut stdout), read(&mut stderr)).await?;
93+
Ok(std::process::Output { status: result.0, stdout: result.1, stderr: result.2 })
94+
}
95+
}
96+
97+
impl UserData for Child {
98+
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
99+
methods.add_method("id", |_, me, ()| Ok(me.inner.id()));
100+
46101
methods.add_async_method_mut("read", |_, mut me, len: usize| async move {
47102
async fn read(r: Option<impl AsyncBufReadExt + Unpin>, len: usize) -> Option<Vec<u8>> {
48103
let mut r = r?;
@@ -62,14 +117,15 @@ impl UserData for Child {
62117
})
63118
});
64119
methods.add_async_method_mut("read_line", |lua, mut me, ()| async move {
65-
match read_line(&mut me).await {
120+
match me.read_line().await {
66121
(Some(b), event) => (lua.create_string(b)?, event).into_lua_multi(&lua),
67122
(None, event) => (Value::Nil, event).into_lua_multi(&lua),
68123
}
69124
});
125+
// TODO: deprecate this method
70126
methods.add_async_method_mut("read_line_with", |lua, mut me, options: Table| async move {
71127
let timeout = Duration::from_millis(options.raw_get("timeout")?);
72-
let Ok(result) = tokio::time::timeout(timeout, read_line(&mut me)).await else {
128+
let Ok(result) = tokio::time::timeout(timeout, me.read_line()).await else {
73129
return (Value::Nil, 3u8).into_lua_multi(&lua);
74130
};
75131
match result {
@@ -98,38 +154,21 @@ impl UserData for Child {
98154
});
99155

100156
methods.add_async_method_mut("wait", |lua, mut me, ()| async move {
101-
drop(me.stdin.take());
102-
match me.inner.wait().await {
157+
match me.wait().await {
103158
Ok(status) => (Status::new(status), Value::Nil).into_lua_multi(&lua),
104159
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
105160
}
106161
});
107162
methods.add_async_function("wait_with_output", |lua, ud: AnyUserData| async move {
108-
async fn read_to_end(r: &mut Option<impl AsyncBufReadExt + Unpin>) -> io::Result<Vec<u8>> {
109-
let mut vec = Vec::new();
110-
if let Some(r) = r.as_mut() {
111-
r.read_to_end(&mut vec).await?;
112-
}
113-
Ok(vec)
163+
match ud.take::<Self>()?.wait_with_output().await {
164+
Ok(output) => (Output::new(output), Value::Nil).into_lua_multi(&lua),
165+
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
114166
}
115-
116-
let mut me = ud.take::<Self>()?;
117-
let mut stdout_pipe = me.stdout.take();
118-
let mut stderr_pipe = me.stderr.take();
119-
120-
let stdout_fut = read_to_end(&mut stdout_pipe);
121-
let stderr_fut = read_to_end(&mut stderr_pipe);
122-
123-
drop(me.stdin.take());
124-
let result = try_join3(me.inner.wait(), stdout_fut, stderr_fut).await;
125-
drop(stdout_pipe);
126-
drop(stderr_pipe);
127-
128-
match result {
129-
Ok((status, stdout, stderr)) => {
130-
(Output::new(std::process::Output { status, stdout, stderr }), Value::Nil)
131-
.into_lua_multi(&lua)
132-
}
167+
});
168+
methods.add_async_method_mut("try_wait", |lua, mut me, ()| async move {
169+
match me.inner.try_wait() {
170+
Ok(Some(status)) => (Status::new(status), Value::Nil).into_lua_multi(&lua),
171+
Ok(None) => (Value::Nil, Value::Nil).into_lua_multi(&lua),
133172
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
134173
}
135174
});

0 commit comments

Comments
 (0)